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