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