123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314 |
- <?php
- /**
- * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
- * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
- *
- * Licensed under The MIT License
- * For full copyright and license information, please see the LICENSE.txt
- * Redistributions of files must retain the above copyright notice.
- *
- * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
- * @link https://cakephp.org CakePHP(tm) Project
- * @since 3.0.0
- * @license https://opensource.org/licenses/mit-license.php MIT License
- */
- namespace Cake\Database\Expression;
- use Cake\Database\Exception as DatabaseException;
- use Cake\Database\ExpressionInterface;
- use Cake\Database\Type\ExpressionTypeCasterTrait;
- use Cake\Database\ValueBinder;
- /**
- * A Comparison is a type of query expression that represents an operation
- * involving a field an operator and a value. In its most common form the
- * string representation of a comparison is `field = value`
- */
- class Comparison implements ExpressionInterface, FieldInterface
- {
- use ExpressionTypeCasterTrait;
- use FieldTrait;
- /**
- * The value to be used in the right hand side of the operation
- *
- * @var mixed
- */
- protected $_value;
- /**
- * The type to be used for casting the value to a database representation
- *
- * @var string|array
- */
- protected $_type;
- /**
- * The operator used for comparing field and value
- *
- * @var string
- */
- protected $_operator;
- /**
- * Whether or not the value in this expression is a traversable
- *
- * @var bool
- */
- protected $_isMultiple = false;
- /**
- * A cached list of ExpressionInterface objects that were
- * found in the value for this expression.
- *
- * @var \Cake\Database\ExpressionInterface[]
- */
- protected $_valueExpressions = [];
- /**
- * Constructor
- *
- * @param string|\Cake\Database\ExpressionInterface $field the field name to compare to a value
- * @param mixed $value The value to be used in comparison
- * @param string $type the type name used to cast the value
- * @param string $operator the operator used for comparing field and value
- */
- public function __construct($field, $value, $type, $operator)
- {
- if (is_string($type)) {
- $this->_type = $type;
- }
- $this->setField($field);
- $this->setValue($value);
- $this->_operator = $operator;
- }
- /**
- * Sets the value
- *
- * @param mixed $value The value to compare
- * @return void
- */
- public function setValue($value)
- {
- $hasType = isset($this->_type) && is_string($this->_type);
- $isMultiple = $hasType && strpos($this->_type, '[]') !== false;
- if ($hasType) {
- $value = $this->_castToExpression($value, $this->_type);
- }
- if ($isMultiple) {
- list($value, $this->_valueExpressions) = $this->_collectExpressions($value);
- }
- $this->_isMultiple = $isMultiple;
- $this->_value = $value;
- }
- /**
- * Returns the value used for comparison
- *
- * @return mixed
- */
- public function getValue()
- {
- return $this->_value;
- }
- /**
- * Sets the operator to use for the comparison
- *
- * @param string $operator The operator to be used for the comparison.
- * @return void
- */
- public function setOperator($operator)
- {
- $this->_operator = $operator;
- }
- /**
- * Returns the operator used for comparison
- *
- * @return string
- */
- public function getOperator()
- {
- return $this->_operator;
- }
- /**
- * Convert the expression into a SQL fragment.
- *
- * @param \Cake\Database\ValueBinder $generator Placeholder generator object
- * @return string
- */
- public function sql(ValueBinder $generator)
- {
- $field = $this->_field;
- if ($field instanceof ExpressionInterface) {
- $field = $field->sql($generator);
- }
- if ($this->_value instanceof ExpressionInterface) {
- $template = '%s %s (%s)';
- $value = $this->_value->sql($generator);
- } else {
- list($template, $value) = $this->_stringExpression($generator);
- }
- return sprintf($template, $field, $this->_operator, $value);
- }
- /**
- * {@inheritDoc}
- *
- */
- public function traverse(callable $callable)
- {
- if ($this->_field instanceof ExpressionInterface) {
- $callable($this->_field);
- $this->_field->traverse($callable);
- }
- if ($this->_value instanceof ExpressionInterface) {
- $callable($this->_value);
- $this->_value->traverse($callable);
- }
- foreach ($this->_valueExpressions as $v) {
- $callable($v);
- $v->traverse($callable);
- }
- }
- /**
- * Create a deep clone.
- *
- * Clones the field and value if they are expression objects.
- *
- * @return void
- */
- public function __clone()
- {
- foreach (['_value', '_field'] as $prop) {
- if ($this->{$prop} instanceof ExpressionInterface) {
- $this->{$prop} = clone $this->{$prop};
- }
- }
- }
- /**
- * Returns a template and a placeholder for the value after registering it
- * with the placeholder $generator
- *
- * @param \Cake\Database\ValueBinder $generator The value binder to use.
- * @return array First position containing the template and the second a placeholder
- */
- protected function _stringExpression($generator)
- {
- $template = '%s ';
- if ($this->_field instanceof ExpressionInterface) {
- $template = '(%s) ';
- }
- if ($this->_isMultiple) {
- $template .= '%s (%s)';
- $type = str_replace('[]', '', $this->_type);
- $value = $this->_flattenValue($this->_value, $generator, $type);
- // To avoid SQL errors when comparing a field to a list of empty values,
- // better just throw an exception here
- if ($value === '') {
- $field = $this->_field instanceof ExpressionInterface ? $this->_field->sql($generator) : $this->_field;
- throw new DatabaseException(
- "Impossible to generate condition with empty list of values for field ($field)"
- );
- }
- } else {
- $template .= '%s %s';
- $value = $this->_bindValue($this->_value, $generator, $this->_type);
- }
- return [$template, $value];
- }
- /**
- * Registers a value in the placeholder generator and returns the generated placeholder
- *
- * @param mixed $value The value to bind
- * @param \Cake\Database\ValueBinder $generator The value binder to use
- * @param string $type The type of $value
- * @return string generated placeholder
- */
- protected function _bindValue($value, $generator, $type)
- {
- $placeholder = $generator->placeholder('c');
- $generator->bind($placeholder, $value, $type);
- return $placeholder;
- }
- /**
- * Converts a traversable value into a set of placeholders generated by
- * $generator and separated by `,`
- *
- * @param array|\Traversable $value the value to flatten
- * @param \Cake\Database\ValueBinder $generator The value binder to use
- * @param string|array|null $type the type to cast values to
- * @return string
- */
- protected function _flattenValue($value, $generator, $type = 'string')
- {
- $parts = [];
- foreach ($this->_valueExpressions as $k => $v) {
- $parts[$k] = $v->sql($generator);
- unset($value[$k]);
- }
- if (!empty($value)) {
- $parts += $generator->generateManyNamed($value, $type);
- }
- return implode(',', $parts);
- }
- /**
- * Returns an array with the original $values in the first position
- * and all ExpressionInterface objects that could be found in the second
- * position.
- *
- * @param array|\Traversable $values The rows to insert
- * @return array
- */
- protected function _collectExpressions($values)
- {
- if ($values instanceof ExpressionInterface) {
- return [$values, []];
- }
- $expressions = $result = [];
- $isArray = is_array($values);
- if ($isArray) {
- $result = $values;
- }
- foreach ($values as $k => $v) {
- if ($v instanceof ExpressionInterface) {
- $expressions[$k] = $v;
- }
- if ($isArray) {
- $result[$k] = $v;
- }
- }
- return [$result, $expressions];
- }
- }
|