Condition.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  1. <?php
  2. namespace Drupal\Core\Database\Query;
  3. use Drupal\Core\Database\Connection;
  4. use Drupal\Core\Database\InvalidQueryException;
  5. /**
  6. * Generic class for a series of conditions in a query.
  7. */
  8. class Condition implements ConditionInterface, \Countable {
  9. /**
  10. * Provides a map of condition operators to condition operator options.
  11. */
  12. protected static $conditionOperatorMap = [
  13. 'BETWEEN' => ['delimiter' => ' AND '],
  14. 'NOT BETWEEN' => ['delimiter' => ' AND '],
  15. 'IN' => ['delimiter' => ', ', 'prefix' => '(', 'postfix' => ')'],
  16. 'NOT IN' => ['delimiter' => ', ', 'prefix' => '(', 'postfix' => ')'],
  17. 'IS NULL' => ['use_value' => FALSE],
  18. 'IS NOT NULL' => ['use_value' => FALSE],
  19. // Use backslash for escaping wildcard characters.
  20. 'LIKE' => ['postfix' => " ESCAPE '\\\\'"],
  21. 'NOT LIKE' => ['postfix' => " ESCAPE '\\\\'"],
  22. // Exists expects an already bracketed subquery as right hand part. Do
  23. // not define additional brackets.
  24. 'EXISTS' => [],
  25. 'NOT EXISTS' => [],
  26. // These ones are here for performance reasons.
  27. '=' => [],
  28. '<' => [],
  29. '>' => [],
  30. '>=' => [],
  31. '<=' => [],
  32. ];
  33. /**
  34. * Array of conditions.
  35. *
  36. * @var array
  37. */
  38. protected $conditions = [];
  39. /**
  40. * Array of arguments.
  41. *
  42. * @var array
  43. */
  44. protected $arguments = [];
  45. /**
  46. * Whether the conditions have been changed.
  47. *
  48. * TRUE if the condition has been changed since the last compile.
  49. * FALSE if the condition has been compiled and not changed.
  50. *
  51. * @var bool
  52. */
  53. protected $changed = TRUE;
  54. /**
  55. * The identifier of the query placeholder this condition has been compiled against.
  56. *
  57. * @var string
  58. */
  59. protected $queryPlaceholderIdentifier;
  60. /**
  61. * Contains the string version of the Condition.
  62. *
  63. * @var string
  64. */
  65. protected $stringVersion;
  66. /**
  67. * Constructs a Condition object.
  68. *
  69. * @param string $conjunction
  70. * The operator to use to combine conditions: 'AND' or 'OR'.
  71. */
  72. public function __construct($conjunction) {
  73. $this->conditions['#conjunction'] = $conjunction;
  74. }
  75. /**
  76. * Implements Countable::count().
  77. *
  78. * Returns the size of this conditional. The size of the conditional is the
  79. * size of its conditional array minus one, because one element is the
  80. * conjunction.
  81. */
  82. public function count() {
  83. return count($this->conditions) - 1;
  84. }
  85. /**
  86. * {@inheritdoc}
  87. */
  88. public function condition($field, $value = NULL, $operator = '=') {
  89. if (empty($operator)) {
  90. $operator = '=';
  91. }
  92. if (empty($value) && is_array($value)) {
  93. throw new InvalidQueryException(sprintf("Query condition '%s %s ()' cannot be empty.", $field, $operator));
  94. }
  95. $this->conditions[] = [
  96. 'field' => $field,
  97. 'value' => $value,
  98. 'operator' => $operator,
  99. ];
  100. $this->changed = TRUE;
  101. return $this;
  102. }
  103. /**
  104. * {@inheritdoc}
  105. */
  106. public function where($snippet, $args = []) {
  107. $this->conditions[] = [
  108. 'field' => $snippet,
  109. 'value' => $args,
  110. 'operator' => NULL,
  111. ];
  112. $this->changed = TRUE;
  113. return $this;
  114. }
  115. /**
  116. * {@inheritdoc}
  117. */
  118. public function isNull($field) {
  119. return $this->condition($field, NULL, 'IS NULL');
  120. }
  121. /**
  122. * {@inheritdoc}
  123. */
  124. public function isNotNull($field) {
  125. return $this->condition($field, NULL, 'IS NOT NULL');
  126. }
  127. /**
  128. * {@inheritdoc}
  129. */
  130. public function exists(SelectInterface $select) {
  131. return $this->condition('', $select, 'EXISTS');
  132. }
  133. /**
  134. * {@inheritdoc}
  135. */
  136. public function notExists(SelectInterface $select) {
  137. return $this->condition('', $select, 'NOT EXISTS');
  138. }
  139. /**
  140. * {@inheritdoc}
  141. */
  142. public function alwaysFalse() {
  143. return $this->where('1 = 0');
  144. }
  145. /**
  146. * {@inheritdoc}
  147. */
  148. public function &conditions() {
  149. return $this->conditions;
  150. }
  151. /**
  152. * {@inheritdoc}
  153. */
  154. public function arguments() {
  155. // If the caller forgot to call compile() first, refuse to run.
  156. if ($this->changed) {
  157. return NULL;
  158. }
  159. return $this->arguments;
  160. }
  161. /**
  162. * {@inheritdoc}
  163. */
  164. public function compile(Connection $connection, PlaceholderInterface $queryPlaceholder) {
  165. // Re-compile if this condition changed or if we are compiled against a
  166. // different query placeholder object.
  167. if ($this->changed || isset($this->queryPlaceholderIdentifier) && ($this->queryPlaceholderIdentifier != $queryPlaceholder->uniqueIdentifier())) {
  168. $this->queryPlaceholderIdentifier = $queryPlaceholder->uniqueIdentifier();
  169. $condition_fragments = [];
  170. $arguments = [];
  171. $conditions = $this->conditions;
  172. $conjunction = $conditions['#conjunction'];
  173. unset($conditions['#conjunction']);
  174. foreach ($conditions as $condition) {
  175. // Process field.
  176. if ($condition['field'] instanceof ConditionInterface) {
  177. // Left hand part is a structured condition or a subquery. Compile,
  178. // put brackets around it (if it is a query), and collect any
  179. // arguments.
  180. $condition['field']->compile($connection, $queryPlaceholder);
  181. $field_fragment = (string) $condition['field'];
  182. if ($condition['field'] instanceof SelectInterface) {
  183. $field_fragment = '(' . $field_fragment . ')';
  184. }
  185. $arguments += $condition['field']->arguments();
  186. // If the operator and value were not passed in to the
  187. // @see ConditionInterface::condition() method (and thus have the
  188. // default value as defined over there) it is assumed to be a valid
  189. // condition on its own: ignore the operator and value parts.
  190. $ignore_operator = $condition['operator'] === '=' && $condition['value'] === NULL;
  191. }
  192. elseif (!isset($condition['operator'])) {
  193. // Left hand part is a literal string added with the
  194. // @see ConditionInterface::where() method. Put brackets around
  195. // the snippet and collect the arguments from the value part.
  196. // Also ignore the operator and value parts.
  197. $field_fragment = '(' . $condition['field'] . ')';
  198. $arguments += $condition['value'];
  199. $ignore_operator = TRUE;
  200. }
  201. else {
  202. // Left hand part is a normal field. Add it as is.
  203. $field_fragment = $connection->escapeField($condition['field']);
  204. $ignore_operator = FALSE;
  205. }
  206. // Process operator.
  207. if ($ignore_operator) {
  208. $operator = ['operator' => '', 'use_value' => FALSE];
  209. }
  210. else {
  211. // Remove potentially dangerous characters.
  212. // If something passed in an invalid character stop early, so we
  213. // don't rely on a broken SQL statement when we would just replace
  214. // those characters.
  215. if (stripos($condition['operator'], 'UNION') !== FALSE || strpbrk($condition['operator'], '[-\'"();') !== FALSE) {
  216. $this->changed = TRUE;
  217. $this->arguments = [];
  218. // Provide a string which will result into an empty query result.
  219. $this->stringVersion = '( AND 1 = 0 )';
  220. // Conceptually throwing an exception caused by user input is bad
  221. // as you result into a WSOD, which depending on your webserver
  222. // configuration can result into the assumption that your site is
  223. // broken.
  224. // On top of that the database API relies on __toString() which
  225. // does not allow to throw exceptions.
  226. trigger_error('Invalid characters in query operator: ' . $condition['operator'], E_USER_ERROR);
  227. return;
  228. }
  229. // For simplicity, we convert all operators to a data structure to
  230. // allow to specify a prefix, a delimiter and such. Find the
  231. // associated data structure by first doing a database specific
  232. // lookup, followed by a specification according to the SQL standard.
  233. $operator = $connection->mapConditionOperator($condition['operator']);
  234. if (!isset($operator)) {
  235. $operator = $this->mapConditionOperator($condition['operator']);
  236. }
  237. $operator += ['operator' => $condition['operator']];
  238. }
  239. // Add defaults.
  240. $operator += [
  241. 'prefix' => '',
  242. 'postfix' => '',
  243. 'delimiter' => '',
  244. 'use_value' => TRUE,
  245. ];
  246. $operator_fragment = $operator['operator'];
  247. // Process value.
  248. $value_fragment = '';
  249. if ($operator['use_value']) {
  250. // For simplicity, we first convert to an array, so that we can handle
  251. // the single and multi value cases the same.
  252. if (!is_array($condition['value'])) {
  253. if ($condition['value'] instanceof SelectInterface && ($operator['operator'] === 'IN' || $operator['operator'] === 'NOT IN')) {
  254. // Special case: IN is followed by a single select query instead
  255. // of a set of values: unset prefix and postfix to prevent double
  256. // brackets.
  257. $operator['prefix'] = '';
  258. $operator['postfix'] = '';
  259. }
  260. $condition['value'] = [$condition['value']];
  261. }
  262. // Process all individual values.
  263. $value_fragment = [];
  264. foreach ($condition['value'] as $value) {
  265. if ($value instanceof SelectInterface) {
  266. // Right hand part is a subquery. Compile, put brackets around it
  267. // and collect any arguments.
  268. $value->compile($connection, $queryPlaceholder);
  269. $value_fragment[] = '(' . (string) $value . ')';
  270. $arguments += $value->arguments();
  271. }
  272. else {
  273. // Right hand part is a normal value. Replace the value with a
  274. // placeholder and add the value as an argument.
  275. $placeholder = ':db_condition_placeholder_' . $queryPlaceholder->nextPlaceholder();
  276. $value_fragment[] = $placeholder;
  277. $arguments[$placeholder] = $value;
  278. }
  279. }
  280. $value_fragment = $operator['prefix'] . implode($operator['delimiter'], $value_fragment) . $operator['postfix'];
  281. }
  282. // Concatenate the left hand part, operator and right hand part.
  283. $condition_fragments[] = trim(implode(' ', [$field_fragment, $operator_fragment, $value_fragment]));
  284. }
  285. // Concatenate all conditions using the conjunction and brackets around
  286. // the individual conditions to assure the proper evaluation order.
  287. $this->stringVersion = count($condition_fragments) > 1 ? '(' . implode(") $conjunction (", $condition_fragments) . ')' : implode($condition_fragments);
  288. $this->arguments = $arguments;
  289. $this->changed = FALSE;
  290. }
  291. }
  292. /**
  293. * {@inheritdoc}
  294. */
  295. public function compiled() {
  296. return !$this->changed;
  297. }
  298. /**
  299. * Implements PHP magic __toString method to convert the conditions to string.
  300. *
  301. * @return string
  302. * A string version of the conditions.
  303. */
  304. public function __toString() {
  305. // If the caller forgot to call compile() first, refuse to run.
  306. if ($this->changed) {
  307. return '';
  308. }
  309. return $this->stringVersion;
  310. }
  311. /**
  312. * PHP magic __clone() method.
  313. *
  314. * Only copies fields that implement Drupal\Core\Database\Query\ConditionInterface. Also sets
  315. * $this->changed to TRUE.
  316. */
  317. public function __clone() {
  318. $this->changed = TRUE;
  319. foreach ($this->conditions as $key => $condition) {
  320. if ($key !== '#conjunction') {
  321. if ($condition['field'] instanceof ConditionInterface) {
  322. $this->conditions[$key]['field'] = clone($condition['field']);
  323. }
  324. if ($condition['value'] instanceof SelectInterface) {
  325. $this->conditions[$key]['value'] = clone($condition['value']);
  326. }
  327. }
  328. }
  329. }
  330. /**
  331. * Gets any special processing requirements for the condition operator.
  332. *
  333. * Some condition types require special processing, such as IN, because
  334. * the value data they pass in is not a simple value. This is a simple
  335. * overridable lookup function.
  336. *
  337. * @param string $operator
  338. * The condition operator, such as "IN", "BETWEEN", etc. Case-sensitive.
  339. *
  340. * @return array
  341. * The extra handling directives for the specified operator or an empty
  342. * array if there are no extra handling directives.
  343. */
  344. protected function mapConditionOperator($operator) {
  345. if (isset(static::$conditionOperatorMap[$operator])) {
  346. $return = static::$conditionOperatorMap[$operator];
  347. }
  348. else {
  349. // We need to upper case because PHP index matches are case sensitive but
  350. // do not need the more expensive mb_strtoupper() because SQL statements
  351. // are ASCII.
  352. $operator = strtoupper($operator);
  353. $return = isset(static::$conditionOperatorMap[$operator]) ? static::$conditionOperatorMap[$operator] : [];
  354. }
  355. $return += ['operator' => $operator];
  356. return $return;
  357. }
  358. /**
  359. * {@inheritdoc}
  360. */
  361. public function conditionGroupFactory($conjunction = 'AND') {
  362. return new static($conjunction);
  363. }
  364. /**
  365. * {@inheritdoc}
  366. */
  367. public function andConditionGroup() {
  368. return $this->conditionGroupFactory('AND');
  369. }
  370. /**
  371. * {@inheritdoc}
  372. */
  373. public function orConditionGroup() {
  374. return $this->conditionGroupFactory('OR');
  375. }
  376. }