WhenThenExpression.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  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\Query;
  19. use Cake\Database\Type\ExpressionTypeCasterTrait;
  20. use Cake\Database\TypeMap;
  21. use Cake\Database\ValueBinder;
  22. use Closure;
  23. use InvalidArgumentException;
  24. use LogicException;
  25. /**
  26. * Represents a SQL when/then clause with a fluid API
  27. */
  28. class WhenThenExpression implements ExpressionInterface
  29. {
  30. use CaseExpressionTrait;
  31. use ExpressionTypeCasterTrait;
  32. /**
  33. * The names of the clauses that are valid for use with the
  34. * `clause()` method.
  35. *
  36. * @var array<string>
  37. */
  38. protected $validClauseNames = [
  39. 'when',
  40. 'then',
  41. ];
  42. /**
  43. * The type map to use when using an array of conditions for the
  44. * `WHEN` value.
  45. *
  46. * @var \Cake\Database\TypeMap
  47. */
  48. protected $_typeMap;
  49. /**
  50. * Then `WHEN` value.
  51. *
  52. * @var \Cake\Database\ExpressionInterface|scalar|object|null
  53. */
  54. protected $when = null;
  55. /**
  56. * The `WHEN` value type.
  57. *
  58. * @var array|string|null
  59. */
  60. protected $whenType = null;
  61. /**
  62. * The `THEN` value.
  63. *
  64. * @var \Cake\Database\ExpressionInterface|scalar|object|null
  65. */
  66. protected $then = null;
  67. /**
  68. * Whether the `THEN` value has been defined, eg whether `then()`
  69. * has been invoked.
  70. *
  71. * @var bool
  72. */
  73. protected $hasThenBeenDefined = false;
  74. /**
  75. * The `THEN` result type.
  76. *
  77. * @var string|null
  78. */
  79. protected $thenType = null;
  80. /**
  81. * Constructor.
  82. *
  83. * @param \Cake\Database\TypeMap|null $typeMap The type map to use when using an array of conditions for the `WHEN`
  84. * value.
  85. */
  86. public function __construct(?TypeMap $typeMap = null)
  87. {
  88. if ($typeMap === null) {
  89. $typeMap = new TypeMap();
  90. }
  91. $this->_typeMap = $typeMap;
  92. }
  93. /**
  94. * Sets the `WHEN` value.
  95. *
  96. * @param \Cake\Database\ExpressionInterface|object|array|scalar $when The `WHEN` value. When using an array of
  97. * conditions, it must be compatible with `\Cake\Database\Query::where()`. Note that this argument is _not_
  98. * completely safe for use with user data, as a user supplied array would allow for raw SQL to slip in! If you
  99. * plan to use user data, either pass a single type for the `$type` argument (which forces the `$when` value to be
  100. * a non-array, and then always binds the data), use a conditions array where the user data is only passed on the
  101. * value side of the array entries, or custom bindings!
  102. * @param array|string|null $type The when value type. Either an associative array when using array style
  103. * conditions, or else a string. If no type is provided, the type will be tried to be inferred from the value.
  104. * @return $this
  105. * @throws \InvalidArgumentException In case the `$when` argument is neither a non-empty array, nor a scalar value,
  106. * an object, or an instance of `\Cake\Database\ExpressionInterface`.
  107. * @throws \InvalidArgumentException In case the `$type` argument is neither an array, a string, nor null.
  108. * @throws \InvalidArgumentException In case the `$when` argument is an array, and the `$type` argument is neither
  109. * an array, nor null.
  110. * @throws \InvalidArgumentException In case the `$when` argument is a non-array value, and the `$type` argument is
  111. * neither a string, nor null.
  112. * @see CaseStatementExpression::when() for a more detailed usage explanation.
  113. */
  114. public function when($when, $type = null)
  115. {
  116. if (
  117. !(is_array($when) && !empty($when)) &&
  118. !is_scalar($when) &&
  119. !is_object($when)
  120. ) {
  121. throw new InvalidArgumentException(sprintf(
  122. 'The `$when` argument must be either a non-empty array, a scalar value, an object, ' .
  123. 'or an instance of `\%s`, `%s` given.',
  124. ExpressionInterface::class,
  125. is_array($when) ? '[]' : get_debug_type($when)
  126. ));
  127. }
  128. if (
  129. $type !== null &&
  130. !is_array($type) &&
  131. !is_string($type)
  132. ) {
  133. throw new InvalidArgumentException(sprintf(
  134. 'The `$type` argument must be either an array, a string, or `null`, `%s` given.',
  135. get_debug_type($type)
  136. ));
  137. }
  138. if (is_array($when)) {
  139. if (
  140. $type !== null &&
  141. !is_array($type)
  142. ) {
  143. throw new InvalidArgumentException(sprintf(
  144. 'When using an array for the `$when` argument, the `$type` argument must be an ' .
  145. 'array too, `%s` given.',
  146. get_debug_type($type)
  147. ));
  148. }
  149. // avoid dirtying the type map for possible consecutive `when()` calls
  150. $typeMap = clone $this->_typeMap;
  151. if (
  152. is_array($type) &&
  153. count($type) > 0
  154. ) {
  155. $typeMap = $typeMap->setTypes($type);
  156. }
  157. $when = new QueryExpression($when, $typeMap);
  158. } else {
  159. if (
  160. $type !== null &&
  161. !is_string($type)
  162. ) {
  163. throw new InvalidArgumentException(sprintf(
  164. 'When using a non-array value for the `$when` argument, the `$type` argument must ' .
  165. 'be a string, `%s` given.',
  166. get_debug_type($type)
  167. ));
  168. }
  169. if (
  170. $type === null &&
  171. !($when instanceof ExpressionInterface)
  172. ) {
  173. $type = $this->inferType($when);
  174. }
  175. }
  176. $this->when = $when;
  177. $this->whenType = $type;
  178. return $this;
  179. }
  180. /**
  181. * Sets the `THEN` result value.
  182. *
  183. * @param \Cake\Database\ExpressionInterface|object|scalar|null $result The result value.
  184. * @param string|null $type The result type. If no type is provided, the type will be inferred from the given
  185. * result value.
  186. * @return $this
  187. */
  188. public function then($result, ?string $type = null)
  189. {
  190. if (
  191. $result !== null &&
  192. !is_scalar($result) &&
  193. !(is_object($result) && !($result instanceof Closure))
  194. ) {
  195. throw new InvalidArgumentException(sprintf(
  196. 'The `$result` argument must be either `null`, a scalar value, an object, ' .
  197. 'or an instance of `\%s`, `%s` given.',
  198. ExpressionInterface::class,
  199. get_debug_type($result)
  200. ));
  201. }
  202. $this->then = $result;
  203. if ($type === null) {
  204. $type = $this->inferType($result);
  205. }
  206. $this->thenType = $type;
  207. $this->hasThenBeenDefined = true;
  208. return $this;
  209. }
  210. /**
  211. * Returns the expression's result value type.
  212. *
  213. * @return string|null
  214. * @see WhenThenExpression::then()
  215. */
  216. public function getResultType(): ?string
  217. {
  218. return $this->thenType;
  219. }
  220. /**
  221. * Returns the available data for the given clause.
  222. *
  223. * ### Available clauses
  224. *
  225. * The following clause names are available:
  226. *
  227. * * `when`: The `WHEN` value.
  228. * * `then`: The `THEN` result value.
  229. *
  230. * @param string $clause The name of the clause to obtain.
  231. * @return \Cake\Database\ExpressionInterface|object|scalar|null
  232. * @throws \InvalidArgumentException In case the given clause name is invalid.
  233. */
  234. public function clause(string $clause)
  235. {
  236. if (!in_array($clause, $this->validClauseNames, true)) {
  237. throw new InvalidArgumentException(
  238. sprintf(
  239. 'The `$clause` argument must be one of `%s`, the given value `%s` is invalid.',
  240. implode('`, `', $this->validClauseNames),
  241. $clause
  242. )
  243. );
  244. }
  245. return $this->{$clause};
  246. }
  247. /**
  248. * @inheritDoc
  249. */
  250. public function sql(ValueBinder $binder): string
  251. {
  252. if ($this->when === null) {
  253. throw new LogicException('Case expression has incomplete when clause. Missing `when()`.');
  254. }
  255. if (!$this->hasThenBeenDefined) {
  256. throw new LogicException('Case expression has incomplete when clause. Missing `then()` after `when()`.');
  257. }
  258. $when = $this->when;
  259. if (
  260. is_string($this->whenType) &&
  261. !($when instanceof ExpressionInterface)
  262. ) {
  263. $when = $this->_castToExpression($when, $this->whenType);
  264. }
  265. if ($when instanceof Query) {
  266. $when = sprintf('(%s)', $when->sql($binder));
  267. } elseif ($when instanceof ExpressionInterface) {
  268. $when = $when->sql($binder);
  269. } else {
  270. $placeholder = $binder->placeholder('c');
  271. if (is_string($this->whenType)) {
  272. $whenType = $this->whenType;
  273. } else {
  274. $whenType = null;
  275. }
  276. $binder->bind($placeholder, $when, $whenType);
  277. $when = $placeholder;
  278. }
  279. $then = $this->compileNullableValue($binder, $this->then, $this->thenType);
  280. return "WHEN $when THEN $then";
  281. }
  282. /**
  283. * @inheritDoc
  284. */
  285. public function traverse(Closure $callback)
  286. {
  287. if ($this->when instanceof ExpressionInterface) {
  288. $callback($this->when);
  289. $this->when->traverse($callback);
  290. }
  291. if ($this->then instanceof ExpressionInterface) {
  292. $callback($this->then);
  293. $this->then->traverse($callback);
  294. }
  295. return $this;
  296. }
  297. /**
  298. * Clones the inner expression objects.
  299. *
  300. * @return void
  301. */
  302. public function __clone()
  303. {
  304. if ($this->when instanceof ExpressionInterface) {
  305. $this->when = clone $this->when;
  306. }
  307. if ($this->then instanceof ExpressionInterface) {
  308. $this->then = clone $this->then;
  309. }
  310. }
  311. }