QueryExpression.php 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  11. * @link https://cakephp.org CakePHP(tm) Project
  12. * @since 3.0.0
  13. * @license https://opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Database\Expression;
  16. use BadMethodCallException;
  17. use Cake\Database\ExpressionInterface;
  18. use Cake\Database\Query;
  19. use Cake\Database\TypeMapTrait;
  20. use Cake\Database\ValueBinder;
  21. use Countable;
  22. /**
  23. * Represents a SQL Query expression. Internally it stores a tree of
  24. * expressions that can be compiled by converting this object to string
  25. * and will contain a correctly parenthesized and nested expression.
  26. *
  27. * @method $this and(callable|string|array|\Cake\Database\ExpressionInterface $conditions)
  28. * @method $this or(callable|string|array|\Cake\Database\ExpressionInterface $conditions)
  29. */
  30. class QueryExpression implements ExpressionInterface, Countable
  31. {
  32. use TypeMapTrait;
  33. /**
  34. * String to be used for joining each of the internal expressions
  35. * this object internally stores for example "AND", "OR", etc.
  36. *
  37. * @var string
  38. */
  39. protected $_conjunction;
  40. /**
  41. * A list of strings or other expression objects that represent the "branches" of
  42. * the expression tree. For example one key of the array might look like "sum > :value"
  43. *
  44. * @var array
  45. */
  46. protected $_conditions = [];
  47. /**
  48. * Constructor. A new expression object can be created without any params and
  49. * be built dynamically. Otherwise it is possible to pass an array of conditions
  50. * containing either a tree-like array structure to be parsed and/or other
  51. * expression objects. Optionally, you can set the conjunction keyword to be used
  52. * for joining each part of this level of the expression tree.
  53. *
  54. * @param string|array|\Cake\Database\ExpressionInterface $conditions tree-like array structure containing all the conditions
  55. * to be added or nested inside this expression object.
  56. * @param array|\Cake\Database\TypeMap $types associative array of types to be associated with the values
  57. * passed in $conditions.
  58. * @param string $conjunction the glue that will join all the string conditions at this
  59. * level of the expression tree. For example "AND", "OR", "XOR"...
  60. * @see \Cake\Database\Expression\QueryExpression::add() for more details on $conditions and $types
  61. */
  62. public function __construct($conditions = [], $types = [], $conjunction = 'AND')
  63. {
  64. $this->setTypeMap($types);
  65. $this->setConjunction(strtoupper($conjunction));
  66. if (!empty($conditions)) {
  67. $this->add($conditions, $this->getTypeMap()->getTypes());
  68. }
  69. }
  70. /**
  71. * Changes the conjunction for the conditions at this level of the expression tree.
  72. *
  73. * @param string $conjunction Value to be used for joining conditions
  74. * @return $this
  75. */
  76. public function setConjunction($conjunction)
  77. {
  78. $this->_conjunction = strtoupper($conjunction);
  79. return $this;
  80. }
  81. /**
  82. * Gets the currently configured conjunction for the conditions at this level of the expression tree.
  83. *
  84. * @return string
  85. */
  86. public function getConjunction()
  87. {
  88. return $this->_conjunction;
  89. }
  90. /**
  91. * Changes the conjunction for the conditions at this level of the expression tree.
  92. * If called with no arguments it will return the currently configured value.
  93. *
  94. * @deprecated 3.4.0 Use setConjunction()/getConjunction() instead.
  95. * @param string|null $conjunction value to be used for joining conditions. If null it
  96. * will not set any value, but return the currently stored one
  97. * @return string|$this
  98. */
  99. public function tieWith($conjunction = null)
  100. {
  101. deprecationWarning(
  102. 'QueryExpression::tieWith() is deprecated. ' .
  103. 'Use QueryExpression::setConjunction()/getConjunction() instead.'
  104. );
  105. if ($conjunction !== null) {
  106. return $this->setConjunction($conjunction);
  107. }
  108. return $this->getConjunction();
  109. }
  110. /**
  111. * Backwards compatible wrapper for tieWith()
  112. *
  113. * @param string|null $conjunction value to be used for joining conditions. If null it
  114. * will not set any value, but return the currently stored one
  115. * @return string|$this
  116. * @deprecated 3.2.0 Use setConjunction()/getConjunction() instead
  117. */
  118. public function type($conjunction = null)
  119. {
  120. deprecationWarning(
  121. 'QueryExpression::type() is deprecated. ' .
  122. 'Use QueryExpression::setConjunction()/getConjunction() instead.'
  123. );
  124. return $this->tieWith($conjunction);
  125. }
  126. /**
  127. * Adds one or more conditions to this expression object. Conditions can be
  128. * expressed in a one dimensional array, that will cause all conditions to
  129. * be added directly at this level of the tree or they can be nested arbitrarily
  130. * making it create more expression objects that will be nested inside and
  131. * configured to use the specified conjunction.
  132. *
  133. * If the type passed for any of the fields is expressed "type[]" (note braces)
  134. * then it will cause the placeholder to be re-written dynamically so if the
  135. * value is an array, it will create as many placeholders as values are in it.
  136. *
  137. * @param string|array|\Cake\Database\ExpressionInterface $conditions single or multiple conditions to
  138. * be added. When using an array and the key is 'OR' or 'AND' a new expression
  139. * object will be created with that conjunction and internal array value passed
  140. * as conditions.
  141. * @param array $types associative array of fields pointing to the type of the
  142. * values that are being passed. Used for correctly binding values to statements.
  143. * @see \Cake\Database\Query::where() for examples on conditions
  144. * @return $this
  145. */
  146. public function add($conditions, $types = [])
  147. {
  148. if (is_string($conditions)) {
  149. $this->_conditions[] = $conditions;
  150. return $this;
  151. }
  152. if ($conditions instanceof ExpressionInterface) {
  153. $this->_conditions[] = $conditions;
  154. return $this;
  155. }
  156. $this->_addConditions($conditions, $types);
  157. return $this;
  158. }
  159. /**
  160. * Adds a new condition to the expression object in the form "field = value".
  161. *
  162. * @param string|\Cake\Database\ExpressionInterface $field Database field to be compared against value
  163. * @param mixed $value The value to be bound to $field for comparison
  164. * @param string|null $type the type name for $value as configured using the Type map.
  165. * If it is suffixed with "[]" and the value is an array then multiple placeholders
  166. * will be created, one per each value in the array.
  167. * @return $this
  168. */
  169. public function eq($field, $value, $type = null)
  170. {
  171. if ($type === null) {
  172. $type = $this->_calculateType($field);
  173. }
  174. return $this->add(new Comparison($field, $value, $type, '='));
  175. }
  176. /**
  177. * Adds a new condition to the expression object in the form "field != value".
  178. *
  179. * @param string|\Cake\Database\ExpressionInterface $field Database field to be compared against value
  180. * @param mixed $value The value to be bound to $field for comparison
  181. * @param string|null $type the type name for $value as configured using the Type map.
  182. * If it is suffixed with "[]" and the value is an array then multiple placeholders
  183. * will be created, one per each value in the array.
  184. * @return $this
  185. */
  186. public function notEq($field, $value, $type = null)
  187. {
  188. if ($type === null) {
  189. $type = $this->_calculateType($field);
  190. }
  191. return $this->add(new Comparison($field, $value, $type, '!='));
  192. }
  193. /**
  194. * Adds a new condition to the expression object in the form "field > value".
  195. *
  196. * @param string|\Cake\Database\ExpressionInterface $field Database field to be compared against value
  197. * @param mixed $value The value to be bound to $field for comparison
  198. * @param string|null $type the type name for $value as configured using the Type map.
  199. * @return $this
  200. */
  201. public function gt($field, $value, $type = null)
  202. {
  203. if ($type === null) {
  204. $type = $this->_calculateType($field);
  205. }
  206. return $this->add(new Comparison($field, $value, $type, '>'));
  207. }
  208. /**
  209. * Adds a new condition to the expression object in the form "field < value".
  210. *
  211. * @param string|\Cake\Database\ExpressionInterface $field Database field to be compared against value
  212. * @param mixed $value The value to be bound to $field for comparison
  213. * @param string|null $type the type name for $value as configured using the Type map.
  214. * @return $this
  215. */
  216. public function lt($field, $value, $type = null)
  217. {
  218. if ($type === null) {
  219. $type = $this->_calculateType($field);
  220. }
  221. return $this->add(new Comparison($field, $value, $type, '<'));
  222. }
  223. /**
  224. * Adds a new condition to the expression object in the form "field >= value".
  225. *
  226. * @param string|\Cake\Database\ExpressionInterface $field Database field to be compared against value
  227. * @param mixed $value The value to be bound to $field for comparison
  228. * @param string|null $type the type name for $value as configured using the Type map.
  229. * @return $this
  230. */
  231. public function gte($field, $value, $type = null)
  232. {
  233. if ($type === null) {
  234. $type = $this->_calculateType($field);
  235. }
  236. return $this->add(new Comparison($field, $value, $type, '>='));
  237. }
  238. /**
  239. * Adds a new condition to the expression object in the form "field <= value".
  240. *
  241. * @param string|\Cake\Database\ExpressionInterface $field Database field to be compared against value
  242. * @param mixed $value The value to be bound to $field for comparison
  243. * @param string|null $type the type name for $value as configured using the Type map.
  244. * @return $this
  245. */
  246. public function lte($field, $value, $type = null)
  247. {
  248. if ($type === null) {
  249. $type = $this->_calculateType($field);
  250. }
  251. return $this->add(new Comparison($field, $value, $type, '<='));
  252. }
  253. /**
  254. * Adds a new condition to the expression object in the form "field IS NULL".
  255. *
  256. * @param string|\Cake\Database\ExpressionInterface $field database field to be
  257. * tested for null
  258. * @return $this
  259. */
  260. public function isNull($field)
  261. {
  262. if (!($field instanceof ExpressionInterface)) {
  263. $field = new IdentifierExpression($field);
  264. }
  265. return $this->add(new UnaryExpression('IS NULL', $field, UnaryExpression::POSTFIX));
  266. }
  267. /**
  268. * Adds a new condition to the expression object in the form "field IS NOT NULL".
  269. *
  270. * @param string|\Cake\Database\ExpressionInterface $field database field to be
  271. * tested for not null
  272. * @return $this
  273. */
  274. public function isNotNull($field)
  275. {
  276. if (!($field instanceof ExpressionInterface)) {
  277. $field = new IdentifierExpression($field);
  278. }
  279. return $this->add(new UnaryExpression('IS NOT NULL', $field, UnaryExpression::POSTFIX));
  280. }
  281. /**
  282. * Adds a new condition to the expression object in the form "field LIKE value".
  283. *
  284. * @param string|\Cake\Database\ExpressionInterface $field Database field to be compared against value
  285. * @param mixed $value The value to be bound to $field for comparison
  286. * @param string|null $type the type name for $value as configured using the Type map.
  287. * @return $this
  288. */
  289. public function like($field, $value, $type = null)
  290. {
  291. if ($type === null) {
  292. $type = $this->_calculateType($field);
  293. }
  294. return $this->add(new Comparison($field, $value, $type, 'LIKE'));
  295. }
  296. /**
  297. * Adds a new condition to the expression object in the form "field NOT LIKE value".
  298. *
  299. * @param string|\Cake\Database\ExpressionInterface $field Database field to be compared against value
  300. * @param mixed $value The value to be bound to $field for comparison
  301. * @param string|null $type the type name for $value as configured using the Type map.
  302. * @return $this
  303. */
  304. public function notLike($field, $value, $type = null)
  305. {
  306. if ($type === null) {
  307. $type = $this->_calculateType($field);
  308. }
  309. return $this->add(new Comparison($field, $value, $type, 'NOT LIKE'));
  310. }
  311. /**
  312. * Adds a new condition to the expression object in the form
  313. * "field IN (value1, value2)".
  314. *
  315. * @param string|\Cake\Database\ExpressionInterface $field Database field to be compared against value
  316. * @param string|array $values the value to be bound to $field for comparison
  317. * @param string|null $type the type name for $value as configured using the Type map.
  318. * @return $this
  319. */
  320. public function in($field, $values, $type = null)
  321. {
  322. if ($type === null) {
  323. $type = $this->_calculateType($field);
  324. }
  325. $type = $type ?: 'string';
  326. $type .= '[]';
  327. $values = $values instanceof ExpressionInterface ? $values : (array)$values;
  328. return $this->add(new Comparison($field, $values, $type, 'IN'));
  329. }
  330. /**
  331. * Adds a new case expression to the expression object
  332. *
  333. * @param array|\Cake\Database\ExpressionInterface $conditions The conditions to test. Must be a ExpressionInterface
  334. * instance, or an array of ExpressionInterface instances.
  335. * @param array|\Cake\Database\ExpressionInterface $values associative array of values to be associated with the conditions
  336. * passed in $conditions. If there are more $values than $conditions, the last $value is used as the `ELSE` value
  337. * @param array $types associative array of types to be associated with the values
  338. * passed in $values
  339. * @return $this
  340. */
  341. public function addCase($conditions, $values = [], $types = [])
  342. {
  343. return $this->add(new CaseExpression($conditions, $values, $types));
  344. }
  345. /**
  346. * Adds a new condition to the expression object in the form
  347. * "field NOT IN (value1, value2)".
  348. *
  349. * @param string|\Cake\Database\ExpressionInterface $field Database field to be compared against value
  350. * @param array $values the value to be bound to $field for comparison
  351. * @param string|null $type the type name for $value as configured using the Type map.
  352. * @return $this
  353. */
  354. public function notIn($field, $values, $type = null)
  355. {
  356. if ($type === null) {
  357. $type = $this->_calculateType($field);
  358. }
  359. $type = $type ?: 'string';
  360. $type .= '[]';
  361. $values = $values instanceof ExpressionInterface ? $values : (array)$values;
  362. return $this->add(new Comparison($field, $values, $type, 'NOT IN'));
  363. }
  364. /**
  365. * Adds a new condition to the expression object in the form "EXISTS (...)".
  366. *
  367. * @param \Cake\Database\ExpressionInterface $query the inner query
  368. * @return $this
  369. */
  370. public function exists(ExpressionInterface $query)
  371. {
  372. return $this->add(new UnaryExpression('EXISTS', $query, UnaryExpression::PREFIX));
  373. }
  374. /**
  375. * Adds a new condition to the expression object in the form "NOT EXISTS (...)".
  376. *
  377. * @param \Cake\Database\ExpressionInterface $query the inner query
  378. * @return $this
  379. */
  380. public function notExists(ExpressionInterface $query)
  381. {
  382. return $this->add(new UnaryExpression('NOT EXISTS', $query, UnaryExpression::PREFIX));
  383. }
  384. /**
  385. * Adds a new condition to the expression object in the form
  386. * "field BETWEEN from AND to".
  387. *
  388. * @param string|\Cake\Database\ExpressionInterface $field The field name to compare for values in between the range.
  389. * @param mixed $from The initial value of the range.
  390. * @param mixed $to The ending value in the comparison range.
  391. * @param string|null $type the type name for $value as configured using the Type map.
  392. * @return $this
  393. */
  394. public function between($field, $from, $to, $type = null)
  395. {
  396. if ($type === null) {
  397. $type = $this->_calculateType($field);
  398. }
  399. return $this->add(new BetweenExpression($field, $from, $to, $type));
  400. }
  401. // @codingStandardsIgnoreStart
  402. /**
  403. * Returns a new QueryExpression object containing all the conditions passed
  404. * and set up the conjunction to be "AND"
  405. *
  406. * @param callable|string|array|\Cake\Database\ExpressionInterface $conditions to be joined with AND
  407. * @param array $types associative array of fields pointing to the type of the
  408. * values that are being passed. Used for correctly binding values to statements.
  409. * @return \Cake\Database\Expression\QueryExpression
  410. */
  411. public function and_($conditions, $types = [])
  412. {
  413. if ($this->isCallable($conditions)) {
  414. return $conditions(new static([], $this->getTypeMap()->setTypes($types)));
  415. }
  416. return new static($conditions, $this->getTypeMap()->setTypes($types));
  417. }
  418. /**
  419. * Returns a new QueryExpression object containing all the conditions passed
  420. * and set up the conjunction to be "OR"
  421. *
  422. * @param callable|string|array|\Cake\Database\ExpressionInterface $conditions to be joined with OR
  423. * @param array $types associative array of fields pointing to the type of the
  424. * values that are being passed. Used for correctly binding values to statements.
  425. * @return \Cake\Database\Expression\QueryExpression
  426. */
  427. public function or_($conditions, $types = [])
  428. {
  429. if ($this->isCallable($conditions)) {
  430. return $conditions(new static([], $this->getTypeMap()->setTypes($types), 'OR'));
  431. }
  432. return new static($conditions, $this->getTypeMap()->setTypes($types), 'OR');
  433. }
  434. // @codingStandardsIgnoreEnd
  435. /**
  436. * Adds a new set of conditions to this level of the tree and negates
  437. * the final result by prepending a NOT, it will look like
  438. * "NOT ( (condition1) AND (conditions2) )" conjunction depends on the one
  439. * currently configured for this object.
  440. *
  441. * @param string|array|\Cake\Database\ExpressionInterface $conditions to be added and negated
  442. * @param array $types associative array of fields pointing to the type of the
  443. * values that are being passed. Used for correctly binding values to statements.
  444. * @return $this
  445. */
  446. public function not($conditions, $types = [])
  447. {
  448. return $this->add(['NOT' => $conditions], $types);
  449. }
  450. /**
  451. * Returns the number of internal conditions that are stored in this expression.
  452. * Useful to determine if this expression object is void or it will generate
  453. * a non-empty string when compiled
  454. *
  455. * @return int
  456. */
  457. public function count()
  458. {
  459. return count($this->_conditions);
  460. }
  461. /**
  462. * Builds equal condition or assignment with identifier wrapping.
  463. *
  464. * @param string $left Left join condition field name.
  465. * @param string $right Right join condition field name.
  466. * @return $this
  467. */
  468. public function equalFields($left, $right)
  469. {
  470. $wrapIdentifier = function ($field) {
  471. if ($field instanceof ExpressionInterface) {
  472. return $field;
  473. }
  474. return new IdentifierExpression($field);
  475. };
  476. return $this->eq($wrapIdentifier($left), $wrapIdentifier($right));
  477. }
  478. /**
  479. * Returns the string representation of this object so that it can be used in a
  480. * SQL query. Note that values condition values are not included in the string,
  481. * in their place placeholders are put and can be replaced by the quoted values
  482. * accordingly.
  483. *
  484. * @param \Cake\Database\ValueBinder $generator Placeholder generator object
  485. * @return string
  486. */
  487. public function sql(ValueBinder $generator)
  488. {
  489. $len = $this->count();
  490. if ($len === 0) {
  491. return '';
  492. }
  493. $conjunction = $this->_conjunction;
  494. $template = ($len === 1) ? '%s' : '(%s)';
  495. $parts = [];
  496. foreach ($this->_conditions as $part) {
  497. if ($part instanceof Query) {
  498. $part = '(' . $part->sql($generator) . ')';
  499. } elseif ($part instanceof ExpressionInterface) {
  500. $part = $part->sql($generator);
  501. }
  502. if (strlen($part)) {
  503. $parts[] = $part;
  504. }
  505. }
  506. return sprintf($template, implode(" $conjunction ", $parts));
  507. }
  508. /**
  509. * Traverses the tree structure of this query expression by executing a callback
  510. * function for each of the conditions that are included in this object.
  511. * Useful for compiling the final expression, or doing
  512. * introspection in the structure.
  513. *
  514. * Callback function receives as only argument an instance of ExpressionInterface
  515. *
  516. * @param callable $callable The callable to apply to all sub-expressions.
  517. * @return void
  518. */
  519. public function traverse(callable $callable)
  520. {
  521. foreach ($this->_conditions as $c) {
  522. if ($c instanceof ExpressionInterface) {
  523. $callable($c);
  524. $c->traverse($callable);
  525. }
  526. }
  527. }
  528. /**
  529. * Executes a callable function for each of the parts that form this expression.
  530. *
  531. * The callable function is required to return a value with which the currently
  532. * visited part will be replaced. If the callable function returns null then
  533. * the part will be discarded completely from this expression.
  534. *
  535. * The callback function will receive each of the conditions as first param and
  536. * the key as second param. It is possible to declare the second parameter as
  537. * passed by reference, this will enable you to change the key under which the
  538. * modified part is stored.
  539. *
  540. * @param callable $callable The callable to apply to each part.
  541. * @return $this
  542. */
  543. public function iterateParts(callable $callable)
  544. {
  545. $parts = [];
  546. foreach ($this->_conditions as $k => $c) {
  547. $key =& $k;
  548. $part = $callable($c, $key);
  549. if ($part !== null) {
  550. $parts[$key] = $part;
  551. }
  552. }
  553. $this->_conditions = $parts;
  554. return $this;
  555. }
  556. /**
  557. * Helps calling the `and()` and `or()` methods transparently.
  558. *
  559. * @param string $method The method name.
  560. * @param array $args The arguments to pass to the method.
  561. * @return \Cake\Database\Expression\QueryExpression
  562. * @throws \BadMethodCallException
  563. */
  564. public function __call($method, $args)
  565. {
  566. if (in_array($method, ['and', 'or'])) {
  567. return call_user_func_array([$this, $method . '_'], $args);
  568. }
  569. throw new BadMethodCallException(sprintf('Method %s does not exist', $method));
  570. }
  571. /**
  572. * Check whether or not a callable is acceptable.
  573. *
  574. * We don't accept ['class', 'method'] style callbacks,
  575. * as they often contain user input and arrays of strings
  576. * are easy to sneak in.
  577. *
  578. * @param callable $c The callable to check.
  579. * @return bool Valid callable.
  580. */
  581. public function isCallable($c)
  582. {
  583. if (is_string($c)) {
  584. return false;
  585. }
  586. if (is_object($c) && is_callable($c)) {
  587. return true;
  588. }
  589. return is_array($c) && isset($c[0]) && is_object($c[0]) && is_callable($c);
  590. }
  591. /**
  592. * Returns true if this expression contains any other nested
  593. * ExpressionInterface objects
  594. *
  595. * @return bool
  596. */
  597. public function hasNestedExpression()
  598. {
  599. foreach ($this->_conditions as $c) {
  600. if ($c instanceof ExpressionInterface) {
  601. return true;
  602. }
  603. }
  604. return false;
  605. }
  606. /**
  607. * Auxiliary function used for decomposing a nested array of conditions and build
  608. * a tree structure inside this object to represent the full SQL expression.
  609. * String conditions are stored directly in the conditions, while any other
  610. * representation is wrapped around an adequate instance or of this class.
  611. *
  612. * @param array $conditions list of conditions to be stored in this object
  613. * @param array $types list of types associated on fields referenced in $conditions
  614. * @return void
  615. */
  616. protected function _addConditions(array $conditions, array $types)
  617. {
  618. $operators = ['and', 'or', 'xor'];
  619. $typeMap = $this->getTypeMap()->setTypes($types);
  620. foreach ($conditions as $k => $c) {
  621. $numericKey = is_numeric($k);
  622. if ($this->isCallable($c)) {
  623. $expr = new static([], $typeMap);
  624. $c = $c($expr, $this);
  625. }
  626. if ($numericKey && empty($c)) {
  627. continue;
  628. }
  629. $isArray = is_array($c);
  630. $isOperator = in_array(strtolower($k), $operators);
  631. $isNot = strtolower($k) === 'not';
  632. if (($isOperator || $isNot) && ($isArray || $c instanceof Countable) && count($c) === 0) {
  633. continue;
  634. }
  635. if ($numericKey && $c instanceof ExpressionInterface) {
  636. $this->_conditions[] = $c;
  637. continue;
  638. }
  639. if ($numericKey && is_string($c)) {
  640. $this->_conditions[] = $c;
  641. continue;
  642. }
  643. if ($numericKey && $isArray || $isOperator) {
  644. $this->_conditions[] = new static($c, $typeMap, $numericKey ? 'AND' : $k);
  645. continue;
  646. }
  647. if ($isNot) {
  648. $this->_conditions[] = new UnaryExpression('NOT', new static($c, $typeMap));
  649. continue;
  650. }
  651. if (!$numericKey) {
  652. $this->_conditions[] = $this->_parseCondition($k, $c);
  653. }
  654. }
  655. }
  656. /**
  657. * Parses a string conditions by trying to extract the operator inside it if any
  658. * and finally returning either an adequate QueryExpression object or a plain
  659. * string representation of the condition. This function is responsible for
  660. * generating the placeholders and replacing the values by them, while storing
  661. * the value elsewhere for future binding.
  662. *
  663. * @param string $field The value from with the actual field and operator will
  664. * be extracted.
  665. * @param mixed $value The value to be bound to a placeholder for the field
  666. * @return string|\Cake\Database\ExpressionInterface
  667. */
  668. protected function _parseCondition($field, $value)
  669. {
  670. $operator = '=';
  671. $expression = $field;
  672. $parts = explode(' ', trim($field), 2);
  673. if (count($parts) > 1) {
  674. list($expression, $operator) = $parts;
  675. }
  676. $type = $this->getTypeMap()->type($expression);
  677. $operator = strtolower(trim($operator));
  678. $typeMultiple = strpos($type, '[]') !== false;
  679. if (in_array($operator, ['in', 'not in']) || $typeMultiple) {
  680. $type = $type ?: 'string';
  681. $type .= $typeMultiple ? null : '[]';
  682. $operator = $operator === '=' ? 'IN' : $operator;
  683. $operator = $operator === '!=' ? 'NOT IN' : $operator;
  684. $typeMultiple = true;
  685. }
  686. if ($typeMultiple) {
  687. $value = $value instanceof ExpressionInterface ? $value : (array)$value;
  688. }
  689. if ($operator === 'is' && $value === null) {
  690. return new UnaryExpression(
  691. 'IS NULL',
  692. new IdentifierExpression($expression),
  693. UnaryExpression::POSTFIX
  694. );
  695. }
  696. if ($operator === 'is not' && $value === null) {
  697. return new UnaryExpression(
  698. 'IS NOT NULL',
  699. new IdentifierExpression($expression),
  700. UnaryExpression::POSTFIX
  701. );
  702. }
  703. if ($operator === 'is' && $value !== null) {
  704. $operator = '=';
  705. }
  706. if ($operator === 'is not' && $value !== null) {
  707. $operator = '!=';
  708. }
  709. return new Comparison($expression, $value, $type, $operator);
  710. }
  711. /**
  712. * Returns the type name for the passed field if it was stored in the typeMap
  713. *
  714. * @param string|\Cake\Database\Expression\IdentifierExpression $field The field name to get a type for.
  715. * @return string|null The computed type or null, if the type is unknown.
  716. */
  717. protected function _calculateType($field)
  718. {
  719. $field = $field instanceof IdentifierExpression ? $field->getIdentifier() : $field;
  720. if (is_string($field)) {
  721. return $this->getTypeMap()->type($field);
  722. }
  723. return null;
  724. }
  725. /**
  726. * Clone this object and its subtree of expressions.
  727. *
  728. * @return void
  729. */
  730. public function __clone()
  731. {
  732. foreach ($this->_conditions as $i => $condition) {
  733. if ($condition instanceof ExpressionInterface) {
  734. $this->_conditions[$i] = clone $condition;
  735. }
  736. }
  737. }
  738. }