3 namespace Drupal\Core\Database\Query;
5 use Drupal\Core\Database\Connection;
6 use Drupal\Core\Database\InvalidQueryException;
9 * Generic class for a series of conditions in a query.
11 class Condition implements ConditionInterface, \Countable {
14 * Provides a map of condition operators to condition operator options.
16 protected static $conditionOperatorMap = [
17 'BETWEEN' => ['delimiter' => ' AND '],
18 'NOT BETWEEN' => ['delimiter' => ' AND '],
19 'IN' => ['delimiter' => ', ', 'prefix' => '(', 'postfix' => ')'],
20 'NOT IN' => ['delimiter' => ', ', 'prefix' => '(', 'postfix' => ')'],
21 'IS NULL' => ['use_value' => FALSE],
22 'IS NOT NULL' => ['use_value' => FALSE],
23 // Use backslash for escaping wildcard characters.
24 'LIKE' => ['postfix' => " ESCAPE '\\\\'"],
25 'NOT LIKE' => ['postfix' => " ESCAPE '\\\\'"],
26 // Exists expects an already bracketed subquery as right hand part. Do
27 // not define additional brackets.
30 // These ones are here for performance reasons.
39 * Array of conditions.
43 protected $conditions = [];
50 protected $arguments = [];
53 * Whether the conditions have been changed.
55 * TRUE if the condition has been changed since the last compile.
56 * FALSE if the condition has been compiled and not changed.
60 protected $changed = TRUE;
63 * The identifier of the query placeholder this condition has been compiled against.
65 protected $queryPlaceholderIdentifier;
68 * Contains the string version of the Condition.
72 protected $stringVersion;
75 * Constructs a Condition object.
77 * @param string $conjunction
78 * The operator to use to combine conditions: 'AND' or 'OR'.
80 public function __construct($conjunction) {
81 $this->conditions['#conjunction'] = $conjunction;
85 * Implements Countable::count().
87 * Returns the size of this conditional. The size of the conditional is the
88 * size of its conditional array minus one, because one element is the
91 public function count() {
92 return count($this->conditions) - 1;
98 public function condition($field, $value = NULL, $operator = '=') {
99 if (empty($operator)) {
102 if (empty($value) && is_array($value)) {
103 throw new InvalidQueryException(sprintf("Query condition '%s %s ()' cannot be empty.", $field, $operator));
106 $this->conditions[] = [
109 'operator' => $operator,
112 $this->changed = TRUE;
120 public function where($snippet, $args = []) {
121 $this->conditions[] = [
126 $this->changed = TRUE;
134 public function isNull($field) {
135 return $this->condition($field, NULL, 'IS NULL');
141 public function isNotNull($field) {
142 return $this->condition($field, NULL, 'IS NOT NULL');
148 public function exists(SelectInterface $select) {
149 return $this->condition('', $select, 'EXISTS');
155 public function notExists(SelectInterface $select) {
156 return $this->condition('', $select, 'NOT EXISTS');
162 public function &conditions() {
163 return $this->conditions;
169 public function arguments() {
170 // If the caller forgot to call compile() first, refuse to run.
171 if ($this->changed) {
174 return $this->arguments;
180 public function compile(Connection $connection, PlaceholderInterface $queryPlaceholder) {
181 // Re-compile if this condition changed or if we are compiled against a
182 // different query placeholder object.
183 if ($this->changed || isset($this->queryPlaceholderIdentifier) && ($this->queryPlaceholderIdentifier != $queryPlaceholder->uniqueIdentifier())) {
184 $this->queryPlaceholderIdentifier = $queryPlaceholder->uniqueIdentifier();
186 $condition_fragments = [];
189 $conditions = $this->conditions;
190 $conjunction = $conditions['#conjunction'];
191 unset($conditions['#conjunction']);
192 foreach ($conditions as $condition) {
194 if ($condition['field'] instanceof ConditionInterface) {
195 // Left hand part is a structured condition or a subquery. Compile,
196 // put brackets around it (if it is a query), and collect any
198 $condition['field']->compile($connection, $queryPlaceholder);
199 $field_fragment = (string) $condition['field'];
200 if ($condition['field'] instanceof SelectInterface) {
201 $field_fragment = '(' . $field_fragment . ')';
203 $arguments += $condition['field']->arguments();
204 // If the operator and value were not passed in to the
205 // @see ConditionInterface::condition() method (and thus have the
206 // default value as defined over there) it is assumed to be a valid
207 // condition on its own: ignore the operator and value parts.
208 $ignore_operator = $condition['operator'] === '=' && $condition['value'] === NULL;
210 elseif (!isset($condition['operator'])) {
211 // Left hand part is a literal string added with the
212 // @see ConditionInterface::where() method. Put brackets around
213 // the snippet and collect the arguments from the value part.
214 // Also ignore the operator and value parts.
215 $field_fragment = '(' . $condition['field'] . ')';
216 $arguments += $condition['value'];
217 $ignore_operator = TRUE;
220 // Left hand part is a normal field. Add it as is.
221 $field_fragment = $connection->escapeField($condition['field']);
222 $ignore_operator = FALSE;
226 if ($ignore_operator) {
227 $operator = ['operator' => '', 'use_value' => FALSE];
230 // Remove potentially dangerous characters.
231 // If something passed in an invalid character stop early, so we
232 // don't rely on a broken SQL statement when we would just replace
234 if (stripos($condition['operator'], 'UNION') !== FALSE || strpbrk($condition['operator'], '[-\'"();') !== FALSE) {
235 $this->changed = TRUE;
236 $this->arguments = [];
237 // Provide a string which will result into an empty query result.
238 $this->stringVersion = '( AND 1 = 0 )';
240 // Conceptually throwing an exception caused by user input is bad
241 // as you result into a WSOD, which depending on your webserver
242 // configuration can result into the assumption that your site is
244 // On top of that the database API relies on __toString() which
245 // does not allow to throw exceptions.
246 trigger_error('Invalid characters in query operator: ' . $condition['operator'], E_USER_ERROR);
250 // For simplicity, we convert all operators to a data structure to
251 // allow to specify a prefix, a delimiter and such. Find the
252 // associated data structure by first doing a database specific
253 // lookup, followed by a specification according to the SQL standard.
254 $operator = $connection->mapConditionOperator($condition['operator']);
255 if (!isset($operator)) {
256 $operator = $this->mapConditionOperator($condition['operator']);
258 $operator += ['operator' => $condition['operator']];
267 $operator_fragment = $operator['operator'];
270 $value_fragment = '';
271 if ($operator['use_value']) {
272 // For simplicity, we first convert to an array, so that we can handle
273 // the single and multi value cases the same.
274 if (!is_array($condition['value'])) {
275 if ($condition['value'] instanceof SelectInterface && ($operator['operator'] === 'IN' || $operator['operator'] === 'NOT IN')) {
276 // Special case: IN is followed by a single select query instead
277 // of a set of values: unset prefix and postfix to prevent double
279 $operator['prefix'] = '';
280 $operator['postfix'] = '';
282 $condition['value'] = [$condition['value']];
284 // Process all individual values.
285 $value_fragment = [];
286 foreach ($condition['value'] as $value) {
287 if ($value instanceof SelectInterface) {
288 // Right hand part is a subquery. Compile, put brackets around it
289 // and collect any arguments.
290 $value->compile($connection, $queryPlaceholder);
291 $value_fragment[] = '(' . (string) $value . ')';
292 $arguments += $value->arguments();
295 // Right hand part is a normal value. Replace the value with a
296 // placeholder and add the value as an argument.
297 $placeholder = ':db_condition_placeholder_' . $queryPlaceholder->nextPlaceholder();
298 $value_fragment[] = $placeholder;
299 $arguments[$placeholder] = $value;
302 $value_fragment = $operator['prefix'] . implode($operator['delimiter'], $value_fragment) . $operator['postfix'];
305 // Concatenate the left hand part, operator and right hand part.
306 $condition_fragments[] = trim(implode(' ', [$field_fragment, $operator_fragment, $value_fragment]));
309 // Concatenate all conditions using the conjunction and brackets around
310 // the individual conditions to assure the proper evaluation order.
311 $this->stringVersion = count($condition_fragments) > 1 ? '(' . implode(") $conjunction (", $condition_fragments) . ')' : implode($condition_fragments);
312 $this->arguments = $arguments;
313 $this->changed = FALSE;
320 public function compiled() {
321 return !$this->changed;
325 * Implements PHP magic __toString method to convert the conditions to string.
328 * A string version of the conditions.
330 public function __toString() {
331 // If the caller forgot to call compile() first, refuse to run.
332 if ($this->changed) {
335 return $this->stringVersion;
339 * PHP magic __clone() method.
341 * Only copies fields that implement Drupal\Core\Database\Query\ConditionInterface. Also sets
342 * $this->changed to TRUE.
344 public function __clone() {
345 $this->changed = TRUE;
346 foreach ($this->conditions as $key => $condition) {
347 if ($key !== '#conjunction') {
348 if ($condition['field'] instanceof ConditionInterface) {
349 $this->conditions[$key]['field'] = clone($condition['field']);
351 if ($condition['value'] instanceof SelectInterface) {
352 $this->conditions[$key]['value'] = clone($condition['value']);
359 * Gets any special processing requirements for the condition operator.
361 * Some condition types require special processing, such as IN, because
362 * the value data they pass in is not a simple value. This is a simple
363 * overridable lookup function.
365 * @param string $operator
366 * The condition operator, such as "IN", "BETWEEN", etc. Case-sensitive.
369 * The extra handling directives for the specified operator or an empty
370 * array if there are no extra handling directives.
372 protected function mapConditionOperator($operator) {
373 if (isset(static::$conditionOperatorMap[$operator])) {
374 $return = static::$conditionOperatorMap[$operator];
377 // We need to upper case because PHP index matches are case sensitive but
378 // do not need the more expensive Unicode::strtoupper() because SQL statements are ASCII.
379 $operator = strtoupper($operator);
380 $return = isset(static::$conditionOperatorMap[$operator]) ? static::$conditionOperatorMap[$operator] : [];
383 $return += ['operator' => $operator];
391 public function conditionGroupFactory($conjunction = 'AND') {
392 return new Condition($conjunction);
398 public function andConditionGroup() {
399 return $this->conditionGroupFactory('AND');
405 public function orConditionGroup() {
406 return $this->conditionGroupFactory('OR');