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

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

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