Comparison.php 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  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|null $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. public function traverse(callable $visitor)
  148. {
  149. if ($this->_field instanceof ExpressionInterface) {
  150. $visitor($this->_field);
  151. $this->_field->traverse($visitor);
  152. }
  153. if ($this->_value instanceof ExpressionInterface) {
  154. $visitor($this->_value);
  155. $this->_value->traverse($visitor);
  156. }
  157. foreach ($this->_valueExpressions as $v) {
  158. $visitor($v);
  159. $v->traverse($visitor);
  160. }
  161. }
  162. /**
  163. * Create a deep clone.
  164. *
  165. * Clones the field and value if they are expression objects.
  166. *
  167. * @return void
  168. */
  169. public function __clone()
  170. {
  171. foreach (['_value', '_field'] as $prop) {
  172. if ($this->{$prop} instanceof ExpressionInterface) {
  173. $this->{$prop} = clone $this->{$prop};
  174. }
  175. }
  176. }
  177. /**
  178. * Returns a template and a placeholder for the value after registering it
  179. * with the placeholder $generator
  180. *
  181. * @param \Cake\Database\ValueBinder $generator The value binder to use.
  182. * @return array First position containing the template and the second a placeholder
  183. */
  184. protected function _stringExpression($generator)
  185. {
  186. $template = '%s ';
  187. if ($this->_field instanceof ExpressionInterface) {
  188. $template = '(%s) ';
  189. }
  190. if ($this->_isMultiple) {
  191. $template .= '%s (%s)';
  192. $type = str_replace('[]', '', $this->_type);
  193. $value = $this->_flattenValue($this->_value, $generator, $type);
  194. // To avoid SQL errors when comparing a field to a list of empty values,
  195. // better just throw an exception here
  196. if ($value === '') {
  197. $field = $this->_field instanceof ExpressionInterface ? $this->_field->sql($generator) : $this->_field;
  198. throw new DatabaseException(
  199. "Impossible to generate condition with empty list of values for field ($field)"
  200. );
  201. }
  202. } else {
  203. $template .= '%s %s';
  204. $value = $this->_bindValue($this->_value, $generator, $this->_type);
  205. }
  206. return [$template, $value];
  207. }
  208. /**
  209. * Registers a value in the placeholder generator and returns the generated placeholder
  210. *
  211. * @param mixed $value The value to bind
  212. * @param \Cake\Database\ValueBinder $generator The value binder to use
  213. * @param string $type The type of $value
  214. * @return string generated placeholder
  215. */
  216. protected function _bindValue($value, $generator, $type)
  217. {
  218. $placeholder = $generator->placeholder('c');
  219. $generator->bind($placeholder, $value, $type);
  220. return $placeholder;
  221. }
  222. /**
  223. * Converts a traversable value into a set of placeholders generated by
  224. * $generator and separated by `,`
  225. *
  226. * @param array|\Traversable $value the value to flatten
  227. * @param \Cake\Database\ValueBinder $generator The value binder to use
  228. * @param string|array|null $type the type to cast values to
  229. * @return string
  230. */
  231. protected function _flattenValue($value, $generator, $type = 'string')
  232. {
  233. $parts = [];
  234. foreach ($this->_valueExpressions as $k => $v) {
  235. $parts[$k] = $v->sql($generator);
  236. unset($value[$k]);
  237. }
  238. if (!empty($value)) {
  239. $parts += $generator->generateManyNamed($value, $type);
  240. }
  241. return implode(',', $parts);
  242. }
  243. /**
  244. * Returns an array with the original $values in the first position
  245. * and all ExpressionInterface objects that could be found in the second
  246. * position.
  247. *
  248. * @param array|\Traversable $values The rows to insert
  249. * @return array
  250. */
  251. protected function _collectExpressions($values)
  252. {
  253. if ($values instanceof ExpressionInterface) {
  254. return [$values, []];
  255. }
  256. $expressions = $result = [];
  257. $isArray = is_array($values);
  258. if ($isArray) {
  259. $result = $values;
  260. }
  261. foreach ($values as $k => $v) {
  262. if ($v instanceof ExpressionInterface) {
  263. $expressions[$k] = $v;
  264. }
  265. if ($isArray) {
  266. $result[$k] = $v;
  267. }
  268. }
  269. return [$result, $expressions];
  270. }
  271. }