Comparison.php 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  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\Exception as DatabaseException;
  17. use Cake\Database\ExpressionInterface;
  18. use Cake\Database\Type\ExpressionTypeCasterTrait;
  19. use Cake\Database\ValueBinder;
  20. /**
  21. * A Comparison is a type of query expression that represents an operation
  22. * involving a field an operator and a value. In its most common form the
  23. * string representation of a comparison is `field = value`
  24. */
  25. class Comparison implements ExpressionInterface, FieldInterface
  26. {
  27. use ExpressionTypeCasterTrait;
  28. use FieldTrait;
  29. /**
  30. * The value to be used in the right hand side of the operation
  31. *
  32. * @var mixed
  33. */
  34. protected $_value;
  35. /**
  36. * The type to be used for casting the value to a database representation
  37. *
  38. * @var string|array
  39. */
  40. protected $_type;
  41. /**
  42. * The operator used for comparing field and value
  43. *
  44. * @var string
  45. */
  46. protected $_operator;
  47. /**
  48. * Whether or not the value in this expression is a traversable
  49. *
  50. * @var bool
  51. */
  52. protected $_isMultiple = false;
  53. /**
  54. * A cached list of ExpressionInterface objects that were
  55. * found in the value for this expression.
  56. *
  57. * @var \Cake\Database\ExpressionInterface[]
  58. */
  59. protected $_valueExpressions = [];
  60. /**
  61. * Constructor
  62. *
  63. * @param string|\Cake\Database\ExpressionInterface $field the field name to compare to a value
  64. * @param mixed $value The value to be used in comparison
  65. * @param string $type the type name used to cast the value
  66. * @param string $operator the operator used for comparing field and value
  67. */
  68. public function __construct($field, $value, $type, $operator)
  69. {
  70. if (is_string($type)) {
  71. $this->_type = $type;
  72. }
  73. $this->setField($field);
  74. $this->setValue($value);
  75. $this->_operator = $operator;
  76. }
  77. /**
  78. * Sets the value
  79. *
  80. * @param mixed $value The value to compare
  81. * @return void
  82. */
  83. public function setValue($value)
  84. {
  85. $hasType = isset($this->_type) && is_string($this->_type);
  86. $isMultiple = $hasType && strpos($this->_type, '[]') !== false;
  87. if ($hasType) {
  88. $value = $this->_castToExpression($value, $this->_type);
  89. }
  90. if ($isMultiple) {
  91. list($value, $this->_valueExpressions) = $this->_collectExpressions($value);
  92. }
  93. $this->_isMultiple = $isMultiple;
  94. $this->_value = $value;
  95. }
  96. /**
  97. * Returns the value used for comparison
  98. *
  99. * @return mixed
  100. */
  101. public function getValue()
  102. {
  103. return $this->_value;
  104. }
  105. /**
  106. * Sets the operator to use for the comparison
  107. *
  108. * @param string $operator The operator to be used for the comparison.
  109. * @return void
  110. */
  111. public function setOperator($operator)
  112. {
  113. $this->_operator = $operator;
  114. }
  115. /**
  116. * Returns the operator used for comparison
  117. *
  118. * @return string
  119. */
  120. public function getOperator()
  121. {
  122. return $this->_operator;
  123. }
  124. /**
  125. * Convert the expression into a SQL fragment.
  126. *
  127. * @param \Cake\Database\ValueBinder $generator Placeholder generator object
  128. * @return string
  129. */
  130. public function sql(ValueBinder $generator)
  131. {
  132. $field = $this->_field;
  133. if ($field instanceof ExpressionInterface) {
  134. $field = $field->sql($generator);
  135. }
  136. if ($this->_value instanceof ExpressionInterface) {
  137. $template = '%s %s (%s)';
  138. $value = $this->_value->sql($generator);
  139. } else {
  140. list($template, $value) = $this->_stringExpression($generator);
  141. }
  142. return sprintf($template, $field, $this->_operator, $value);
  143. }
  144. /**
  145. * {@inheritDoc}
  146. *
  147. */
  148. public function traverse(callable $callable)
  149. {
  150. if ($this->_field instanceof ExpressionInterface) {
  151. $callable($this->_field);
  152. $this->_field->traverse($callable);
  153. }
  154. if ($this->_value instanceof ExpressionInterface) {
  155. $callable($this->_value);
  156. $this->_value->traverse($callable);
  157. }
  158. foreach ($this->_valueExpressions as $v) {
  159. $callable($v);
  160. $v->traverse($callable);
  161. }
  162. }
  163. /**
  164. * Create a deep clone.
  165. *
  166. * Clones the field and value if they are expression objects.
  167. *
  168. * @return void
  169. */
  170. public function __clone()
  171. {
  172. foreach (['_value', '_field'] as $prop) {
  173. if ($this->{$prop} instanceof ExpressionInterface) {
  174. $this->{$prop} = clone $this->{$prop};
  175. }
  176. }
  177. }
  178. /**
  179. * Returns a template and a placeholder for the value after registering it
  180. * with the placeholder $generator
  181. *
  182. * @param \Cake\Database\ValueBinder $generator The value binder to use.
  183. * @return array First position containing the template and the second a placeholder
  184. */
  185. protected function _stringExpression($generator)
  186. {
  187. $template = '%s ';
  188. if ($this->_field instanceof ExpressionInterface) {
  189. $template = '(%s) ';
  190. }
  191. if ($this->_isMultiple) {
  192. $template .= '%s (%s)';
  193. $type = str_replace('[]', '', $this->_type);
  194. $value = $this->_flattenValue($this->_value, $generator, $type);
  195. // To avoid SQL errors when comparing a field to a list of empty values,
  196. // better just throw an exception here
  197. if ($value === '') {
  198. $field = $this->_field instanceof ExpressionInterface ? $this->_field->sql($generator) : $this->_field;
  199. throw new DatabaseException(
  200. "Impossible to generate condition with empty list of values for field ($field)"
  201. );
  202. }
  203. } else {
  204. $template .= '%s %s';
  205. $value = $this->_bindValue($this->_value, $generator, $this->_type);
  206. }
  207. return [$template, $value];
  208. }
  209. /**
  210. * Registers a value in the placeholder generator and returns the generated placeholder
  211. *
  212. * @param mixed $value The value to bind
  213. * @param \Cake\Database\ValueBinder $generator The value binder to use
  214. * @param string $type The type of $value
  215. * @return string generated placeholder
  216. */
  217. protected function _bindValue($value, $generator, $type)
  218. {
  219. $placeholder = $generator->placeholder('c');
  220. $generator->bind($placeholder, $value, $type);
  221. return $placeholder;
  222. }
  223. /**
  224. * Converts a traversable value into a set of placeholders generated by
  225. * $generator and separated by `,`
  226. *
  227. * @param array|\Traversable $value the value to flatten
  228. * @param \Cake\Database\ValueBinder $generator The value binder to use
  229. * @param string|array|null $type the type to cast values to
  230. * @return string
  231. */
  232. protected function _flattenValue($value, $generator, $type = 'string')
  233. {
  234. $parts = [];
  235. foreach ($this->_valueExpressions as $k => $v) {
  236. $parts[$k] = $v->sql($generator);
  237. unset($value[$k]);
  238. }
  239. if (!empty($value)) {
  240. $parts += $generator->generateManyNamed($value, $type);
  241. }
  242. return implode(',', $parts);
  243. }
  244. /**
  245. * Returns an array with the original $values in the first position
  246. * and all ExpressionInterface objects that could be found in the second
  247. * position.
  248. *
  249. * @param array|\Traversable $values The rows to insert
  250. * @return array
  251. */
  252. protected function _collectExpressions($values)
  253. {
  254. if ($values instanceof ExpressionInterface) {
  255. return [$values, []];
  256. }
  257. $expressions = $result = [];
  258. $isArray = is_array($values);
  259. if ($isArray) {
  260. $result = $values;
  261. }
  262. foreach ($values as $k => $v) {
  263. if ($v instanceof ExpressionInterface) {
  264. $expressions[$k] = $v;
  265. }
  266. if ($isArray) {
  267. $result[$k] = $v;
  268. }
  269. }
  270. return [$result, $expressions];
  271. }
  272. }