CaseStatementExpression.php 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
  5. * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  6. *
  7. * Licensed under The MIT License
  8. * For full copyright and license information, please see the LICENSE.txt
  9. * Redistributions of files must retain the above copyright notice.
  10. *
  11. * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  12. * @link https://cakephp.org CakePHP(tm) Project
  13. * @since 4.3.0
  14. * @license https://opensource.org/licenses/mit-license.php MIT License
  15. */
  16. namespace Cake\Database\Expression;
  17. use Cake\Database\ExpressionInterface;
  18. use Cake\Database\Type\ExpressionTypeCasterTrait;
  19. use Cake\Database\TypeMap;
  20. use Cake\Database\TypeMapTrait;
  21. use Cake\Database\ValueBinder;
  22. use Closure;
  23. use InvalidArgumentException;
  24. use LogicException;
  25. class CaseStatementExpression implements CaseExpressionInterface
  26. {
  27. use CaseExpressionTrait;
  28. use ExpressionTypeCasterTrait;
  29. use TypeMapTrait;
  30. /**
  31. * The names of the clauses that are valid for use with the
  32. * `clause()` method.
  33. *
  34. * @var array<string>
  35. */
  36. protected $validClauseNames = [
  37. 'value',
  38. 'when',
  39. 'else',
  40. ];
  41. /**
  42. * Whether this is a simple case expression.
  43. *
  44. * @var bool
  45. */
  46. protected $isSimpleVariant = false;
  47. /**
  48. * The case value.
  49. *
  50. * @var \Cake\Database\ExpressionInterface|object|scalar|null
  51. */
  52. protected $value = null;
  53. /**
  54. * The case value type.
  55. *
  56. * @var string|null
  57. */
  58. protected $valueType = null;
  59. /**
  60. * The `WHEN ... THEN ...` expressions.
  61. *
  62. * @var array<\Cake\Database\Expression\WhenThenExpressionInterface>
  63. */
  64. protected $when = [];
  65. /**
  66. * Buffer that holds values and types for use with `then()`.
  67. *
  68. * @var array|null
  69. */
  70. protected $whenBuffer = null;
  71. /**
  72. * The else part result value.
  73. *
  74. * @var \Cake\Database\ExpressionInterface|object|scalar|null
  75. */
  76. protected $else = null;
  77. /**
  78. * The else part result type.
  79. *
  80. * @var string|null
  81. */
  82. protected $elseType = null;
  83. /**
  84. * The return type.
  85. *
  86. * @var string|null
  87. */
  88. protected $returnType = null;
  89. /**
  90. * Constructor.
  91. *
  92. * @param \Cake\Database\TypeMap|null $typeMap The type map to use when using an array of conditions for the `WHEN`
  93. * value.
  94. */
  95. public function __construct(?TypeMap $typeMap = null)
  96. {
  97. if ($typeMap === null) {
  98. $typeMap = new TypeMap();
  99. }
  100. $this->_typeMap = $typeMap;
  101. }
  102. /**
  103. * @inheritDoc
  104. */
  105. public function value($value, ?string $valueType = null)
  106. {
  107. if (
  108. $value !== null &&
  109. !is_scalar($value) &&
  110. !(is_object($value) && !($value instanceof Closure))
  111. ) {
  112. throw new InvalidArgumentException(sprintf(
  113. 'The `$value` argument must be either `null`, a scalar value, an object, ' .
  114. 'or an instance of `\%s`, `%s` given.',
  115. ExpressionInterface::class,
  116. getTypeName($value)
  117. ));
  118. }
  119. $this->value = $value;
  120. if (
  121. $value !== null &&
  122. $valueType === null
  123. ) {
  124. $valueType = $this->inferType($value);
  125. }
  126. $this->valueType = $valueType;
  127. $this->isSimpleVariant = true;
  128. return $this;
  129. }
  130. /**
  131. * @inheritDoc
  132. */
  133. public function when($when, $type = null)
  134. {
  135. if ($this->whenBuffer !== null) {
  136. throw new LogicException(
  137. 'Cannot add new `WHEN` value while an open `when()` buffer is present, ' .
  138. 'it must first be closed using `then()`.'
  139. );
  140. }
  141. if ($when instanceof Closure) {
  142. $when = $when(new WhenThenExpression($this->getTypeMap()));
  143. if (!($when instanceof WhenThenExpressionInterface)) {
  144. throw new LogicException(sprintf(
  145. '`when()` callables must return an instance of `\%s`, `%s` given.',
  146. WhenThenExpressionInterface::class,
  147. getTypeName($when)
  148. ));
  149. }
  150. }
  151. if ($when instanceof WhenThenExpressionInterface) {
  152. $this->when[] = $when;
  153. } else {
  154. $this->whenBuffer = ['when' => $when, 'type' => $type];
  155. }
  156. return $this;
  157. }
  158. /**
  159. * @inheritDoc
  160. */
  161. public function then($result, ?string $type = null)
  162. {
  163. if ($this->whenBuffer === null) {
  164. throw new LogicException(
  165. 'There is no `when()` buffer present, ' .
  166. 'you must first open one before calling `then()`.'
  167. );
  168. }
  169. $whenThen = (new WhenThenExpression($this->getTypeMap()))
  170. ->when($this->whenBuffer['when'], $this->whenBuffer['type'])
  171. ->then($result, $type);
  172. $this->whenBuffer = null;
  173. $this->when[] = $whenThen;
  174. return $this;
  175. }
  176. /**
  177. * @inheritDoc
  178. */
  179. public function else($result, ?string $type = null)
  180. {
  181. if ($this->whenBuffer !== null) {
  182. throw new LogicException(
  183. 'Cannot set `ELSE` value when an open `when()` buffer is present, ' .
  184. 'it must first be closed using `then()`.'
  185. );
  186. }
  187. if (
  188. $result !== null &&
  189. !is_scalar($result) &&
  190. !(is_object($result) && !($result instanceof Closure))
  191. ) {
  192. throw new InvalidArgumentException(sprintf(
  193. 'The `$result` argument must be either `null`, a scalar value, an object, ' .
  194. 'or an instance of `\%s`, `%s` given.',
  195. ExpressionInterface::class,
  196. getTypeName($result)
  197. ));
  198. }
  199. if ($type === null) {
  200. $type = $this->inferType($result);
  201. }
  202. $this->whenBuffer = null;
  203. $this->else = $result;
  204. $this->elseType = $type;
  205. return $this;
  206. }
  207. /**
  208. * @inheritDoc
  209. */
  210. public function getReturnType(): string
  211. {
  212. if ($this->returnType !== null) {
  213. return $this->returnType;
  214. }
  215. $types = [];
  216. foreach ($this->when as $when) {
  217. $type = $when->getResultType();
  218. if ($type !== null) {
  219. $types[] = $type;
  220. }
  221. }
  222. if ($this->elseType !== null) {
  223. $types[] = $this->elseType;
  224. }
  225. $types = array_unique($types);
  226. if (count($types) === 1) {
  227. return $types[0];
  228. }
  229. return 'string';
  230. }
  231. /**
  232. * @inheritDoc
  233. */
  234. public function setReturnType(string $type)
  235. {
  236. $this->returnType = $type;
  237. return $this;
  238. }
  239. /**
  240. * @inheritDoc
  241. */
  242. public function sql(ValueBinder $binder): string
  243. {
  244. if ($this->whenBuffer !== null) {
  245. throw new LogicException(
  246. sprintf(
  247. 'Cannot compile incomplete `\%s` expression, there is an open `when()` buffer present ' .
  248. 'that must be closed using `then()`.',
  249. CaseExpressionInterface::class
  250. )
  251. );
  252. }
  253. if (empty($this->when)) {
  254. throw new LogicException(
  255. sprintf(
  256. 'Cannot compile incomplete `\%s` expression, there are no `WHEN ... THEN ...` statements.',
  257. CaseExpressionInterface::class
  258. )
  259. );
  260. }
  261. $value = '';
  262. if ($this->isSimpleVariant) {
  263. $value = $this->compileNullableValue($binder, $this->value, $this->valueType) . ' ';
  264. }
  265. $whenThenExpressions = [];
  266. foreach ($this->when as $whenThen) {
  267. $whenThenExpressions[] = $whenThen->sql($binder);
  268. }
  269. $whenThen = implode(' ', $whenThenExpressions);
  270. $else = $this->compileNullableValue($binder, $this->else, $this->elseType);
  271. return "CASE {$value}{$whenThen} ELSE $else END";
  272. }
  273. /**
  274. * @inheritDoc
  275. */
  276. public function traverse(Closure $callback)
  277. {
  278. if ($this->whenBuffer !== null) {
  279. throw new LogicException(
  280. sprintf(
  281. 'Cannot traverse incomplete `\%s` expression, there is an open `when()` buffer present ' .
  282. 'that must be closed using `then()`.',
  283. CaseExpressionInterface::class
  284. )
  285. );
  286. }
  287. if ($this->value instanceof ExpressionInterface) {
  288. $callback($this->value);
  289. $this->value->traverse($callback);
  290. }
  291. foreach ($this->when as $when) {
  292. $callback($when);
  293. $when->traverse($callback);
  294. }
  295. if ($this->else instanceof ExpressionInterface) {
  296. $callback($this->else);
  297. $this->else->traverse($callback);
  298. }
  299. return $this;
  300. }
  301. /**
  302. * Clones the inner expression objects.
  303. *
  304. * @return void
  305. */
  306. public function __clone()
  307. {
  308. if ($this->whenBuffer !== null) {
  309. throw new LogicException(
  310. sprintf(
  311. 'Cannot clone incomplete `\%s` expression, there is an open `when()` buffer present ' .
  312. 'that must be closed using `then()`.',
  313. CaseExpressionInterface::class
  314. )
  315. );
  316. }
  317. if ($this->value instanceof ExpressionInterface) {
  318. $this->value = clone $this->value;
  319. }
  320. foreach ($this->when as $key => $when) {
  321. $this->when[$key] = clone $this->when[$key];
  322. }
  323. if ($this->else instanceof ExpressionInterface) {
  324. $this->else = clone $this->else;
  325. }
  326. }
  327. }