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