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\ExpressionInterface;
18: use Cake\Database\ValueBinder;
19:
20: /**
21: * This expression represents SQL fragments that are used for comparing one tuple
22: * to another, one tuple to a set of other tuples or one tuple to an expression
23: */
24: class TupleComparison extends Comparison
25: {
26: /**
27: * Constructor
28: *
29: * @param string|array|\Cake\Database\ExpressionInterface $fields the fields to use to form a tuple
30: * @param array|\Cake\Database\ExpressionInterface $values the values to use to form a tuple
31: * @param array $types the types names to use for casting each of the values, only
32: * one type per position in the value array in needed
33: * @param string $conjunction the operator used for comparing field and value
34: */
35: public function __construct($fields, $values, $types = [], $conjunction = '=')
36: {
37: parent::__construct($fields, $values, $types, $conjunction);
38: $this->_type = (array)$types;
39: }
40:
41: /**
42: * Convert the expression into a SQL fragment.
43: *
44: * @param \Cake\Database\ValueBinder $generator Placeholder generator object
45: * @return string
46: */
47: public function sql(ValueBinder $generator)
48: {
49: $template = '(%s) %s (%s)';
50: $fields = [];
51: $originalFields = $this->getField();
52:
53: if (!is_array($originalFields)) {
54: $originalFields = [$originalFields];
55: }
56:
57: foreach ($originalFields as $field) {
58: $fields[] = $field instanceof ExpressionInterface ? $field->sql($generator) : $field;
59: }
60:
61: $values = $this->_stringifyValues($generator);
62:
63: $field = implode(', ', $fields);
64:
65: return sprintf($template, $field, $this->_operator, $values);
66: }
67:
68: /**
69: * Returns a string with the values as placeholders in a string to be used
70: * for the SQL version of this expression
71: *
72: * @param \Cake\Database\ValueBinder $generator The value binder to convert expressions with.
73: * @return string
74: */
75: protected function _stringifyValues($generator)
76: {
77: $values = [];
78: $parts = $this->getValue();
79:
80: if ($parts instanceof ExpressionInterface) {
81: return $parts->sql($generator);
82: }
83:
84: foreach ($parts as $i => $value) {
85: if ($value instanceof ExpressionInterface) {
86: $values[] = $value->sql($generator);
87: continue;
88: }
89:
90: $type = $this->_type;
91: $multiType = is_array($type);
92: $isMulti = $this->isMulti();
93: $type = $multiType ? $type : str_replace('[]', '', $type);
94: $type = $type ?: null;
95:
96: if ($isMulti) {
97: $bound = [];
98: foreach ($value as $k => $val) {
99: $valType = $multiType ? $type[$k] : $type;
100: $bound[] = $this->_bindValue($generator, $val, $valType);
101: }
102:
103: $values[] = sprintf('(%s)', implode(',', $bound));
104: continue;
105: }
106:
107: $valType = $multiType && isset($type[$i]) ? $type[$i] : $type;
108: $values[] = $this->_bindValue($generator, $value, $valType);
109: }
110:
111: return implode(', ', $values);
112: }
113:
114: /**
115: * Registers a value in the placeholder generator and returns the generated
116: * placeholder
117: *
118: * @param \Cake\Database\ValueBinder $generator The value binder
119: * @param mixed $value The value to bind
120: * @param string $type The type to use
121: * @return string generated placeholder
122: */
123: protected function _bindValue($generator, $value, $type)
124: {
125: $placeholder = $generator->placeholder('tuple');
126: $generator->bind($placeholder, $value, $type);
127:
128: return $placeholder;
129: }
130:
131: /**
132: * Traverses the tree of expressions stored in this object, visiting first
133: * expressions in the left hand side and then the rest.
134: *
135: * Callback function receives as its only argument an instance of an ExpressionInterface
136: *
137: * @param callable $callable The callable to apply to sub-expressions
138: * @return void
139: */
140: public function traverse(callable $callable)
141: {
142: foreach ($this->getField() as $field) {
143: $this->_traverseValue($field, $callable);
144: }
145:
146: $value = $this->getValue();
147: if ($value instanceof ExpressionInterface) {
148: $callable($value);
149: $value->traverse($callable);
150:
151: return;
152: }
153:
154: foreach ($value as $i => $val) {
155: if ($this->isMulti()) {
156: foreach ($val as $v) {
157: $this->_traverseValue($v, $callable);
158: }
159: } else {
160: $this->_traverseValue($val, $callable);
161: }
162: }
163: }
164:
165: /**
166: * Conditionally executes the callback for the passed value if
167: * it is an ExpressionInterface
168: *
169: * @param mixed $value The value to traverse
170: * @param callable $callable The callable to use when traversing
171: * @return void
172: */
173: protected function _traverseValue($value, $callable)
174: {
175: if ($value instanceof ExpressionInterface) {
176: $callable($value);
177: $value->traverse($callable);
178: }
179: }
180:
181: /**
182: * Determines if each of the values in this expressions is a tuple in
183: * itself
184: *
185: * @return bool
186: */
187: public function isMulti()
188: {
189: return in_array(strtolower($this->_operator), ['in', 'not in']);
190: }
191: }
192: