*/ protected $_valueExpressions = []; /** * Constructor * * @param \Cake\Database\ExpressionInterface|string $field the field name to compare to a value * @param mixed $value The value to be used in comparison * @param string|null $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, ?string $type = null, string $operator = '=') { $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): void { $value = $this->_castToExpression($value, $this->_type); $isMultiple = $this->_type && strpos($this->_type, '[]') !== false; if ($isMultiple) { [$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(string $operator): void { $this->_operator = $operator; } /** * Returns the operator used for comparison * * @return string */ public function getOperator(): string { return $this->_operator; } /** * @inheritDoc */ public function sql(ValueBinder $binder): string { /** @var \Cake\Database\ExpressionInterface|string $field */ $field = $this->_field; if ($field instanceof ExpressionInterface) { $field = $field->sql($binder); } if ($this->_value instanceof IdentifierExpression) { $template = '%s %s %s'; $value = $this->_value->sql($binder); } elseif ($this->_value instanceof ExpressionInterface) { $template = '%s %s (%s)'; $value = $this->_value->sql($binder); } else { [$template, $value] = $this->_stringExpression($binder); } return sprintf($template, $field, $this->_operator, $value); } /** * @inheritDoc */ public function traverse(Closure $callback) { if ($this->_field instanceof ExpressionInterface) { $callback($this->_field); $this->_field->traverse($callback); } if ($this->_value instanceof ExpressionInterface) { $callback($this->_value); $this->_value->traverse($callback); } foreach ($this->_valueExpressions as $v) { $callback($v); $v->traverse($callback); } return $this; } /** * 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 $binder * * @param \Cake\Database\ValueBinder $binder The value binder to use. * @return array First position containing the template and the second a placeholder */ protected function _stringExpression(ValueBinder $binder): array { $template = '%s '; if ($this->_field instanceof ExpressionInterface && !$this->_field instanceof IdentifierExpression) { $template = '(%s) '; } if ($this->_isMultiple) { $template .= '%s (%s)'; $type = $this->_type; if ($type !== null) { $type = str_replace('[]', '', $type); } $value = $this->_flattenValue($this->_value, $binder, $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($binder) : $this->_field; /** @psalm-suppress PossiblyInvalidCast */ throw new DatabaseException( "Impossible to generate condition with empty list of values for field ($field)" ); } } else { $template .= '%s %s'; $value = $this->_bindValue($this->_value, $binder, $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 $binder The value binder to use * @param string|null $type The type of $value * @return string generated placeholder */ protected function _bindValue($value, ValueBinder $binder, ?string $type = null): string { $placeholder = $binder->placeholder('c'); $binder->bind($placeholder, $value, $type); return $placeholder; } /** * Converts a traversable value into a set of placeholders generated by * $binder and separated by `,` * * @param iterable $value the value to flatten * @param \Cake\Database\ValueBinder $binder The value binder to use * @param string|null $type the type to cast values to * @return string */ protected function _flattenValue(iterable $value, ValueBinder $binder, ?string $type = null): string { $parts = []; if (is_array($value)) { foreach ($this->_valueExpressions as $k => $v) { $parts[$k] = $v->sql($binder); unset($value[$k]); } } if (!empty($value)) { $parts += $binder->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 \Cake\Database\ExpressionInterface|iterable $values The rows to insert * @return array */ protected function _collectExpressions($values): array { if ($values instanceof ExpressionInterface) { return [$values, []]; } $expressions = $result = []; $isArray = is_array($values); if ($isArray) { /** @var array $result */ $result = $values; } foreach ($values as $k => $v) { if ($v instanceof ExpressionInterface) { $expressions[$k] = $v; } if ($isArray) { $result[$k] = $v; } } return [$result, $expressions]; } } // phpcs:disable class_alias( 'Cake\Database\Expression\ComparisonExpression', 'Cake\Database\Expression\Comparison' ); // phpcs:enable