CaseExpression.php 8.1 KB

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