Condition.php 13 KB

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