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 Cake\Database\Exception as DatabaseException;
18: use Cake\Database\ExpressionInterface;
19: use Cake\Database\Type\ExpressionTypeCasterTrait;
20: use Cake\Database\ValueBinder;
21:
22: /**
23: * A Comparison is a type of query expression that represents an operation
24: * involving a field an operator and a value. In its most common form the
25: * string representation of a comparison is `field = value`
26: */
27: class Comparison implements ExpressionInterface, FieldInterface
28: {
29: use ExpressionTypeCasterTrait;
30: use FieldTrait;
31:
32: /**
33: * The value to be used in the right hand side of the operation
34: *
35: * @var mixed
36: */
37: protected $_value;
38:
39: /**
40: * The type to be used for casting the value to a database representation
41: *
42: * @var string|array
43: */
44: protected $_type;
45:
46: /**
47: * The operator used for comparing field and value
48: *
49: * @var string
50: */
51: protected $_operator;
52:
53: /**
54: * Whether or not the value in this expression is a traversable
55: *
56: * @var bool
57: */
58: protected $_isMultiple = false;
59:
60: /**
61: * A cached list of ExpressionInterface objects that were
62: * found in the value for this expression.
63: *
64: * @var \Cake\Database\ExpressionInterface[]
65: */
66: protected $_valueExpressions = [];
67:
68: /**
69: * Constructor
70: *
71: * @param string|\Cake\Database\ExpressionInterface $field the field name to compare to a value
72: * @param mixed $value The value to be used in comparison
73: * @param string $type the type name used to cast the value
74: * @param string $operator the operator used for comparing field and value
75: */
76: public function __construct($field, $value, $type, $operator)
77: {
78: if (is_string($type)) {
79: $this->_type = $type;
80: }
81:
82: $this->setField($field);
83: $this->setValue($value);
84: $this->_operator = $operator;
85: }
86:
87: /**
88: * Sets the value
89: *
90: * @param mixed $value The value to compare
91: * @return void
92: */
93: public function setValue($value)
94: {
95: $hasType = isset($this->_type) && is_string($this->_type);
96: $isMultiple = $hasType && strpos($this->_type, '[]') !== false;
97:
98: if ($hasType) {
99: $value = $this->_castToExpression($value, $this->_type);
100: }
101:
102: if ($isMultiple) {
103: list($value, $this->_valueExpressions) = $this->_collectExpressions($value);
104: }
105:
106: $this->_isMultiple = $isMultiple;
107: $this->_value = $value;
108: }
109:
110: /**
111: * Returns the value used for comparison
112: *
113: * @return mixed
114: */
115: public function getValue()
116: {
117: return $this->_value;
118: }
119:
120: /**
121: * Sets the operator to use for the comparison
122: *
123: * @param string $operator The operator to be used for the comparison.
124: * @return void
125: */
126: public function setOperator($operator)
127: {
128: $this->_operator = $operator;
129: }
130:
131: /**
132: * Returns the operator used for comparison
133: *
134: * @return string
135: */
136: public function getOperator()
137: {
138: return $this->_operator;
139: }
140:
141: /**
142: * Convert the expression into a SQL fragment.
143: *
144: * @param \Cake\Database\ValueBinder $generator Placeholder generator object
145: * @return string
146: */
147: public function sql(ValueBinder $generator)
148: {
149: $field = $this->_field;
150:
151: if ($field instanceof ExpressionInterface) {
152: $field = $field->sql($generator);
153: }
154:
155: if ($this->_value instanceof ExpressionInterface) {
156: $template = '%s %s (%s)';
157: $value = $this->_value->sql($generator);
158: } else {
159: list($template, $value) = $this->_stringExpression($generator);
160: }
161:
162: return sprintf($template, $field, $this->_operator, $value);
163: }
164:
165: /**
166: * {@inheritDoc}
167: *
168: */
169: public function traverse(callable $callable)
170: {
171: if ($this->_field instanceof ExpressionInterface) {
172: $callable($this->_field);
173: $this->_field->traverse($callable);
174: }
175:
176: if ($this->_value instanceof ExpressionInterface) {
177: $callable($this->_value);
178: $this->_value->traverse($callable);
179: }
180:
181: foreach ($this->_valueExpressions as $v) {
182: $callable($v);
183: $v->traverse($callable);
184: }
185: }
186:
187: /**
188: * Create a deep clone.
189: *
190: * Clones the field and value if they are expression objects.
191: *
192: * @return void
193: */
194: public function __clone()
195: {
196: foreach (['_value', '_field'] as $prop) {
197: if ($this->{$prop} instanceof ExpressionInterface) {
198: $this->{$prop} = clone $this->{$prop};
199: }
200: }
201: }
202:
203: /**
204: * Returns a template and a placeholder for the value after registering it
205: * with the placeholder $generator
206: *
207: * @param \Cake\Database\ValueBinder $generator The value binder to use.
208: * @return array First position containing the template and the second a placeholder
209: */
210: protected function _stringExpression($generator)
211: {
212: $template = '%s ';
213:
214: if ($this->_field instanceof ExpressionInterface) {
215: $template = '(%s) ';
216: }
217:
218: if ($this->_isMultiple) {
219: $template .= '%s (%s)';
220: $type = str_replace('[]', '', $this->_type);
221: $value = $this->_flattenValue($this->_value, $generator, $type);
222:
223: // To avoid SQL errors when comparing a field to a list of empty values,
224: // better just throw an exception here
225: if ($value === '') {
226: $field = $this->_field instanceof ExpressionInterface ? $this->_field->sql($generator) : $this->_field;
227: throw new DatabaseException(
228: "Impossible to generate condition with empty list of values for field ($field)"
229: );
230: }
231: } else {
232: $template .= '%s %s';
233: $value = $this->_bindValue($this->_value, $generator, $this->_type);
234: }
235:
236: return [$template, $value];
237: }
238:
239: /**
240: * Registers a value in the placeholder generator and returns the generated placeholder
241: *
242: * @param mixed $value The value to bind
243: * @param \Cake\Database\ValueBinder $generator The value binder to use
244: * @param string $type The type of $value
245: * @return string generated placeholder
246: */
247: protected function _bindValue($value, $generator, $type)
248: {
249: $placeholder = $generator->placeholder('c');
250: $generator->bind($placeholder, $value, $type);
251:
252: return $placeholder;
253: }
254:
255: /**
256: * Converts a traversable value into a set of placeholders generated by
257: * $generator and separated by `,`
258: *
259: * @param array|\Traversable $value the value to flatten
260: * @param \Cake\Database\ValueBinder $generator The value binder to use
261: * @param string|array|null $type the type to cast values to
262: * @return string
263: */
264: protected function _flattenValue($value, $generator, $type = 'string')
265: {
266: $parts = [];
267: foreach ($this->_valueExpressions as $k => $v) {
268: $parts[$k] = $v->sql($generator);
269: unset($value[$k]);
270: }
271:
272: if (!empty($value)) {
273: $parts += $generator->generateManyNamed($value, $type);
274: }
275:
276: return implode(',', $parts);
277: }
278:
279: /**
280: * Returns an array with the original $values in the first position
281: * and all ExpressionInterface objects that could be found in the second
282: * position.
283: *
284: * @param array|\Traversable $values The rows to insert
285: * @return array
286: */
287: protected function _collectExpressions($values)
288: {
289: if ($values instanceof ExpressionInterface) {
290: return [$values, []];
291: }
292:
293: $expressions = $result = [];
294: $isArray = is_array($values);
295:
296: if ($isArray) {
297: $result = $values;
298: }
299:
300: foreach ($values as $k => $v) {
301: if ($v instanceof ExpressionInterface) {
302: $expressions[$k] = $v;
303: }
304:
305: if ($isArray) {
306: $result[$k] = $v;
307: }
308: }
309:
310: return [$result, $expressions];
311: }
312: }
313: