Comparison.php 8.6 KB

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