PageRenderTime 26ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/lithium/libraries/lithium/data/source/database/adapter/MySql.php

https://github.com/brtriver/sukonv
PHP | 437 lines | 235 code | 47 blank | 155 comment | 40 complexity | 11aae7b7a3cc8180308d14b0e9f56704 MD5 | raw file
  1. <?php
  2. /**
  3. * Lithium: the most rad php framework
  4. *
  5. * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org)
  6. * @license http://opensource.org/licenses/bsd-license.php The BSD License
  7. */
  8. namespace lithium\data\source\database\adapter;
  9. use lithium\data\model\QueryException;
  10. /**
  11. * Extends the `Database` class to implement the necessary SQL-formatting and resultset-fetching
  12. * features for working with MySQL databases.
  13. *
  14. * For more information on configuring the database connection, see the `__construct()` method.
  15. *
  16. * @see lithium\data\source\database\adapter\MySql::__construct()
  17. */
  18. class MySql extends \lithium\data\source\Database {
  19. protected $_classes = array(
  20. 'entity' => 'lithium\data\entity\Record',
  21. 'set' => 'lithium\data\collection\RecordSet',
  22. 'relationship' => 'lithium\data\model\Relationship',
  23. 'result' => 'lithium\data\source\database\adapter\my_sql\Result'
  24. );
  25. /**
  26. * MySQL column type definitions.
  27. *
  28. * @var array
  29. */
  30. protected $_columns = array(
  31. 'primary_key' => array('name' => 'NOT NULL AUTO_INCREMENT'),
  32. 'string' => array('name' => 'varchar', 'length' => 255),
  33. 'text' => array('name' => 'text'),
  34. 'integer' => array('name' => 'int', 'length' => 11, 'formatter' => 'intval'),
  35. 'float' => array('name' => 'float', 'formatter' => 'floatval'),
  36. 'datetime' => array('name' => 'datetime', 'format' => 'Y-m-d H:i:s', 'formatter' => 'date'),
  37. 'timestamp' => array(
  38. 'name' => 'timestamp', 'format' => 'Y-m-d H:i:s', 'formatter' => 'date'
  39. ),
  40. 'time' => array('name' => 'time', 'format' => 'H:i:s', 'formatter' => 'date'),
  41. 'date' => array('name' => 'date', 'format' => 'Y-m-d', 'formatter' => 'date'),
  42. 'binary' => array('name' => 'blob'),
  43. 'boolean' => array('name' => 'tinyint', 'length' => 1)
  44. );
  45. /**
  46. * Pair of opening and closing quote characters used for quoting identifiers in queries.
  47. *
  48. * @var array
  49. */
  50. protected $_quotes = array('`', '`');
  51. /**
  52. * MySQL-specific value denoting whether or not table aliases should be used in DELETE and
  53. * UPDATE queries.
  54. *
  55. * @var boolean
  56. */
  57. protected $_useAlias = true;
  58. /**
  59. * Constructs the MySQL adapter and sets the default port to 3306.
  60. *
  61. * @see lithium\data\source\Database::__construct()
  62. * @see lithium\data\Source::__construct()
  63. * @see lithium\data\Connections::add()
  64. * @param array $config Configuration options for this class. For additional configuration,
  65. * see `lithium\data\source\Database` and `lithium\data\Source`. Available options
  66. * defined by this class:
  67. * - `'database'`: The name of the database to connect to. Defaults to 'lithium'.
  68. * - `'host'`: The IP or machine name where MySQL is running, followed by a colon,
  69. * followed by a port number or socket. Defaults to `'localhost:3306'`.
  70. * - `'persistent'`: If a persistent connection (if available) should be made.
  71. * Defaults to true.
  72. *
  73. * Typically, these parameters are set in `Connections::add()`, when adding the adapter to the
  74. * list of active connections.
  75. * @return The adapter instance.
  76. */
  77. public function __construct(array $config = array()) {
  78. $defaults = array('host' => 'localhost:3306', 'encoding' => null);
  79. parent::__construct($config + $defaults);
  80. }
  81. /**
  82. * Check for required PHP extension, or supported database feature.
  83. *
  84. * @param string $feature Test for support for a specific feature, i.e. `"transactions"` or
  85. * `"arrays"`.
  86. * @return boolean Returns `true` if the particular feature (or if MySQL) support is enabled,
  87. * otherwise `false`.
  88. */
  89. public static function enabled($feature = null) {
  90. if (!$feature) {
  91. return extension_loaded('mysql');
  92. }
  93. $features = array(
  94. 'arrays' => false,
  95. 'transactions' => false,
  96. 'booleans' => true,
  97. 'relationships' => true,
  98. );
  99. return isset($features[$feature]) ? $features[$feature] : null;
  100. }
  101. /**
  102. * Connects to the database using the options provided to the class constructor.
  103. *
  104. * @return boolean Returns `true` if a database connection could be established, otherwise
  105. * `false`.
  106. */
  107. public function connect() {
  108. $config = $this->_config;
  109. $this->_isConnected = false;
  110. $host = $config['host'];
  111. if (!$config['database']) {
  112. return false;
  113. }
  114. if (!$config['persistent']) {
  115. $this->connection = mysql_connect($host, $config['login'], $config['password'], true);
  116. } else {
  117. $this->connection = mysql_pconnect($host, $config['login'], $config['password']);
  118. }
  119. if (!$this->connection) {
  120. return false;
  121. }
  122. if (mysql_select_db($config['database'], $this->connection)) {
  123. $this->_isConnected = true;
  124. } else {
  125. return false;
  126. }
  127. if ($config['encoding']) {
  128. $this->encoding($config['encoding']);
  129. }
  130. $info = mysql_get_server_info($this->connection);
  131. $this->_useAlias = (boolean) version_compare($info, "4.1", ">=");
  132. return $this->_isConnected;
  133. }
  134. /**
  135. * Disconnects the adapter from the database.
  136. *
  137. * @return boolean True on success, else false.
  138. */
  139. public function disconnect() {
  140. if ($this->_isConnected) {
  141. $this->_isConnected = !mysql_close($this->connection);
  142. return !$this->_isConnected;
  143. }
  144. return true;
  145. }
  146. /**
  147. * Returns the list of tables in the currently-connected database.
  148. *
  149. * @param string $model The fully-name-spaced class name of the model object making the request.
  150. * @return array Returns an array of objects to which models can connect.
  151. * @filter This method can be filtered.
  152. */
  153. public function entities($model = null) {
  154. $_config = $this->_config;
  155. $params = compact('model');
  156. return $this->_filter(__METHOD__, $params, function($self, $params) use ($_config) {
  157. $name = $self->name($_config['database']);
  158. if (!$result = $self->invokeMethod('_execute', array("SHOW TABLES FROM {$name};"))) {
  159. return null;
  160. }
  161. $entities = array();
  162. while ($data = $result->next()) {
  163. list($entities[]) = $data;
  164. }
  165. return $entities;
  166. });
  167. }
  168. /**
  169. * Gets the column schema for a given MySQL table.
  170. *
  171. * @param mixed $entity Specifies the table name for which the schema should be returned, or
  172. * the class name of the model object requesting the schema, in which case the model
  173. * class will be queried for the correct table name.
  174. * @param array $meta
  175. * @return array Returns an associative array describing the given table's schema, where the
  176. * array keys are the available fields, and the values are arrays describing each
  177. * field, containing the following keys:
  178. * - `'type'`: The field type name
  179. * @filter This method can be filtered.
  180. */
  181. public function describe($entity, array $meta = array()) {
  182. $params = compact('entity', 'meta');
  183. return $this->_filter(__METHOD__, $params, function($self, $params) {
  184. extract($params);
  185. $name = $self->invokeMethod('_entityName', array($entity));
  186. $columns = $self->read("DESCRIBE {$name}", array('return' => 'array', 'schema' => array(
  187. 'field', 'type', 'null', 'key', 'default', 'extra'
  188. )));
  189. $fields = array();
  190. foreach ($columns as $column) {
  191. $match = $self->invokeMethod('_column', array($column['type']));
  192. $fields[$column['field']] = $match + array(
  193. 'null' => ($column['null'] == 'YES' ? true : false),
  194. 'default' => $column['default'],
  195. );
  196. }
  197. return $fields;
  198. });
  199. }
  200. /**
  201. * Gets or sets the encoding for the connection.
  202. *
  203. * @param $encoding
  204. * @return boolean|string If setting the encoding; returns true on success, else false.
  205. * When getting, returns the encoding.
  206. */
  207. public function encoding($encoding = null) {
  208. $encodingMap = array('UTF-8' => 'utf8');
  209. if (empty($encoding)) {
  210. $encoding = mysql_client_encoding($this->connection);
  211. return ($key = array_search($encoding, $encodingMap)) ? $key : $encoding;
  212. }
  213. $encoding = isset($encodingMap[$encoding]) ? $encodingMap[$encoding] : $encoding;
  214. return mysql_set_charset($encoding, $this->connection);
  215. }
  216. /**
  217. * Converts a given value into the proper type based on a given schema definition.
  218. *
  219. * @see lithium\data\source\Database::schema()
  220. * @param mixed $value The value to be converted. Arrays will be recursively converted.
  221. * @param array $schema Formatted array from `lithium\data\source\Database::schema()`
  222. * @return mixed Value with converted type.
  223. */
  224. public function value($value, array $schema = array()) {
  225. if (($result = parent::value($value, $schema)) !== null) {
  226. return $result;
  227. }
  228. return "'" . mysql_real_escape_string((string) $value, $this->connection) . "'";
  229. }
  230. /**
  231. * In cases where the query is a raw string (as opposed to a `Query` object), to database must
  232. * determine the correct column names from the result resource.
  233. *
  234. * @param mixed $query
  235. * @param resource $resource
  236. * @param object $context
  237. * @return array
  238. */
  239. public function schema($query, $resource = null, $context = null) {
  240. if (is_object($query)) {
  241. return parent::schema($query, $resource, $context);
  242. }
  243. $result = array();
  244. $count = mysql_num_fields($resource);
  245. for ($i = 0; $i < $count; $i++) {
  246. $result[] = mysql_field_name($resource, $i);
  247. }
  248. return $result;
  249. }
  250. /**
  251. * Retrieves database error message and error code.
  252. *
  253. * @return array
  254. */
  255. public function error() {
  256. if (mysql_error($this->connection)) {
  257. return array(mysql_errno($this->connection), mysql_error($this->connection));
  258. }
  259. return null;
  260. }
  261. public function alias($alias, $context) {
  262. if ($context->type() == 'update' || $context->type() == 'delete') {
  263. return;
  264. }
  265. return parent::alias($alias, $context);
  266. }
  267. /**
  268. * @todo Eventually, this will need to rewrite aliases for DELETE and UPDATE queries, same with
  269. * order().
  270. * @param string $conditions
  271. * @param string $context
  272. * @param array $options
  273. * @return void
  274. */
  275. public function conditions($conditions, $context, array $options = array()) {
  276. return parent::conditions($conditions, $context, $options);
  277. }
  278. /**
  279. * Execute a given query.
  280. *
  281. * @see lithium\data\source\Database::renderCommand()
  282. * @param string $sql The sql string to execute
  283. * @param array $options Available options:
  284. * - 'buffered': If set to `false` uses mysql_unbuffered_query which
  285. * sends the SQL query query to MySQL without automatically fetching and buffering the
  286. * result rows as `mysql_query()` does (for less memory usage).
  287. * @return resource Returns the result resource handle if the query is successful.
  288. */
  289. protected function _execute($sql, array $options = array()) {
  290. $defaults = array('buffered' => true);
  291. $options += $defaults;
  292. return $this->_filter(__METHOD__, compact('sql', 'options'), function($self, $params) {
  293. $sql = $params['sql'];
  294. $options = $params['options'];
  295. $func = ($options['buffered']) ? 'mysql_query' : 'mysql_unbuffered_query';
  296. $resource = $func($sql, $self->connection);
  297. if ($resource === true) {
  298. return true;
  299. }
  300. if (is_resource($resource)) {
  301. return $self->invokeMethod('_instance', array('result', compact('resource')));
  302. }
  303. list($code, $error) = $self->error();
  304. throw new QueryException("{$sql}: {$error}", $code);
  305. });
  306. }
  307. protected function _results($results) {
  308. $numFields = mysql_num_fields($results);
  309. $index = $j = 0;
  310. while ($j < $numFields) {
  311. $column = mysql_fetch_field($results, $j);
  312. $name = $column->name;
  313. $table = $column->table;
  314. $this->map[$index++] = empty($table) ? array(0, $name) : array($table, $name);
  315. $j++;
  316. }
  317. }
  318. /**
  319. * Gets the last auto-generated ID from the query that inserted a new record.
  320. *
  321. * @param object $query The `Query` object associated with the query which generated
  322. * @return mixed Returns the last inserted ID key for an auto-increment column or a column
  323. * bound to a sequence.
  324. */
  325. protected function _insertId($query) {
  326. $resource = $this->_execute('SELECT LAST_INSERT_ID() AS insertID');
  327. list($id) = $resource->next();
  328. return ($id && $id !== '0') ? $id : null;
  329. }
  330. /**
  331. * Converts database-layer column types to basic types.
  332. *
  333. * @param string $real Real database-layer column type (i.e. `"varchar(255)"`)
  334. * @return array Column type (i.e. "string") plus 'length' when appropriate.
  335. */
  336. protected function _column($real) {
  337. if (is_array($real)) {
  338. return $real['type'] . (isset($real['length']) ? "({$real['length']})" : '');
  339. }
  340. if (!preg_match('/(?P<type>\w+)(?:\((?P<length>[\d,]+)\))?/', $real, $column)) {
  341. return $real;
  342. }
  343. $column = array_intersect_key($column, array('type' => null, 'length' => null));
  344. if (isset($column['length']) && $column['length']) {
  345. $length = explode(',', $column['length']) + array(null, null);
  346. $column['length'] = $length[0] ? intval($length[0]) : null;
  347. $length[1] ? $column['precision'] = intval($length[1]) : null;
  348. }
  349. switch (true) {
  350. case in_array($column['type'], array('date', 'time', 'datetime', 'timestamp')):
  351. return $column;
  352. case ($column['type'] == 'tinyint' && $column['length'] == '1'):
  353. case ($column['type'] == 'boolean'):
  354. return array('type' => 'boolean');
  355. break;
  356. case (strpos($column['type'], 'int') !== false):
  357. $column['type'] = 'integer';
  358. break;
  359. case (strpos($column['type'], 'char') !== false || $column['type'] == 'tinytext'):
  360. $column['type'] = 'string';
  361. break;
  362. case (strpos($column['type'], 'text') !== false):
  363. $column['type'] = 'text';
  364. break;
  365. case (strpos($column['type'], 'blob') !== false || $column['type'] == 'binary'):
  366. $column['type'] = 'binary';
  367. break;
  368. case preg_match('/float|double|decimal/', $column['type']):
  369. $column['type'] = 'float';
  370. break;
  371. default:
  372. $column['type'] = 'text';
  373. break;
  374. }
  375. return $column;
  376. }
  377. /**
  378. * Helper method that retrieves an entity's name via its metadata.
  379. *
  380. * @param string $entity Entity name.
  381. * @return string Name.
  382. */
  383. protected function _entityName($entity) {
  384. if (class_exists($entity, false) && method_exists($entity, 'meta')) {
  385. $entity = $entity::meta('name');
  386. }
  387. return $entity;
  388. }
  389. }
  390. ?>