PageRenderTime 46ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/libs/dibi/libs/DibiFluent.php

https://github.com/premiumcombination/nts
PHP | 502 lines | 243 code | 110 blank | 149 comment | 32 complexity | 6e4f471eb7d20041191cf5dd4651c869 MD5 | raw file
  1. <?php
  2. /**
  3. * This file is part of the "dibi" - smart database abstraction layer.
  4. *
  5. * Copyright (c) 2005, 2010 David Grudl (http://davidgrudl.com)
  6. *
  7. * For the full copyright and license information, please view
  8. * the file license.txt that was distributed with this source code.
  9. *
  10. * @package dibi
  11. */
  12. /**
  13. * dibi SQL builder via fluent interfaces. EXPERIMENTAL!
  14. *
  15. * @author David Grudl
  16. *
  17. * @property-read string $command
  18. * @property-read DibiConnection $connection
  19. * @property-read DibiResultIterator $iterator
  20. * @method DibiFluent select($field)
  21. * @method DibiFluent distinct()
  22. * @method DibiFluent from($table)
  23. * @method DibiFluent where($cond)
  24. * @method DibiFluent groupBy($field)
  25. * @method DibiFluent having($cond)
  26. * @method DibiFluent orderBy($field)
  27. * @method DibiFluent limit(int $limit)
  28. * @method DibiFluent offset(int $offset)
  29. */
  30. class DibiFluent extends DibiObject implements IDataSource
  31. {
  32. const REMOVE = FALSE;
  33. /** @var array */
  34. public static $masks = array(
  35. 'SELECT' => array('SELECT', 'DISTINCT', 'FROM', 'WHERE', 'GROUP BY',
  36. 'HAVING', 'ORDER BY', 'LIMIT', 'OFFSET'),
  37. 'UPDATE' => array('UPDATE', 'SET', 'WHERE', 'ORDER BY', 'LIMIT'),
  38. 'INSERT' => array('INSERT', 'INTO', 'VALUES', 'SELECT'),
  39. 'DELETE' => array('DELETE', 'FROM', 'USING', 'WHERE', 'ORDER BY', 'LIMIT'),
  40. );
  41. /** @var array default modifiers for arrays */
  42. public static $modifiers = array(
  43. 'SELECT' => '%n',
  44. 'FROM' => '%n',
  45. 'IN' => '%in',
  46. 'VALUES' => '%l',
  47. 'SET' => '%a',
  48. 'WHERE' => '%and',
  49. 'HAVING' => '%and',
  50. 'ORDER BY' => '%by',
  51. 'GROUP BY' => '%by',
  52. );
  53. /** @var array clauses separators */
  54. public static $separators = array(
  55. 'SELECT' => ',',
  56. 'FROM' => ',',
  57. 'WHERE' => 'AND',
  58. 'GROUP BY' => ',',
  59. 'HAVING' => 'AND',
  60. 'ORDER BY' => ',',
  61. 'LIMIT' => FALSE,
  62. 'OFFSET' => FALSE,
  63. 'SET' => ',',
  64. 'VALUES' => ',',
  65. 'INTO' => FALSE,
  66. );
  67. /** @var array clauses */
  68. public static $clauseSwitches = array(
  69. 'JOIN' => 'FROM',
  70. 'INNER JOIN' => 'FROM',
  71. 'LEFT JOIN' => 'FROM',
  72. 'RIGHT JOIN' => 'FROM',
  73. );
  74. /** @var DibiConnection */
  75. private $connection;
  76. /** @var string */
  77. private $command;
  78. /** @var array */
  79. private $clauses = array();
  80. /** @var array */
  81. private $flags = array();
  82. /** @var array */
  83. private $cursor;
  84. /** @var DibiHashMap normalized clauses */
  85. private static $normalizer;
  86. /**
  87. * @param DibiConnection
  88. */
  89. public function __construct(DibiConnection $connection)
  90. {
  91. $this->connection = $connection;
  92. if (self::$normalizer === NULL) {
  93. self::$normalizer = new DibiHashMap(array(__CLASS__, '_formatClause'));
  94. }
  95. }
  96. /**
  97. * Appends new argument to the clause.
  98. * @param string clause name
  99. * @param array arguments
  100. * @return DibiFluent provides a fluent interface
  101. */
  102. public function __call($clause, $args)
  103. {
  104. $clause = self::$normalizer->$clause;
  105. // lazy initialization
  106. if ($this->command === NULL) {
  107. if (isset(self::$masks[$clause])) {
  108. $this->clauses = array_fill_keys(self::$masks[$clause], NULL);
  109. }
  110. $this->cursor = & $this->clauses[$clause];
  111. $this->cursor = array();
  112. $this->command = $clause;
  113. }
  114. // auto-switch to a clause
  115. if (isset(self::$clauseSwitches[$clause])) {
  116. $this->cursor = & $this->clauses[self::$clauseSwitches[$clause]];
  117. }
  118. if (array_key_exists($clause, $this->clauses)) {
  119. // append to clause
  120. $this->cursor = & $this->clauses[$clause];
  121. // TODO: really delete?
  122. if ($args === array(self::REMOVE)) {
  123. $this->cursor = NULL;
  124. return $this;
  125. }
  126. if (isset(self::$separators[$clause])) {
  127. $sep = self::$separators[$clause];
  128. if ($sep === FALSE) { // means: replace
  129. $this->cursor = array();
  130. } elseif (!empty($this->cursor)) {
  131. $this->cursor[] = $sep;
  132. }
  133. }
  134. } else {
  135. // append to currect flow
  136. if ($args === array(self::REMOVE)) {
  137. return $this;
  138. }
  139. $this->cursor[] = $clause;
  140. }
  141. if ($this->cursor === NULL) {
  142. $this->cursor = array();
  143. }
  144. // special types or argument
  145. if (count($args) === 1) {
  146. $arg = $args[0];
  147. // TODO: really ignore TRUE?
  148. if ($arg === TRUE) { // flag
  149. return $this;
  150. } elseif (is_string($arg) && preg_match('#^[a-z:_][a-z0-9_.:]*$#i', $arg)) { // identifier
  151. $args = array('%n', $arg);
  152. } elseif ($arg instanceof self) {
  153. $args = array_merge(array('('), $arg->_export(), array(')'));
  154. } elseif (is_array($arg) || $arg instanceof Traversable) { // any array
  155. if (isset(self::$modifiers[$clause])) {
  156. $args = array(self::$modifiers[$clause], $arg);
  157. } elseif (is_string(key($arg))) { // associative array
  158. $args = array('%a', $arg);
  159. }
  160. } // case $arg === FALSE is handled above
  161. }
  162. foreach ($args as $arg) $this->cursor[] = $arg;
  163. return $this;
  164. }
  165. /**
  166. * Switch to a clause.
  167. * @param string clause name
  168. * @return DibiFluent provides a fluent interface
  169. */
  170. public function clause($clause, $remove = FALSE)
  171. {
  172. $this->cursor = & $this->clauses[self::$normalizer->$clause];
  173. if ($remove) { // deprecated, use removeClause
  174. trigger_error(__METHOD__ . '(..., TRUE) is deprecated; use removeClause() instead.', E_USER_NOTICE);
  175. $this->cursor = NULL;
  176. } elseif ($this->cursor === NULL) {
  177. $this->cursor = array();
  178. }
  179. return $this;
  180. }
  181. /**
  182. * Removes a clause.
  183. * @param string clause name
  184. * @return DibiFluent provides a fluent interface
  185. */
  186. public function removeClause($clause)
  187. {
  188. $this->clauses[self::$normalizer->$clause] = NULL;
  189. return $this;
  190. }
  191. /**
  192. * Change a SQL flag.
  193. * @param string flag name
  194. * @param bool value
  195. * @return DibiFluent provides a fluent interface
  196. */
  197. public function setFlag($flag, $value = TRUE)
  198. {
  199. $flag = strtoupper($flag);
  200. if ($value) {
  201. $this->flags[$flag] = TRUE;
  202. } else {
  203. unset($this->flags[$flag]);
  204. }
  205. return $this;
  206. }
  207. /**
  208. * Is a flag set?
  209. * @param string flag name
  210. * @return bool
  211. */
  212. final public function getFlag($flag)
  213. {
  214. return isset($this->flags[strtoupper($flag)]);
  215. }
  216. /**
  217. * Returns SQL command.
  218. * @return string
  219. */
  220. final public function getCommand()
  221. {
  222. return $this->command;
  223. }
  224. /**
  225. * Returns the dibi connection.
  226. * @return DibiConnection
  227. */
  228. final public function getConnection()
  229. {
  230. return $this->connection;
  231. }
  232. /********************* executing ****************d*g**/
  233. /**
  234. * Generates and executes SQL query.
  235. * @param mixed what to return?
  236. * @return DibiResult|int result set object (if any)
  237. * @throws DibiException
  238. */
  239. public function execute($return = NULL)
  240. {
  241. $res = $this->connection->query($this->_export());
  242. return $return === dibi::IDENTIFIER ? $this->connection->getInsertId() : $res;
  243. }
  244. /**
  245. * Generates, executes SQL query and fetches the single row.
  246. * @return DibiRow|FALSE array on success, FALSE if no next record
  247. */
  248. public function fetch()
  249. {
  250. if ($this->command === 'SELECT') {
  251. return $this->connection->query($this->_export(NULL, array('%lmt', 1)))->fetch();
  252. } else {
  253. return $this->connection->query($this->_export())->fetch();
  254. }
  255. }
  256. /**
  257. * Like fetch(), but returns only first field.
  258. * @return mixed value on success, FALSE if no next record
  259. */
  260. public function fetchSingle()
  261. {
  262. if ($this->command === 'SELECT') {
  263. return $this->connection->query($this->_export(NULL, array('%lmt', 1)))->fetchSingle();
  264. } else {
  265. return $this->connection->query($this->_export())->fetchSingle();
  266. }
  267. }
  268. /**
  269. * Fetches all records from table.
  270. * @param int offset
  271. * @param int limit
  272. * @return array
  273. */
  274. public function fetchAll($offset = NULL, $limit = NULL)
  275. {
  276. return $this->connection->query($this->_export(NULL, array('%ofs %lmt', $offset, $limit)))->fetchAll();
  277. }
  278. /**
  279. * Fetches all records from table and returns associative tree.
  280. * @param string associative descriptor
  281. * @return array
  282. */
  283. public function fetchAssoc($assoc)
  284. {
  285. return $this->connection->query($this->_export())->fetchAssoc($assoc);
  286. }
  287. /**
  288. * Fetches all records from table like $key => $value pairs.
  289. * @param string associative key
  290. * @param string value
  291. * @return array
  292. */
  293. public function fetchPairs($key = NULL, $value = NULL)
  294. {
  295. return $this->connection->query($this->_export())->fetchPairs($key, $value);
  296. }
  297. /**
  298. * Required by the IteratorAggregate interface.
  299. * @param int offset
  300. * @param int limit
  301. * @return DibiResultIterator
  302. */
  303. public function getIterator($offset = NULL, $limit = NULL)
  304. {
  305. return $this->connection->query($this->_export(NULL, array('%ofs %lmt', $offset, $limit)))->getIterator();
  306. }
  307. /**
  308. * Generates and prints SQL query or it's part.
  309. * @param string clause name
  310. * @return bool
  311. */
  312. public function test($clause = NULL)
  313. {
  314. return $this->connection->test($this->_export($clause));
  315. }
  316. /**
  317. * @return int
  318. */
  319. public function count()
  320. {
  321. return (int) $this->connection->query(
  322. 'SELECT COUNT(*) FROM (%ex', $this->_export(), ') AS [data]'
  323. )->fetchSingle();
  324. }
  325. /********************* exporting ****************d*g**/
  326. /**
  327. * @return DibiDataSource
  328. */
  329. public function toDataSource()
  330. {
  331. return new DibiDataSource($this->connection->translate($this->_export()), $this->connection);
  332. }
  333. /**
  334. * Returns SQL query.
  335. * @return string
  336. */
  337. final public function __toString()
  338. {
  339. return $this->connection->translate($this->_export());
  340. }
  341. /**
  342. * Generates parameters for DibiTranslator.
  343. * @param string clause name
  344. * @return array
  345. */
  346. protected function _export($clause = NULL, $args = array())
  347. {
  348. if ($clause === NULL) {
  349. $data = $this->clauses;
  350. } else {
  351. $clause = self::$normalizer->$clause;
  352. if (array_key_exists($clause, $this->clauses)) {
  353. $data = array($clause => $this->clauses[$clause]);
  354. } else {
  355. return array();
  356. }
  357. }
  358. foreach ($data as $clause => $statement) {
  359. if ($statement !== NULL) {
  360. $args[] = $clause;
  361. if ($clause === $this->command && $this->flags) {
  362. $args[] = implode(' ', array_keys($this->flags));
  363. }
  364. foreach ($statement as $arg) $args[] = $arg;
  365. }
  366. }
  367. return $args;
  368. }
  369. /**
  370. * Format camelCase clause name to UPPER CASE.
  371. * @param string
  372. * @return string
  373. * @internal
  374. */
  375. public static function _formatClause($s)
  376. {
  377. if ($s === 'order' || $s === 'group') {
  378. $s .= 'By';
  379. trigger_error("Did you mean '$s'?", E_USER_NOTICE);
  380. }
  381. return strtoupper(preg_replace('#[a-z](?=[A-Z])#', '$0 ', $s));
  382. }
  383. public function __clone()
  384. {
  385. // remove references
  386. foreach ($this->clauses as $clause => $val) {
  387. $this->clauses[$clause] = & $val;
  388. unset($val);
  389. }
  390. $this->cursor = & $foo;
  391. }
  392. }