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