QueryExpression.php 29 KB

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