PageRenderTime 50ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/library/Atomik/Db/Query.php

http://atomikframework.googlecode.com/
PHP | 680 lines | 342 code | 77 blank | 261 comment | 36 complexity | 228acbbe740922616ede4043084d9c95 MD5 | raw file
Possible License(s): LGPL-2.1, MIT, CC-BY-3.0
  1. <?php
  2. /**
  3. * Atomik Framework
  4. * Copyright (c) 2008-2009 Maxime Bouroumeau-Fuseau
  5. *
  6. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  7. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  8. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  9. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  10. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  11. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  12. * THE SOFTWARE.
  13. *
  14. * @package Atomik
  15. * @subpackage Db
  16. * @author Maxime Bouroumeau-Fuseau
  17. * @copyright 2008-2009 (c) Maxime Bouroumeau-Fuseau
  18. * @license http://www.opensource.org/licenses/mit-license.php
  19. * @link http://www.atomikframework.com
  20. */
  21. /** Atomik_Db_Query_Generator_Interface */
  22. require_once 'Atomik/Db/Query/Generator/Interface.php';
  23. /** Atomik_Db_Query_Generator */
  24. require_once 'Atomik/Db/Query/Generator.php';
  25. /** Atomik_Db_Query_Expr */
  26. require_once 'Atomik/Db/Query/Expr.php';
  27. /**
  28. * Used to generate and execute SQL queries
  29. *
  30. * @package Atomik
  31. * @subpackage Db
  32. */
  33. class Atomik_Db_Query extends Atomik_Db_Query_Expr
  34. {
  35. const _AND = ' AND ';
  36. const _OR = ' OR ';
  37. /** @var Atomik_Db_Instance */
  38. protected $_instance;
  39. /** @var PDO */
  40. protected $_pdo;
  41. /** @var Atomik_Db_Query_Generator_Interface */
  42. protected $_generator;
  43. /** @var string */
  44. protected $_rawSql;
  45. /** @var array */
  46. protected $_info;
  47. /** @var int */
  48. protected $_fetchMode = PDO::FETCH_ASSOC;
  49. /**
  50. * Shortcut to create a new Atomik_Db_Query_Expr object
  51. *
  52. * @see Atomik_Db_Query_Expr
  53. * @param string $value
  54. * @return Atomik_Db_Query_Expr
  55. */
  56. public static function expr($value)
  57. {
  58. return new Atomik_Db_Query_Expr($value);
  59. }
  60. /**
  61. * Creates a new instance
  62. *
  63. * @return Atomik_Db_Query
  64. */
  65. public static function create(Atomik_Db_Instance $instance = null)
  66. {
  67. if ($instance === null) {
  68. $instance = Atomik_Db::getInstance();
  69. }
  70. return new self($instance);
  71. }
  72. /**
  73. * Constructor
  74. */
  75. public function __construct(Atomik_Db_Instance $instance)
  76. {
  77. $this->reset();
  78. $this->_instance = $instance;
  79. $this->_generator = $instance->getAdapter()->getQueryGenerator();
  80. }
  81. /**
  82. * Resets the query
  83. * @return Atomik_Db_Query
  84. */
  85. public function reset()
  86. {
  87. $this->_statement = null;
  88. $this->_info = array(
  89. 'fields' => array(),
  90. 'from' => array(),
  91. 'table' => null,
  92. 'join' => array(),
  93. 'where' => array(),
  94. 'groupBy' => array(),
  95. 'having' => array(),
  96. 'orderBy' => array(),
  97. 'limit' => null,
  98. 'data' => array(),
  99. 'params' => array()
  100. );
  101. return $this;
  102. }
  103. /**
  104. * Returns the associated instance
  105. *
  106. * @return Atomik_Db_Instance
  107. */
  108. public function getInstance()
  109. {
  110. return $this->_instance;
  111. }
  112. /**
  113. * Returns the sql generator
  114. *
  115. * @return Atomik_Db_Query_Generator_Interface
  116. */
  117. public function getGenerator()
  118. {
  119. return $this->_generator;
  120. }
  121. /**
  122. * Sets the PDOStatement fetch mode
  123. *
  124. * @param int $mode
  125. */
  126. public function setFetchMode($mode)
  127. {
  128. $this->_fetchMode = $mode;
  129. }
  130. /**
  131. * Returns the fetch mode of the PDOStatement
  132. *
  133. * @return int
  134. */
  135. public function getFetchMode()
  136. {
  137. return $this->_fetchMode;
  138. }
  139. /**
  140. * Uses a raw sql query
  141. *
  142. * @param string $sqlString
  143. * @return Atomik_Db_Query
  144. */
  145. public function sql($sqlString)
  146. {
  147. $this->_rawSql = $sqlString;
  148. return $this;
  149. }
  150. /**
  151. * Creates a SELECT statement
  152. *
  153. * Fields to select can be specified as an array or as arguments of the method
  154. *
  155. * @param string|array $fields
  156. * @param args ...
  157. * @return Atomik_Db_Query
  158. */
  159. public function select($fields = null)
  160. {
  161. if (!is_array($fields)) {
  162. $fields = func_get_args();
  163. if (count($fields) == 0 || $fields[0] === null) {
  164. $fields = array('*');
  165. }
  166. }
  167. $this->_rawSql = null;
  168. $this->_info['fields'] = array_merge($this->_info['fields'], $fields);
  169. return $this;
  170. }
  171. public function clearSelect()
  172. {
  173. $this->_info['fields'] = array();
  174. return $this;
  175. }
  176. /**
  177. * Creates a SELECT COUNT() statement
  178. *
  179. * @param string $field
  180. * @return Atomik_Db_Query
  181. */
  182. public function count($field = '*')
  183. {
  184. $this->_rawSql = null;
  185. $this->_info['fields'] = array(sprintf('COUNT(%s)', $field));
  186. return $this;
  187. }
  188. /**
  189. * Specifies the FROM part of a query
  190. *
  191. * Tables can be specified as an array with table name as keys and their alias as values
  192. * or only the table name as value.
  193. * Otherwise, the table name and its alias (optional) can be specified as argument of the
  194. * method. from() can be called more than one time.
  195. *
  196. * @param string|array $table
  197. * @param string $alias
  198. * @return Atomik_Db_Query
  199. */
  200. public function from($table, $alias = null)
  201. {
  202. if (is_array($table)) {
  203. foreach ($table as $key => $value) {
  204. if (is_int($key)) {
  205. $this->from($value);
  206. } else {
  207. $this->from($key, $value);
  208. }
  209. }
  210. return $this;
  211. }
  212. $this->_info['from'][] = array(
  213. 'table' => $this->_formatTableName($table),
  214. 'alias' => $alias
  215. );
  216. return $this;
  217. }
  218. public function clearFrom()
  219. {
  220. $this->_info['from'] = array();
  221. return $this;
  222. }
  223. /**
  224. * Specifies the JOIN part of a SELECT statement
  225. *
  226. * @TODO implement join()
  227. * @return Atomik_Db_Query
  228. */
  229. public function join($table, $on, $alias = null, $type = 'INNER')
  230. {
  231. $this->_info['join'][] = array(
  232. 'table' => $this->_formatTableName($table),
  233. 'on' => $on,
  234. 'alias' => $alias,
  235. 'type' => strtoupper($type)
  236. );
  237. return $this;
  238. }
  239. public function clearJoin()
  240. {
  241. $this->_info['join'] = array();
  242. return $this;
  243. }
  244. /**
  245. * Specifies the WHERE part
  246. *
  247. * Possible arguments:
  248. * - where(string): a raw sql string
  249. * - where(string, string...): the first string is the sql string, following args are parameters (see PDO and params in prepare())
  250. * - where(string, array): the first arg is the sql string, the second arg is an array of parameters
  251. * - where(array): an array where keys are field name and their value the value that it should be equal to
  252. *
  253. * It can be called multiple time. Each condition will be concatenate using AND
  254. *
  255. * @return Atomik_Db_Query
  256. */
  257. public function where()
  258. {
  259. $args = func_get_args();
  260. $this->_info['where'][] = $this->_computeCondition($args);
  261. return $this;
  262. }
  263. /**
  264. * Same as where() but will be concatenante using OR
  265. *
  266. * @see Atomik_Db_Query::where()
  267. * @return Atomik_Db_Query
  268. */
  269. public function orWhere()
  270. {
  271. $args = func_get_args();
  272. $this->_info['where'][] = $this->_computeCondition($args, self::_OR);
  273. return $this;
  274. }
  275. public function clearWhere()
  276. {
  277. $this->_info['where'] = array();
  278. return $this;
  279. }
  280. /**
  281. * Specifies the GROUP BY part
  282. *
  283. * Fields can be specified as an array of as arguments of the method
  284. *
  285. * @param string|array $fields
  286. * @param args ...
  287. * @return Atomik_Db_Query
  288. */
  289. public function groupBy($fields = null)
  290. {
  291. if (!is_array($fields)) {
  292. $fields = func_get_args();
  293. }
  294. $this->_info['groupBy'] = array_merge($this->_info['groupBy'], $fields);
  295. return $this;
  296. }
  297. public function clearGroupBy()
  298. {
  299. $this->_info['groupBy'] = array();
  300. return $this;
  301. }
  302. /**
  303. * Specifies the HAVING part of GROUP BY
  304. *
  305. * Works the same as where()
  306. *
  307. * @see Atomik_Db_Query::where()
  308. * @return Atomik_Db_Query
  309. */
  310. public function having()
  311. {
  312. $args = func_get_args();
  313. $this->_info['having'][] = $this->_computeCondition($args);
  314. return $this;
  315. }
  316. /**
  317. * Same as having() but with OR
  318. *
  319. * @see Atomik_Db_Query::having()
  320. * @see Atomik_Db_Query::orWhere()
  321. * @return Atomik_Db_Query
  322. */
  323. public function orHaving()
  324. {
  325. $args = func_get_args();
  326. $this->_info['having'][] = $this->_computeCondition($args, self::_OR);
  327. return $this;
  328. }
  329. public function clearHaving()
  330. {
  331. $this->_info['having'] = array();
  332. return $this;
  333. }
  334. /**
  335. * Specifies the ORDER BY part
  336. *
  337. * Fields can be specified as an array following this structure:
  338. * array(fieldName, fieldName2 => direction)
  339. * Otherwise it can be specified as argument of the method.
  340. *
  341. * @param string|array $field If the value contain ASC, DESC or a comma, it will be considered as a custom order by statement
  342. * @param string $direction
  343. * @return Atomik_Db_Query
  344. */
  345. public function orderBy($field, $direction = null)
  346. {
  347. if (is_array($field)) {
  348. foreach ($field as $key => $value) {
  349. if (is_int($key)) {
  350. $this->orderBy($value);
  351. } else {
  352. $this->orderBy($key, $value);
  353. }
  354. }
  355. return $this;
  356. } else if (is_string($field)) {
  357. if (strpos($field, ',') !== false || preg_match('/(.+)\s+(ASC|DESC)/', $field, $matches)) {
  358. // use custom sql string
  359. $this->_info['orderBy'] = $field;
  360. return $this;
  361. }
  362. }
  363. $this->_info['orderBy'][$field] = $direction;
  364. return $this;
  365. }
  366. public function clearOrderBy()
  367. {
  368. $this->_info['orderBy'] = array();
  369. return $this;
  370. }
  371. /**
  372. * Specifies the LIMIT part
  373. *
  374. * Arguments can be:
  375. * - limit(sqlString)
  376. * - limit(length)
  377. * - limit(offset, length)
  378. * - limit(array(length))
  379. * - limit(array(offset, length))
  380. *
  381. * @return Atomik_Db_Query
  382. */
  383. public function limit($limit)
  384. {
  385. if ($limit === false) {
  386. $this->_info['limit'] = null;
  387. return $this;
  388. }
  389. if (is_string($limit)) {
  390. $args = explode(',', $limit);
  391. } else if (is_array($limit)) {
  392. $args = $limit;
  393. } else {
  394. $args = func_get_args();
  395. }
  396. $offset = 0;
  397. $length = $args[0];
  398. if (count($args) == 2) {
  399. $offset = $args[0];
  400. $length = $args[1];
  401. }
  402. $this->_info['limit'] = array(
  403. 'length' => (int) $length,
  404. 'offset' => (int) $offset
  405. );
  406. return $this;
  407. }
  408. /**
  409. * Resets params
  410. *
  411. * @param array $params
  412. * @return Atomik_Db_Query
  413. */
  414. public function setParams($params)
  415. {
  416. $this->_info['params'] = (array) $params;
  417. return $this;
  418. }
  419. /**
  420. * Sets a param value
  421. *
  422. * @param string|int $index
  423. * @param string $value
  424. * @return Atomik_Db_Query
  425. */
  426. public function setParam($index, $value)
  427. {
  428. $this->_info['params'][$index] = $value;
  429. return $this;
  430. }
  431. /**
  432. * Returns the parameters associated to the query
  433. *
  434. * @return array
  435. */
  436. public function getParams()
  437. {
  438. return $this->_info['params'];
  439. }
  440. /**
  441. * Returns query information
  442. *
  443. * @param string $key To retreive only one type of information
  444. * @return array
  445. */
  446. public function getInfo($key = null)
  447. {
  448. if ($key === null) {
  449. return $this->_info;
  450. }
  451. return $this->_info[$key];
  452. }
  453. /**
  454. * Returns the query's info as an array
  455. *
  456. * @return array
  457. */
  458. public function toArray()
  459. {
  460. return $this->_info;
  461. }
  462. /**
  463. * Returns a unique hash representing this query
  464. *
  465. * @return string
  466. */
  467. public function toHash()
  468. {
  469. return md5($this->toSql() . implode(',', $this->getParams()));
  470. }
  471. /**
  472. * Returns the query as an SQL string
  473. *
  474. * @return string
  475. */
  476. public function toSql()
  477. {
  478. if ($this->_rawSql !== null) {
  479. return $this->_rawSql;
  480. }
  481. return $this->getGenerator()->generate($this);
  482. }
  483. /**
  484. * Returns the conditions (WHERE part) as an SQL string
  485. *
  486. * @return string
  487. */
  488. public function getConditionString()
  489. {
  490. return $this->_concatConditions($this->_info['where']);
  491. }
  492. /**
  493. * Executes the request against a db instance
  494. *
  495. * @return PDOStatement
  496. */
  497. public function execute()
  498. {
  499. if (($stmt = $this->_instance->query($this->toSql(), $this->getParams())) === false) {
  500. return false;
  501. }
  502. $stmt->setFetchMode($this->_fetchMode);
  503. return $stmt;
  504. }
  505. /**
  506. * PHP magic method. Returns the query as an SQL string.
  507. *
  508. * @see Atomik_Db_Query::toSql()
  509. * @return string
  510. */
  511. public function __toString()
  512. {
  513. return $this->toSql();
  514. }
  515. /**
  516. * Returns the prefixed table name
  517. *
  518. * @param string $tableName
  519. * @return string
  520. */
  521. protected function _formatTableName($tableName)
  522. {
  523. return $this->_instance->getTablePrefix() . $tableName;
  524. }
  525. /**
  526. * Returns information about a condition. Also extract params.
  527. *
  528. * @param array $args
  529. * @param string $operator
  530. * @return array
  531. */
  532. protected function _computeCondition($args, $operator = self::_AND)
  533. {
  534. $sql = array_shift($args);
  535. $params = count($args) && is_array($args[0]) ? $args[0] : $args;
  536. list($sql, $params) = $this->_buildConditionString($sql, $params);
  537. $this->_info['params'] = array_merge($this->_info['params'], $params);
  538. return array(
  539. 'sql' => $sql,
  540. 'operator' => $operator
  541. );
  542. }
  543. /**
  544. * Concatenates a condition array
  545. *
  546. * @param array $array
  547. * @return string
  548. */
  549. protected function _concatConditions($array)
  550. {
  551. $sql = '';
  552. for ($i = 0, $c = count($array); $i < $c; $i++) {
  553. if ($i > 0) {
  554. $sql .= $array[$i]['operator'];
  555. }
  556. $sql .= $array[$i]['sql'];
  557. }
  558. return trim($sql);
  559. }
  560. /**
  561. * Builds an SQL condition string
  562. *
  563. * @param string|array $fields A raw sql string with params if needed or an array of the form fieldName => value
  564. * @param array $params Parameters (see PDO and prepare())
  565. * @param string $operator The operator used to implode conditions
  566. * @return array array(fields, params)
  567. */
  568. protected function _buildConditionString($fields, $params = array(), $operator = self::_AND)
  569. {
  570. if (is_string($fields)) {
  571. $sql = $fields;
  572. $finalParams = array();
  573. foreach ($params as $name => $value) {
  574. if ($value instanceof Atomik_Db_Query_Expr) {
  575. $subject = is_int($name) ? '?' : $name;
  576. $sql = substr_replace($sql, $value->__toString(), strpos($sql, $subject), strlen($subject));
  577. } else {
  578. $finalParams[$name] = $value;
  579. }
  580. }
  581. return array($sql, $finalParams);
  582. }
  583. $sql = '';
  584. $conditions = array();
  585. foreach ($fields as $field => $value) {
  586. if (is_numeric($field)) {
  587. $conditions[] = (string) $value;
  588. continue;
  589. }
  590. if ($value instanceof Atomik_Db_Query_Expr) {
  591. $value = $value->__toString();
  592. } else {
  593. $params[] = $value;
  594. $value = '?';
  595. }
  596. $conditions[] = "$field = $value";
  597. }
  598. if (count($conditions)) {
  599. $sql = implode($operator, $conditions);
  600. }
  601. return array($sql, $params);
  602. }
  603. }