Condition.php

Same filename in this branch
  1. 10 core/tests/fixtures/database_drivers/module/core_fake/src/Driver/Database/CoreFakeWithAllCustomClasses/Condition.php
  2. 10 core/lib/Drupal/Core/Config/Entity/Query/Condition.php
  3. 10 core/lib/Drupal/Core/Entity/Query/Sql/Condition.php
  4. 10 core/lib/Drupal/Core/Entity/Query/Sql/pgsql/Condition.php
  5. 10 core/lib/Drupal/Core/Entity/Query/Null/Condition.php
  6. 10 core/lib/Drupal/Core/Entity/KeyValueStore/Query/Condition.php
  7. 10 core/lib/Drupal/Core/Condition/Attribute/Condition.php
  8. 10 core/lib/Drupal/Core/Condition/Annotation/Condition.php
Same filename in other branches
  1. 9 core/tests/fixtures/database_drivers/module/corefake/src/Driver/Database/corefakeWithAllCustomClasses/Condition.php
  2. 9 core/lib/Drupal/Core/Config/Entity/Query/Condition.php
  3. 9 core/lib/Drupal/Core/Database/Query/Condition.php
  4. 9 core/lib/Drupal/Core/Entity/Query/Sql/Condition.php
  5. 9 core/lib/Drupal/Core/Entity/Query/Sql/pgsql/Condition.php
  6. 9 core/lib/Drupal/Core/Entity/Query/Null/Condition.php
  7. 9 core/lib/Drupal/Core/Entity/KeyValueStore/Query/Condition.php
  8. 9 core/lib/Drupal/Core/Condition/Annotation/Condition.php
  9. 8.9.x core/lib/Drupal/Core/Config/Entity/Query/Condition.php
  10. 8.9.x core/lib/Drupal/Core/Database/Query/Condition.php
  11. 8.9.x core/lib/Drupal/Core/Entity/Query/Sql/Condition.php
  12. 8.9.x core/lib/Drupal/Core/Entity/Query/Sql/pgsql/Condition.php
  13. 8.9.x core/lib/Drupal/Core/Entity/Query/Null/Condition.php
  14. 8.9.x core/lib/Drupal/Core/Entity/KeyValueStore/Query/Condition.php
  15. 8.9.x core/lib/Drupal/Core/Condition/Annotation/Condition.php
  16. 11.x core/tests/fixtures/database_drivers/module/core_fake/src/Driver/Database/CoreFakeWithAllCustomClasses/Condition.php
  17. 11.x core/lib/Drupal/Core/Config/Entity/Query/Condition.php
  18. 11.x core/lib/Drupal/Core/Database/Query/Condition.php
  19. 11.x core/lib/Drupal/Core/Entity/Query/Sql/Condition.php
  20. 11.x core/lib/Drupal/Core/Entity/Query/Sql/pgsql/Condition.php
  21. 11.x core/lib/Drupal/Core/Entity/Query/Null/Condition.php
  22. 11.x core/lib/Drupal/Core/Entity/KeyValueStore/Query/Condition.php
  23. 11.x core/lib/Drupal/Core/Condition/Attribute/Condition.php
  24. 11.x core/lib/Drupal/Core/Condition/Annotation/Condition.php

Namespace

Drupal\Core\Database\Query

File

core/lib/Drupal/Core/Database/Query/Condition.php

View source
<?php

namespace Drupal\Core\Database\Query;

use Drupal\Core\Database\Connection;
use Drupal\Core\Database\InvalidQueryException;

/**
 * Generic class for a series of conditions in a query.
 */
class Condition implements ConditionInterface, \Countable {
    
    /**
     * Provides a map of condition operators to condition operator options.
     */
    protected static $conditionOperatorMap = [
        'BETWEEN' => [
            'delimiter' => ' AND ',
        ],
        'NOT BETWEEN' => [
            'delimiter' => ' AND ',
        ],
        'IN' => [
            'delimiter' => ', ',
            'prefix' => '(',
            'postfix' => ')',
        ],
        'NOT IN' => [
            'delimiter' => ', ',
            'prefix' => '(',
            'postfix' => ')',
        ],
        'IS NULL' => [
            'use_value' => FALSE,
        ],
        'IS NOT NULL' => [
            'use_value' => FALSE,
        ],
        // Use backslash for escaping wildcard characters.
'LIKE' => [
            'postfix' => " ESCAPE '\\\\'",
        ],
        'NOT LIKE' => [
            'postfix' => " ESCAPE '\\\\'",
        ],
        // Exists expects an already bracketed subquery as right hand part. Do
        // not define additional brackets.
'EXISTS' => [],
        'NOT EXISTS' => [],
        // These ones are here for performance reasons.
'=' => [],
        '<' => [],
        '>' => [],
        '>=' => [],
        '<=' => [],
    ];
    
    /**
     * Array of conditions.
     *
     * @var array
     */
    protected $conditions = [];
    
    /**
     * Array of arguments.
     *
     * @var array
     */
    protected $arguments = [];
    
    /**
     * Whether the conditions have been changed.
     *
     * TRUE if the condition has been changed since the last compile.
     * FALSE if the condition has been compiled and not changed.
     *
     * @var bool
     */
    protected $changed = TRUE;
    
    /**
     * The identifier of the query placeholder this condition has been compiled against.
     *
     * @var string
     */
    protected $queryPlaceholderIdentifier;
    
    /**
     * Contains the string version of the Condition.
     *
     * @var string
     */
    protected $stringVersion;
    
    /**
     * Constructs a Condition object.
     *
     * @param string $conjunction
     *   The operator to use to combine conditions: 'AND' or 'OR'.
     */
    public function __construct($conjunction) {
        $this->conditions['#conjunction'] = $conjunction;
    }
    
    /**
     * Implements Countable::count().
     *
     * Returns the size of this conditional. The size of the conditional is the
     * size of its conditional array minus one, because one element is the
     * conjunction.
     */
    public function count() {
        return count($this->conditions) - 1;
    }
    
    /**
     * {@inheritdoc}
     */
    public function condition($field, $value = NULL, $operator = '=') {
        if (empty($operator)) {
            $operator = '=';
        }
        if (empty($value) && is_array($value)) {
            throw new InvalidQueryException(sprintf("Query condition '%s %s ()' cannot be empty.", $field, $operator));
        }
        if (is_array($value) && in_array($operator, [
            '=',
            '<',
            '>',
            '<=',
            '>=',
            'IS NULL',
            'IS NOT NULL',
        ], TRUE)) {
            if (count($value) > 1) {
                $value = implode(', ', $value);
                throw new InvalidQueryException(sprintf("Query condition '%s %s %s' must have an array compatible operator.", $field, $operator, $value));
            }
            else {
                $value = $value[0];
                @trigger_error('Calling ' . __METHOD__ . '() without an array compatible operator is deprecated in drupal:10.1.0 and will be required in drupal:11.0.0. See https://www.drupal.org/node/3350985', E_USER_DEPRECATED);
            }
        }
        $this->conditions[] = [
            'field' => $field,
            'value' => $value,
            'operator' => $operator,
        ];
        $this->changed = TRUE;
        return $this;
    }
    
    /**
     * {@inheritdoc}
     */
    public function where($snippet, $args = []) {
        $this->conditions[] = [
            'field' => $snippet,
            'value' => $args,
            'operator' => NULL,
        ];
        $this->changed = TRUE;
        return $this;
    }
    
    /**
     * {@inheritdoc}
     */
    public function isNull($field) {
        return $this->condition($field, NULL, 'IS NULL');
    }
    
    /**
     * {@inheritdoc}
     */
    public function isNotNull($field) {
        return $this->condition($field, NULL, 'IS NOT NULL');
    }
    
    /**
     * {@inheritdoc}
     */
    public function exists(SelectInterface $select) {
        return $this->condition('', $select, 'EXISTS');
    }
    
    /**
     * {@inheritdoc}
     */
    public function notExists(SelectInterface $select) {
        return $this->condition('', $select, 'NOT EXISTS');
    }
    
    /**
     * {@inheritdoc}
     */
    public function alwaysFalse() {
        return $this->where('1 = 0');
    }
    
    /**
     * {@inheritdoc}
     */
    public function &conditions() {
        return $this->conditions;
    }
    
    /**
     * {@inheritdoc}
     */
    public function arguments() {
        // If the caller forgot to call compile() first, refuse to run.
        if ($this->changed) {
            return NULL;
        }
        return $this->arguments;
    }
    
    /**
     * {@inheritdoc}
     */
    public function compile(Connection $connection, PlaceholderInterface $queryPlaceholder) {
        // Re-compile if this condition changed or if we are compiled against a
        // different query placeholder object.
        if ($this->changed || isset($this->queryPlaceholderIdentifier) && $this->queryPlaceholderIdentifier != $queryPlaceholder->uniqueIdentifier()) {
            $this->queryPlaceholderIdentifier = $queryPlaceholder->uniqueIdentifier();
            $condition_fragments = [];
            $arguments = [];
            $conditions = $this->conditions;
            $conjunction = $conditions['#conjunction'];
            unset($conditions['#conjunction']);
            foreach ($conditions as $condition) {
                // Process field.
                if ($condition['field'] instanceof ConditionInterface) {
                    // Left hand part is a structured condition or a subquery. Compile,
                    // put brackets around it (if it is a query), and collect any
                    // arguments.
                    $condition['field']->compile($connection, $queryPlaceholder);
                    $field_fragment = (string) $condition['field'];
                    if ($condition['field'] instanceof SelectInterface) {
                        $field_fragment = '(' . $field_fragment . ')';
                    }
                    $arguments += $condition['field']->arguments();
                    // If the operator and value were not passed in to the
                    // @see ConditionInterface::condition() method (and thus have the
                    // default value as defined over there) it is assumed to be a valid
                    // condition on its own: ignore the operator and value parts.
                    $ignore_operator = $condition['operator'] === '=' && $condition['value'] === NULL;
                }
                elseif (!isset($condition['operator'])) {
                    // Left hand part is a literal string added with the
                    // @see ConditionInterface::where() method. Put brackets around
                    // the snippet and collect the arguments from the value part.
                    // Also ignore the operator and value parts.
                    $field_fragment = '(' . $condition['field'] . ')';
                    $arguments += $condition['value'];
                    $ignore_operator = TRUE;
                }
                else {
                    // Left hand part is a normal field. Add it as is.
                    $field_fragment = $connection->escapeField($condition['field']);
                    $ignore_operator = FALSE;
                }
                // Process operator.
                if ($ignore_operator) {
                    $operator = [
                        'operator' => '',
                        'use_value' => FALSE,
                    ];
                }
                else {
                    // Remove potentially dangerous characters.
                    // If something passed in an invalid character stop early, so we
                    // don't rely on a broken SQL statement when we would just replace
                    // those characters.
                    if (stripos($condition['operator'], 'UNION') !== FALSE || strpbrk($condition['operator'], '[-\'"();') !== FALSE) {
                        $this->changed = TRUE;
                        $this->arguments = [];
                        // Provide a string which will result into an empty query result.
                        $this->stringVersion = '( AND 1 = 0 )';
                        // Conceptually throwing an exception caused by user input is bad
                        // as you result into a 'white screen of death', which depending on
                        // your webserver configuration can result into the assumption that
                        // your site is broken.
                        // On top of that the database API relies on __toString() which
                        // does not allow to throw exceptions.
                        trigger_error('Invalid characters in query operator: ' . $condition['operator'], E_USER_ERROR);
                        return;
                    }
                    // For simplicity, we convert all operators to a data structure to
                    // allow to specify a prefix, a delimiter and such. Find the
                    // associated data structure by first doing a database specific
                    // lookup, followed by a specification according to the SQL standard.
                    $operator = $connection->mapConditionOperator($condition['operator']);
                    if (!isset($operator)) {
                        $operator = $this->mapConditionOperator($condition['operator']);
                    }
                    $operator += [
                        'operator' => $condition['operator'],
                    ];
                }
                // Add defaults.
                $operator += [
                    'prefix' => '',
                    'postfix' => '',
                    'delimiter' => '',
                    'use_value' => TRUE,
                ];
                $operator_fragment = $operator['operator'];
                // Process value.
                $value_fragment = '';
                if ($operator['use_value']) {
                    // For simplicity, we first convert to an array, so that we can handle
                    // the single and multi value cases the same.
                    if (!is_array($condition['value'])) {
                        if ($condition['value'] instanceof SelectInterface && ($operator['operator'] === 'IN' || $operator['operator'] === 'NOT IN')) {
                            // Special case: IN is followed by a single select query instead
                            // of a set of values: unset prefix and postfix to prevent double
                            // brackets.
                            $operator['prefix'] = '';
                            $operator['postfix'] = '';
                        }
                        $condition['value'] = [
                            $condition['value'],
                        ];
                    }
                    // Process all individual values.
                    $value_fragment = [];
                    foreach ($condition['value'] as $value) {
                        if ($value instanceof SelectInterface) {
                            // Right hand part is a subquery. Compile, put brackets around it
                            // and collect any arguments.
                            $value->compile($connection, $queryPlaceholder);
                            $value_fragment[] = '(' . (string) $value . ')';
                            $arguments += $value->arguments();
                        }
                        else {
                            // Right hand part is a normal value. Replace the value with a
                            // placeholder and add the value as an argument.
                            $placeholder = ':db_condition_placeholder_' . $queryPlaceholder->nextPlaceholder();
                            $value_fragment[] = $placeholder;
                            $arguments[$placeholder] = $value;
                        }
                    }
                    $value_fragment = $operator['prefix'] . implode($operator['delimiter'], $value_fragment) . $operator['postfix'];
                }
                // Concatenate the left hand part, operator and right hand part.
                $condition_fragments[] = trim(implode(' ', [
                    $field_fragment,
                    $operator_fragment,
                    $value_fragment,
                ]));
            }
            // Concatenate all conditions using the conjunction and brackets around
            // the individual conditions to assure the proper evaluation order.
            $this->stringVersion = count($condition_fragments) > 1 ? '(' . implode(") {$conjunction} (", $condition_fragments) . ')' : implode($condition_fragments);
            $this->arguments = $arguments;
            $this->changed = FALSE;
        }
    }
    
    /**
     * {@inheritdoc}
     */
    public function compiled() {
        return !$this->changed;
    }
    
    /**
     * Implements PHP magic __toString method to convert the conditions to string.
     *
     * @return string
     *   A string version of the conditions.
     */
    public function __toString() {
        // If the caller forgot to call compile() first, refuse to run.
        if ($this->changed) {
            return '';
        }
        return $this->stringVersion;
    }
    
    /**
     * PHP magic __clone() method.
     *
     * Only copies fields that implement Drupal\Core\Database\Query\ConditionInterface. Also sets
     * $this->changed to TRUE.
     */
    public function __clone() {
        $this->changed = TRUE;
        foreach ($this->conditions as $key => $condition) {
            if ($key !== '#conjunction') {
                if ($condition['field'] instanceof ConditionInterface) {
                    $this->conditions[$key]['field'] = clone $condition['field'];
                }
                if ($condition['value'] instanceof SelectInterface) {
                    $this->conditions[$key]['value'] = clone $condition['value'];
                }
            }
        }
    }
    
    /**
     * Gets any special processing requirements for the condition operator.
     *
     * Some condition types require special processing, such as IN, because
     * the value data they pass in is not a simple value. This is a simple
     * overridable lookup function.
     *
     * @param string $operator
     *   The condition operator, such as "IN", "BETWEEN", etc. Case-sensitive.
     *
     * @return array
     *   The extra handling directives for the specified operator or an empty
     *   array if there are no extra handling directives.
     */
    protected function mapConditionOperator($operator) {
        if (isset(static::$conditionOperatorMap[$operator])) {
            $return = static::$conditionOperatorMap[$operator];
        }
        else {
            // We need to upper case because PHP index matches are case sensitive but
            // do not need the more expensive mb_strtoupper() because SQL statements
            // are ASCII.
            $operator = strtoupper($operator);
            $return = static::$conditionOperatorMap[$operator] ?? [];
        }
        $return += [
            'operator' => $operator,
        ];
        return $return;
    }
    
    /**
     * {@inheritdoc}
     */
    public function conditionGroupFactory($conjunction = 'AND') {
        return new static($conjunction);
    }
    
    /**
     * {@inheritdoc}
     */
    public function andConditionGroup() {
        return $this->conditionGroupFactory('AND');
    }
    
    /**
     * {@inheritdoc}
     */
    public function orConditionGroup() {
        return $this->conditionGroupFactory('OR');
    }

}

Classes

Title Deprecated Summary
Condition Generic class for a series of conditions in a query.

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.