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