Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
Total | |
0.00% |
0 / 1 |
|
38.89% |
7 / 18 |
CRAP | |
76.84% |
73 / 95 |
Condition | |
0.00% |
0 / 1 |
|
38.89% |
7 / 18 |
65.96 | |
76.84% |
73 / 95 |
__construct | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
count | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
condition | |
0.00% |
0 / 1 |
4.13 | |
80.00% |
8 / 10 |
|||
where | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 5 |
|||
isNull | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
isNotNull | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
exists | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
notExists | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
conditions | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
arguments | |
0.00% |
0 / 1 |
2.15 | |
66.67% |
2 / 3 |
|||
compile | |
0.00% |
0 / 1 |
15.74 | |
85.11% |
40 / 47 |
|||
compiled | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
__toString | |
0.00% |
0 / 1 |
2.15 | |
66.67% |
2 / 3 |
|||
__clone | |
100.00% |
1 / 1 |
5 | |
100.00% |
8 / 8 |
|||
mapConditionOperator | |
100.00% |
1 / 1 |
3 | |
100.00% |
7 / 7 |
|||
conditionGroupFactory | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
andConditionGroup | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
orConditionGroup | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
<?php | |
/** | |
* @file | |
* Contains \Drupal\Core\Database\Query\Condition. | |
*/ | |
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 { | |
/** | |
* Array of conditions. | |
* | |
* @var array | |
*/ | |
protected $conditions = array(); | |
/** | |
* Array of arguments. | |
* | |
* @var array | |
*/ | |
protected $arguments = array(); | |
/** | |
* 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. | |
*/ | |
protected $queryPlaceholderIdentifier; | |
/** | |
* 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)); | |
} | |
$this->conditions[] = array( | |
'field' => $field, | |
'value' => $value, | |
'operator' => $operator, | |
); | |
$this->changed = TRUE; | |
return $this; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function where($snippet, $args = array()) { | |
$this->conditions[] = array( | |
'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 &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 = array(); | |
$arguments = array(); | |
$conditions = $this->conditions; | |
$conjunction = $conditions['#conjunction']; | |
unset($conditions['#conjunction']); | |
foreach ($conditions as $condition) { | |
if (empty($condition['operator'])) { | |
// This condition is a literal string, so let it through as is. | |
$condition_fragments[] = ' (' . $condition['field'] . ') '; | |
$arguments += $condition['value']; | |
} | |
else { | |
// It's a structured condition, so parse it out accordingly. | |
// Note that $condition['field'] will only be an object for a dependent | |
// DatabaseCondition object, not for a dependent subquery. | |
if ($condition['field'] instanceof ConditionInterface) { | |
// Compile the sub-condition recursively and add it to the list. | |
$condition['field']->compile($connection, $queryPlaceholder); | |
$condition_fragments[] = '(' . (string) $condition['field'] . ')'; | |
$arguments += $condition['field']->arguments(); | |
} | |
else { | |
// For simplicity, we treat all operators as the same data structure. | |
// In the typical degenerate case, this won't get changed. | |
$operator_defaults = array( | |
'prefix' => '', | |
'postfix' => '', | |
'delimiter' => '', | |
'operator' => $condition['operator'], | |
'use_value' => TRUE, | |
); | |
// 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; | |
} | |
$operator = $connection->mapConditionOperator($condition['operator']); | |
if (!isset($operator)) { | |
$operator = $this->mapConditionOperator($condition['operator']); | |
} | |
$operator += $operator_defaults; | |
$placeholders = array(); | |
if ($condition['value'] instanceof SelectInterface) { | |
$condition['value']->compile($connection, $queryPlaceholder); | |
$placeholders[] = (string) $condition['value']; | |
$arguments += $condition['value']->arguments(); | |
// Subqueries are the actual value of the operator, we don't | |
// need to add another below. | |
$operator['use_value'] = FALSE; | |
} | |
// We assume that if there is a delimiter, then the value is an | |
// array. If not, it is a scalar. For simplicity, we first convert | |
// up to an array so that we can build the placeholders in the same way. | |
elseif (!$operator['delimiter'] && !is_array($condition['value'])) { | |
$condition['value'] = array($condition['value']); | |
} | |
if ($operator['use_value']) { | |
foreach ($condition['value'] as $value) { | |
$placeholder = ':db_condition_placeholder_' . $queryPlaceholder->nextPlaceholder(); | |
$arguments[$placeholder] = $value; | |
$placeholders[] = $placeholder; | |
} | |
} | |
$condition_fragments[] = ' (' . $connection->escapeField($condition['field']) . ' ' . $operator['operator'] . ' ' . $operator['prefix'] . implode($operator['delimiter'], $placeholders) . $operator['postfix'] . ') '; | |
} | |
} | |
} | |
$this->changed = FALSE; | |
$this->stringVersion = implode($conjunction, $condition_fragments); | |
$this->arguments = $arguments; | |
} | |
} | |
/** | |
* {@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. | |
*/ | |
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 $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) { | |
// $specials does not use drupal_static as its value never changes. | |
static $specials = array( | |
'BETWEEN' => array('delimiter' => ' AND '), | |
'IN' => array('delimiter' => ', ', 'prefix' => ' (', 'postfix' => ')'), | |
'NOT IN' => array('delimiter' => ', ', 'prefix' => ' (', 'postfix' => ')'), | |
'EXISTS' => array('prefix' => ' (', 'postfix' => ')'), | |
'NOT EXISTS' => array('prefix' => ' (', 'postfix' => ')'), | |
'IS NULL' => array('use_value' => FALSE), | |
'IS NOT NULL' => array('use_value' => FALSE), | |
// Use backslash for escaping wildcard characters. | |
'LIKE' => array('postfix' => " ESCAPE '\\\\'"), | |
'NOT LIKE' => array('postfix' => " ESCAPE '\\\\'"), | |
// These ones are here for performance reasons. | |
'=' => array(), | |
'<' => array(), | |
'>' => array(), | |
'>=' => array(), | |
'<=' => array(), | |
); | |
if (isset($specials[$operator])) { | |
$return = $specials[$operator]; | |
} | |
else { | |
// We need to upper case because PHP index matches are case sensitive but | |
// do not need the more expensive Unicode::strtoupper() because SQL statements are ASCII. | |
$operator = strtoupper($operator); | |
$return = isset($specials[$operator]) ? $specials[$operator] : array(); | |
} | |
$return += array('operator' => $operator); | |
return $return; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function conditionGroupFactory($conjunction = 'AND') { | |
return new Condition($conjunction); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function andConditionGroup() { | |
return $this->conditionGroupFactory('AND'); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function orConditionGroup() { | |
return $this->conditionGroupFactory('OR'); | |
} | |
} |