CaseStatementExpression.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409
  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. * Whether this is a simple case expression.
  32. *
  33. * @var bool
  34. */
  35. protected $isSimpleVariant = false;
  36. /**
  37. * The case value.
  38. *
  39. * @var \Cake\Database\ExpressionInterface|object|scalar|null
  40. */
  41. protected $value = null;
  42. /**
  43. * The case value type.
  44. *
  45. * @var string|null
  46. */
  47. protected $valueType = null;
  48. /**
  49. * The `WHEN ... THEN ...` expressions.
  50. *
  51. * @var \Cake\Database\Expression\WhenThenExpressionInterface[]
  52. */
  53. protected $when = [];
  54. /**
  55. * Buffer that holds values and types for use with `then()`.
  56. *
  57. * @var array|null
  58. */
  59. protected $whenBuffer = null;
  60. /**
  61. * The else part result value.
  62. *
  63. * @var mixed|null
  64. */
  65. protected $else = null;
  66. /**
  67. * The else part result type.
  68. *
  69. * @var string|null
  70. */
  71. protected $elseType = null;
  72. /**
  73. * The return type.
  74. *
  75. * @var string|null
  76. */
  77. protected $returnType = null;
  78. /**
  79. * Constructor.
  80. *
  81. * @param \Cake\Database\TypeMap|null $typeMap The type map to use when using an array of conditions for the `WHEN`
  82. * value.
  83. */
  84. public function __construct(?TypeMap $typeMap = null)
  85. {
  86. if ($typeMap !== null) {
  87. $this->setTypeMap($typeMap);
  88. }
  89. }
  90. /**
  91. * @inheritDoc
  92. */
  93. public function getValue()
  94. {
  95. return $this->value;
  96. }
  97. /**
  98. * @inheritDoc
  99. */
  100. public function value($value, ?string $valueType = null)
  101. {
  102. if (
  103. $value !== null &&
  104. !is_scalar($value) &&
  105. !(is_object($value) && !($value instanceof Closure))
  106. ) {
  107. throw new InvalidArgumentException(sprintf(
  108. 'The `$value` argument must be either `null`, a scalar value, an object, ' .
  109. 'or an instance of `\%s`, `%s` given.',
  110. ExpressionInterface::class,
  111. getTypeName($value)
  112. ));
  113. }
  114. $this->value = $value;
  115. if (
  116. $value !== null &&
  117. $valueType === null
  118. ) {
  119. $valueType = $this->inferType($value);
  120. }
  121. $this->valueType = $valueType;
  122. $this->isSimpleVariant = true;
  123. return $this;
  124. }
  125. /**
  126. * @inheritDoc
  127. */
  128. public function getValueType(): ?string
  129. {
  130. return $this->valueType;
  131. }
  132. /**
  133. * @inheritDoc
  134. */
  135. public function getWhen(): array
  136. {
  137. return $this->when;
  138. }
  139. /**
  140. * @inheritDoc
  141. */
  142. public function when($when, $type = null)
  143. {
  144. if ($this->whenBuffer !== null) {
  145. throw new LogicException(
  146. 'Cannot add new `WHEN` value while an open `when()` buffer is present, ' .
  147. 'it must first be closed using `then()`.'
  148. );
  149. }
  150. if ($when instanceof Closure) {
  151. $when = $when(new WhenThenExpression($this->getTypeMap()));
  152. if (!($when instanceof WhenThenExpressionInterface)) {
  153. throw new LogicException(sprintf(
  154. '`when()` callables must return an instance of `\%s`, `%s` given.',
  155. WhenThenExpressionInterface::class,
  156. getTypeName($when)
  157. ));
  158. }
  159. }
  160. if ($when instanceof WhenThenExpressionInterface) {
  161. $this->when[] = $when;
  162. } else {
  163. $this->whenBuffer = ['when' => $when, 'type' => $type];
  164. }
  165. return $this;
  166. }
  167. /**
  168. * @inheritDoc
  169. */
  170. public function then($result, ?string $type = null)
  171. {
  172. if ($this->whenBuffer === null) {
  173. throw new LogicException(
  174. 'There is no `when()` buffer present, ' .
  175. 'you must first open one before calling `then()`.'
  176. );
  177. }
  178. $whenThen = (new WhenThenExpression($this->getTypeMap()))
  179. ->when($this->whenBuffer['when'], $this->whenBuffer['type'])
  180. ->then($result, $type);
  181. $this->whenBuffer = null;
  182. $this->when($whenThen);
  183. return $this;
  184. }
  185. /**
  186. * @inheritDoc
  187. */
  188. public function getElse()
  189. {
  190. return $this->else;
  191. }
  192. /**
  193. * @inheritDoc
  194. */
  195. public function else($result, ?string $type = null)
  196. {
  197. if ($this->whenBuffer !== null) {
  198. throw new LogicException(
  199. 'Cannot set `ELSE` value when an open `when()` buffer is present, ' .
  200. 'it must first be closed using `then()`.'
  201. );
  202. }
  203. if (
  204. $result !== null &&
  205. !is_scalar($result) &&
  206. !(is_object($result) && !($result instanceof Closure))
  207. ) {
  208. throw new InvalidArgumentException(sprintf(
  209. 'The `$result` argument must be either `null`, a scalar value, an object, ' .
  210. 'or an instance of `\%s`, `%s` given.',
  211. ExpressionInterface::class,
  212. getTypeName($result)
  213. ));
  214. }
  215. if ($type === null) {
  216. $type = $this->inferType($result);
  217. }
  218. $this->whenBuffer = null;
  219. $this->else = $result;
  220. $this->elseType = $type;
  221. return $this;
  222. }
  223. /**
  224. * @inheritDoc
  225. */
  226. public function getElseType(): ?string
  227. {
  228. return $this->elseType;
  229. }
  230. /**
  231. * @inheritDoc
  232. */
  233. public function getReturnType(): string
  234. {
  235. if ($this->returnType !== null) {
  236. return $this->returnType;
  237. }
  238. $types = [];
  239. foreach ($this->when as $when) {
  240. $type = $when->getThenType();
  241. if ($type !== null) {
  242. $types[] = $type;
  243. }
  244. }
  245. if ($this->elseType !== null) {
  246. $types[] = $this->elseType;
  247. }
  248. $types = array_unique($types);
  249. if (count($types) === 1) {
  250. return $types[0];
  251. }
  252. return 'string';
  253. }
  254. /**
  255. * @inheritDoc
  256. */
  257. public function setReturnType(string $type)
  258. {
  259. $this->returnType = $type;
  260. return $this;
  261. }
  262. /**
  263. * @inheritDoc
  264. */
  265. public function sql(ValueBinder $binder): string
  266. {
  267. if ($this->whenBuffer !== null) {
  268. throw new LogicException(
  269. sprintf(
  270. 'Cannot compile incomplete `\%s` expression, there is an open `when()` buffer present ' .
  271. 'that must be closed using `then()`.',
  272. CaseExpressionInterface::class
  273. )
  274. );
  275. }
  276. if (empty($this->when)) {
  277. throw new LogicException(
  278. sprintf(
  279. 'Cannot compile incomplete `\%s` expression, there are no `WHEN ... THEN ...` statements.',
  280. CaseExpressionInterface::class
  281. )
  282. );
  283. }
  284. $value = '';
  285. if ($this->isSimpleVariant) {
  286. $value = $this->compileNullableValue($binder, $this->value, $this->valueType) . ' ';
  287. }
  288. $whenThenExpressions = [];
  289. foreach ($this->when as $whenThen) {
  290. $whenThenExpressions[] = $whenThen->sql($binder);
  291. }
  292. $whenThen = implode(' ', $whenThenExpressions);
  293. $else = $this->compileNullableValue($binder, $this->else, $this->elseType);
  294. return "CASE {$value}{$whenThen} ELSE $else END";
  295. }
  296. /**
  297. * @inheritDoc
  298. */
  299. public function traverse(Closure $callback)
  300. {
  301. if ($this->whenBuffer !== null) {
  302. throw new LogicException(
  303. sprintf(
  304. 'Cannot traverse incomplete `\%s` expression, there is an open `when()` buffer present ' .
  305. 'that must be closed using `then()`.',
  306. CaseExpressionInterface::class
  307. )
  308. );
  309. }
  310. if ($this->value instanceof ExpressionInterface) {
  311. $callback($this->value);
  312. $this->value->traverse($callback);
  313. }
  314. foreach ($this->when as $when) {
  315. $callback($when);
  316. $when->traverse($callback);
  317. }
  318. if ($this->else instanceof ExpressionInterface) {
  319. $callback($this->else);
  320. $this->else->traverse($callback);
  321. }
  322. return $this;
  323. }
  324. /**
  325. * Clones the inner expression objects.
  326. *
  327. * @return void
  328. */
  329. public function __clone()
  330. {
  331. if ($this->whenBuffer !== null) {
  332. throw new LogicException(
  333. sprintf(
  334. 'Cannot clone incomplete `\%s` expression, there is an open `when()` buffer present ' .
  335. 'that must be closed using `then()`.',
  336. CaseExpressionInterface::class
  337. )
  338. );
  339. }
  340. if ($this->value instanceof ExpressionInterface) {
  341. $this->value = clone $this->value;
  342. }
  343. foreach ($this->when as $key => $when) {
  344. $this->when[$key] = clone $this->when[$key];
  345. }
  346. if ($this->else instanceof ExpressionInterface) {
  347. $this->else = clone $this->else;
  348. }
  349. }
  350. }