PageRenderTime 43ms CodeModel.GetById 9ms RepoModel.GetById 0ms app.codeStats 0ms

/core/lib/Drupal/Core/Database/Query/Condition.php

https://gitlab.com/guillaumev/alkarama
PHP | 356 lines | 185 code | 37 blank | 134 comment | 24 complexity | cfedfbcc870b975b9c5cd0e52b6677b2 MD5 | raw file
  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. * Array of conditions.
  11. *
  12. * @var array
  13. */
  14. protected $conditions = array();
  15. /**
  16. * Array of arguments.
  17. *
  18. * @var array
  19. */
  20. protected $arguments = array();
  21. /**
  22. * Whether the conditions have been changed.
  23. *
  24. * TRUE if the condition has been changed since the last compile.
  25. * FALSE if the condition has been compiled and not changed.
  26. *
  27. * @var bool
  28. */
  29. protected $changed = TRUE;
  30. /**
  31. * The identifier of the query placeholder this condition has been compiled against.
  32. */
  33. protected $queryPlaceholderIdentifier;
  34. /**
  35. * Constructs a Condition object.
  36. *
  37. * @param string $conjunction
  38. * The operator to use to combine conditions: 'AND' or 'OR'.
  39. */
  40. public function __construct($conjunction) {
  41. $this->conditions['#conjunction'] = $conjunction;
  42. }
  43. /**
  44. * Implements Countable::count().
  45. *
  46. * Returns the size of this conditional. The size of the conditional is the
  47. * size of its conditional array minus one, because one element is the
  48. * conjunction.
  49. */
  50. public function count() {
  51. return count($this->conditions) - 1;
  52. }
  53. /**
  54. * {@inheritdoc}
  55. */
  56. public function condition($field, $value = NULL, $operator = '=') {
  57. if (empty($operator)) {
  58. $operator = '=';
  59. }
  60. if (empty($value) && is_array($value)) {
  61. throw new InvalidQueryException(sprintf("Query condition '%s %s ()' cannot be empty.", $field, $operator));
  62. }
  63. $this->conditions[] = array(
  64. 'field' => $field,
  65. 'value' => $value,
  66. 'operator' => $operator,
  67. );
  68. $this->changed = TRUE;
  69. return $this;
  70. }
  71. /**
  72. * {@inheritdoc}
  73. */
  74. public function where($snippet, $args = array()) {
  75. $this->conditions[] = array(
  76. 'field' => $snippet,
  77. 'value' => $args,
  78. 'operator' => NULL,
  79. );
  80. $this->changed = TRUE;
  81. return $this;
  82. }
  83. /**
  84. * {@inheritdoc}
  85. */
  86. public function isNull($field) {
  87. return $this->condition($field, NULL, 'IS NULL');
  88. }
  89. /**
  90. * {@inheritdoc}
  91. */
  92. public function isNotNull($field) {
  93. return $this->condition($field, NULL, 'IS NOT NULL');
  94. }
  95. /**
  96. * {@inheritdoc}
  97. */
  98. public function exists(SelectInterface $select) {
  99. return $this->condition('', $select, 'EXISTS');
  100. }
  101. /**
  102. * {@inheritdoc}
  103. */
  104. public function notExists(SelectInterface $select) {
  105. return $this->condition('', $select, 'NOT EXISTS');
  106. }
  107. /**
  108. * {@inheritdoc}
  109. */
  110. public function &conditions() {
  111. return $this->conditions;
  112. }
  113. /**
  114. * {@inheritdoc}
  115. */
  116. public function arguments() {
  117. // If the caller forgot to call compile() first, refuse to run.
  118. if ($this->changed) {
  119. return NULL;
  120. }
  121. return $this->arguments;
  122. }
  123. /**
  124. * {@inheritdoc}
  125. */
  126. public function compile(Connection $connection, PlaceholderInterface $queryPlaceholder) {
  127. // Re-compile if this condition changed or if we are compiled against a
  128. // different query placeholder object.
  129. if ($this->changed || isset($this->queryPlaceholderIdentifier) && ($this->queryPlaceholderIdentifier != $queryPlaceholder->uniqueIdentifier())) {
  130. $this->queryPlaceholderIdentifier = $queryPlaceholder->uniqueIdentifier();
  131. $condition_fragments = array();
  132. $arguments = array();
  133. $conditions = $this->conditions;
  134. $conjunction = $conditions['#conjunction'];
  135. unset($conditions['#conjunction']);
  136. foreach ($conditions as $condition) {
  137. if (empty($condition['operator'])) {
  138. // This condition is a literal string, so let it through as is.
  139. $condition_fragments[] = ' (' . $condition['field'] . ') ';
  140. $arguments += $condition['value'];
  141. }
  142. else {
  143. // It's a structured condition, so parse it out accordingly.
  144. // Note that $condition['field'] will only be an object for a dependent
  145. // DatabaseCondition object, not for a dependent subquery.
  146. if ($condition['field'] instanceof ConditionInterface) {
  147. // Compile the sub-condition recursively and add it to the list.
  148. $condition['field']->compile($connection, $queryPlaceholder);
  149. $condition_fragments[] = '(' . (string) $condition['field'] . ')';
  150. $arguments += $condition['field']->arguments();
  151. }
  152. else {
  153. // For simplicity, we treat all operators as the same data structure.
  154. // In the typical degenerate case, this won't get changed.
  155. $operator_defaults = array(
  156. 'prefix' => '',
  157. 'postfix' => '',
  158. 'delimiter' => '',
  159. 'operator' => $condition['operator'],
  160. 'use_value' => TRUE,
  161. );
  162. // Remove potentially dangerous characters.
  163. // If something passed in an invalid character stop early, so we
  164. // don't rely on a broken SQL statement when we would just replace
  165. // those characters.
  166. if (stripos($condition['operator'], 'UNION') !== FALSE || strpbrk($condition['operator'], '[-\'"();') !== FALSE) {
  167. $this->changed = TRUE;
  168. $this->arguments = [];
  169. // Provide a string which will result into an empty query result.
  170. $this->stringVersion = '( AND 1 = 0 )';
  171. // Conceptually throwing an exception caused by user input is bad
  172. // as you result into a WSOD, which depending on your webserver
  173. // configuration can result into the assumption that your site is
  174. // broken.
  175. // On top of that the database API relies on __toString() which
  176. // does not allow to throw exceptions.
  177. trigger_error('Invalid characters in query operator: ' . $condition['operator'], E_USER_ERROR);
  178. return;
  179. }
  180. $operator = $connection->mapConditionOperator($condition['operator']);
  181. if (!isset($operator)) {
  182. $operator = $this->mapConditionOperator($condition['operator']);
  183. }
  184. $operator += $operator_defaults;
  185. $placeholders = array();
  186. if ($condition['value'] instanceof SelectInterface) {
  187. $condition['value']->compile($connection, $queryPlaceholder);
  188. $placeholders[] = (string) $condition['value'];
  189. $arguments += $condition['value']->arguments();
  190. // Subqueries are the actual value of the operator, we don't
  191. // need to add another below.
  192. $operator['use_value'] = FALSE;
  193. }
  194. // We assume that if there is a delimiter, then the value is an
  195. // array. If not, it is a scalar. For simplicity, we first convert
  196. // up to an array so that we can build the placeholders in the same way.
  197. elseif (!$operator['delimiter'] && !is_array($condition['value'])) {
  198. $condition['value'] = array($condition['value']);
  199. }
  200. if ($operator['use_value']) {
  201. foreach ($condition['value'] as $value) {
  202. $placeholder = ':db_condition_placeholder_' . $queryPlaceholder->nextPlaceholder();
  203. $arguments[$placeholder] = $value;
  204. $placeholders[] = $placeholder;
  205. }
  206. }
  207. $condition_fragments[] = ' (' . $connection->escapeField($condition['field']) . ' ' . $operator['operator'] . ' ' . $operator['prefix'] . implode($operator['delimiter'], $placeholders) . $operator['postfix'] . ') ';
  208. }
  209. }
  210. }
  211. $this->changed = FALSE;
  212. $this->stringVersion = implode($conjunction, $condition_fragments);
  213. $this->arguments = $arguments;
  214. }
  215. }
  216. /**
  217. * {@inheritdoc}
  218. */
  219. public function compiled() {
  220. return !$this->changed;
  221. }
  222. /**
  223. * Implements PHP magic __toString method to convert the conditions to string.
  224. *
  225. * @return string
  226. * A string version of the conditions.
  227. */
  228. public function __toString() {
  229. // If the caller forgot to call compile() first, refuse to run.
  230. if ($this->changed) {
  231. return '';
  232. }
  233. return $this->stringVersion;
  234. }
  235. /**
  236. * PHP magic __clone() method.
  237. *
  238. * Only copies fields that implement Drupal\Core\Database\Query\ConditionInterface. Also sets
  239. * $this->changed to TRUE.
  240. */
  241. function __clone() {
  242. $this->changed = TRUE;
  243. foreach ($this->conditions as $key => $condition) {
  244. if ($key !== '#conjunction') {
  245. if ($condition['field'] instanceof ConditionInterface) {
  246. $this->conditions[$key]['field'] = clone($condition['field']);
  247. }
  248. if ($condition['value'] instanceof SelectInterface) {
  249. $this->conditions[$key]['value'] = clone($condition['value']);
  250. }
  251. }
  252. }
  253. }
  254. /**
  255. * Gets any special processing requirements for the condition operator.
  256. *
  257. * Some condition types require special processing, such as IN, because
  258. * the value data they pass in is not a simple value. This is a simple
  259. * overridable lookup function.
  260. *
  261. * @param $operator
  262. * The condition operator, such as "IN", "BETWEEN", etc. Case-sensitive.
  263. *
  264. * @return array
  265. * The extra handling directives for the specified operator or an empty
  266. * array if there are no extra handling directives.
  267. */
  268. protected function mapConditionOperator($operator) {
  269. // $specials does not use drupal_static as its value never changes.
  270. static $specials = array(
  271. 'BETWEEN' => array('delimiter' => ' AND '),
  272. 'NOT BETWEEN' => array('delimiter' => ' AND '),
  273. 'IN' => array('delimiter' => ', ', 'prefix' => ' (', 'postfix' => ')'),
  274. 'NOT IN' => array('delimiter' => ', ', 'prefix' => ' (', 'postfix' => ')'),
  275. 'EXISTS' => array('prefix' => ' (', 'postfix' => ')'),
  276. 'NOT EXISTS' => array('prefix' => ' (', 'postfix' => ')'),
  277. 'IS NULL' => array('use_value' => FALSE),
  278. 'IS NOT NULL' => array('use_value' => FALSE),
  279. // Use backslash for escaping wildcard characters.
  280. 'LIKE' => array('postfix' => " ESCAPE '\\\\'"),
  281. 'NOT LIKE' => array('postfix' => " ESCAPE '\\\\'"),
  282. // These ones are here for performance reasons.
  283. '=' => array(),
  284. '<' => array(),
  285. '>' => array(),
  286. '>=' => array(),
  287. '<=' => array(),
  288. );
  289. if (isset($specials[$operator])) {
  290. $return = $specials[$operator];
  291. }
  292. else {
  293. // We need to upper case because PHP index matches are case sensitive but
  294. // do not need the more expensive Unicode::strtoupper() because SQL statements are ASCII.
  295. $operator = strtoupper($operator);
  296. $return = isset($specials[$operator]) ? $specials[$operator] : array();
  297. }
  298. $return += array('operator' => $operator);
  299. return $return;
  300. }
  301. /**
  302. * {@inheritdoc}
  303. */
  304. public function conditionGroupFactory($conjunction = 'AND') {
  305. return new Condition($conjunction);
  306. }
  307. /**
  308. * {@inheritdoc}
  309. */
  310. public function andConditionGroup() {
  311. return $this->conditionGroupFactory('AND');
  312. }
  313. /**
  314. * {@inheritdoc}
  315. */
  316. public function orConditionGroup() {
  317. return $this->conditionGroupFactory('OR');
  318. }
  319. }