You are here

public function Condition::compile in Drupal 9

Same name in this branch
  1. 9 core/lib/Drupal/Core/Database/Query/Condition.php \Drupal\Core\Database\Query\Condition::compile()
  2. 9 core/lib/Drupal/Core/Config/Entity/Query/Condition.php \Drupal\Core\Config\Entity\Query\Condition::compile()
  3. 9 core/lib/Drupal/Core/Entity/Query/Sql/Condition.php \Drupal\Core\Entity\Query\Sql\Condition::compile()
  4. 9 core/lib/Drupal/Core/Entity/Query/Null/Condition.php \Drupal\Core\Entity\Query\Null\Condition::compile()
Same name and namespace in other branches
  1. 8 core/lib/Drupal/Core/Database/Query/Condition.php \Drupal\Core\Database\Query\Condition::compile()

Compiles the saved conditions for later retrieval.

This method does not return anything, but simply prepares data to be retrieved via __toString() and arguments().

Parameters

$connection: The database connection for which to compile the conditionals.

$queryPlaceholder: The query this condition belongs to. If not given, the current query is used.

Overrides ConditionInterface::compile

File

core/lib/Drupal/Core/Database/Query/Condition.php, line 200

Class

Condition
Generic class for a series of conditions in a query.

Namespace

Drupal\Core\Database\Query

Code

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 WSOD, 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;
  }
}