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\Type\ExpressionTypeCasterTrait;
19: use Cake\Database\ValueBinder;
20:
21: /**
22: * This class represents a SQL Case statement
23: */
24: class CaseExpression implements ExpressionInterface
25: {
26: use ExpressionTypeCasterTrait;
27:
28: /**
29: * A list of strings or other expression objects that represent the conditions of
30: * the case statement. For example one key of the array might look like "sum > :value"
31: *
32: * @var array
33: */
34: protected $_conditions = [];
35:
36: /**
37: * Values that are associated with the conditions in the $_conditions array.
38: * Each value represents the 'true' value for the condition with the corresponding key.
39: *
40: * @var array
41: */
42: protected $_values = [];
43:
44: /**
45: * The `ELSE` value for the case statement. If null then no `ELSE` will be included.
46: *
47: * @var string|\Cake\Database\ExpressionInterface|array|null
48: */
49: protected $_elseValue;
50:
51: /**
52: * Constructs the case expression
53: *
54: * @param array|\Cake\Database\ExpressionInterface $conditions The conditions to test. Must be a ExpressionInterface
55: * instance, or an array of ExpressionInterface instances.
56: * @param array|\Cake\Database\ExpressionInterface $values associative array of values to be associated with the conditions
57: * passed in $conditions. If there are more $values than $conditions, the last $value is used as the `ELSE` value
58: * @param array $types associative array of types to be associated with the values
59: * passed in $values
60: */
61: public function __construct($conditions = [], $values = [], $types = [])
62: {
63: if (!empty($conditions)) {
64: $this->add($conditions, $values, $types);
65: }
66:
67: if (is_array($conditions) && is_array($values) && count($values) > count($conditions)) {
68: end($values);
69: $key = key($values);
70: $this->elseValue($values[$key], isset($types[$key]) ? $types[$key] : null);
71: }
72: }
73:
74: /**
75: * Adds one or more conditions and their respective true values to the case object.
76: * Conditions must be a one dimensional array or a QueryExpression.
77: * The trueValues must be a similar structure, but may contain a string value.
78: *
79: * @param array|\Cake\Database\ExpressionInterface $conditions Must be a ExpressionInterface instance, or an array of ExpressionInterface instances.
80: * @param array|\Cake\Database\ExpressionInterface $values associative array of values of each condition
81: * @param array $types associative array of types to be associated with the values
82: *
83: * @return $this
84: */
85: public function add($conditions = [], $values = [], $types = [])
86: {
87: if (!is_array($conditions)) {
88: $conditions = [$conditions];
89: }
90: if (!is_array($values)) {
91: $values = [$values];
92: }
93: if (!is_array($types)) {
94: $types = [$types];
95: }
96:
97: $this->_addExpressions($conditions, $values, $types);
98:
99: return $this;
100: }
101:
102: /**
103: * Iterates over the passed in conditions and ensures that there is a matching true value for each.
104: * If no matching true value, then it is defaulted to '1'.
105: *
106: * @param array|\Cake\Database\ExpressionInterface $conditions Must be a ExpressionInterface instance, or an array of ExpressionInterface instances.
107: * @param array|\Cake\Database\ExpressionInterface $values associative array of values of each condition
108: * @param array $types associative array of types to be associated with the values
109: *
110: * @return void
111: */
112: protected function _addExpressions($conditions, $values, $types)
113: {
114: $rawValues = array_values($values);
115: $keyValues = array_keys($values);
116:
117: foreach ($conditions as $k => $c) {
118: $numericKey = is_numeric($k);
119:
120: if ($numericKey && empty($c)) {
121: continue;
122: }
123:
124: if (!$c instanceof ExpressionInterface) {
125: continue;
126: }
127:
128: $this->_conditions[] = $c;
129: $value = isset($rawValues[$k]) ? $rawValues[$k] : 1;
130:
131: if ($value === 'literal') {
132: $value = $keyValues[$k];
133: $this->_values[] = $value;
134: continue;
135: }
136:
137: if ($value === 'identifier') {
138: $value = new IdentifierExpression($keyValues[$k]);
139: $this->_values[] = $value;
140: continue;
141: }
142:
143: $type = isset($types[$k]) ? $types[$k] : null;
144:
145: if ($type !== null && !$value instanceof ExpressionInterface) {
146: $value = $this->_castToExpression($value, $type);
147: }
148:
149: if ($value instanceof ExpressionInterface) {
150: $this->_values[] = $value;
151: continue;
152: }
153:
154: $this->_values[] = ['value' => $value, 'type' => $type];
155: }
156: }
157:
158: /**
159: * Sets the default value
160: *
161: * @param \Cake\Database\ExpressionInterface|string|array|null $value Value to set
162: * @param string|null $type Type of value
163: *
164: * @return void
165: */
166: public function elseValue($value = null, $type = null)
167: {
168: if (is_array($value)) {
169: end($value);
170: $value = key($value);
171: }
172:
173: if ($value !== null && !$value instanceof ExpressionInterface) {
174: $value = $this->_castToExpression($value, $type);
175: }
176:
177: if (!$value instanceof ExpressionInterface) {
178: $value = ['value' => $value, 'type' => $type];
179: }
180:
181: $this->_elseValue = $value;
182: }
183:
184: /**
185: * Compiles the relevant parts into sql
186: *
187: * @param array|string|\Cake\Database\ExpressionInterface $part The part to compile
188: * @param \Cake\Database\ValueBinder $generator Sql generator
189: *
190: * @return string
191: */
192: protected function _compile($part, ValueBinder $generator)
193: {
194: if ($part instanceof ExpressionInterface) {
195: $part = $part->sql($generator);
196: } elseif (is_array($part)) {
197: $placeholder = $generator->placeholder('param');
198: $generator->bind($placeholder, $part['value'], $part['type']);
199: $part = $placeholder;
200: }
201:
202: return $part;
203: }
204:
205: /**
206: * Converts the Node into a SQL string fragment.
207: *
208: * @param \Cake\Database\ValueBinder $generator Placeholder generator object
209: *
210: * @return string
211: */
212: public function sql(ValueBinder $generator)
213: {
214: $parts = [];
215: $parts[] = 'CASE';
216: foreach ($this->_conditions as $k => $part) {
217: $value = $this->_values[$k];
218: $parts[] = 'WHEN ' . $this->_compile($part, $generator) . ' THEN ' . $this->_compile($value, $generator);
219: }
220: if ($this->_elseValue !== null) {
221: $parts[] = 'ELSE';
222: $parts[] = $this->_compile($this->_elseValue, $generator);
223: }
224: $parts[] = 'END';
225:
226: return implode(' ', $parts);
227: }
228:
229: /**
230: * {@inheritDoc}
231: *
232: */
233: public function traverse(callable $visitor)
234: {
235: foreach (['_conditions', '_values'] as $part) {
236: foreach ($this->{$part} as $c) {
237: if ($c instanceof ExpressionInterface) {
238: $visitor($c);
239: $c->traverse($visitor);
240: }
241: }
242: }
243: if ($this->_elseValue instanceof ExpressionInterface) {
244: $visitor($this->_elseValue);
245: $this->_elseValue->traverse($visitor);
246: }
247: }
248: }
249: