CaseExpression.php 8.0 KB

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