Bitrix-D7  20.0.0
conditiontree.php
См. документацию.
1 <?php
2 /**
3  * Bitrix Framework
4  * @package bitrix
5  * @subpackage main
6  * @copyright 2001-2016 Bitrix
7  */
8 
10 
17 
18 /**
19  * Handles filtering conditions for Query and join conditions for Entity References.
20  * @package bitrix
21  * @subpackage main
22  */
24 {
25  /** @var Chain[] */
26  protected $chains;
27 
28  /** @var Condition[]|ConditionTree[] */
29  protected $conditions = array();
30 
31  /**
32  * @var string and|or
33  * @see ConditionTree::logic()
34  */
35  protected $logic;
36 
37  const LOGIC_OR = 'or';
38 
39  const LOGIC_AND = 'and';
40 
41  /**
42  * Whether to set NOT before all the conditions.
43  * @var bool
44  */
45  protected $isNegative = false;
46 
47  /**
48  * ConditionTree constructor.
49  */
50  public function __construct()
51  {
52  $this->logic = static::LOGIC_AND;
53  }
54 
55  /**
56  * All conditions will be imploded by this logic: static::LOGIC_AND or static::LOGIC_OR
57  *
58  * @param string $logic and|or
59  *
60  * @return $this|string
61  * @throws ArgumentException
62  */
63  public function logic($logic = null)
64  {
65  if ($logic === null)
66  {
67  return $this->logic;
68  }
69 
70  if (!in_array(strtolower($logic), [static::LOGIC_AND, static::LOGIC_OR], true))
71  {
72  throw new ArgumentException("Unknown logic");
73  }
74 
75  $this->logic = strtolower($logic);
76 
77  return $this;
78  }
79 
80  /**
81  * Sets NOT before all the conditions.
82  *
83  * @param bool $negative
84  *
85  * @return $this
86  */
87  public function negative($negative = true)
88  {
89  $this->isNegative = $negative;
90  return $this;
91  }
92 
93  /**
94  * General condition. In regular case used with 3 parameters:
95  * where(columnName, operator, value), e.g. ('ID', '=', 1); ('SALARY', '>', '500')
96  *
97  * List of available operators can be found in Operator class.
98  * @see Operator::$operators
99  *
100  * Can be used in short format:
101  * where(columnName, value), with operator '=' by default
102  * Can be used in ultra short format:
103  * where(columnName), for boolean fields only
104  *
105  * Can be used for subfilter set:
106  * where(ConditionTree subfilter)
107  *
108  * Instead of columnName, you can use runtime field:
109  * where(new ExpressionField('TMP', 'CONCAT(%s, %s)', ["NAME", "LAST_NAME"]), 'Anton Ivanov')
110  * or with expr helper
111  * where(Query::expr()->concat("NAME", "LAST_NAME"), 'Anton Ivanov')
112  *
113  * @param array ...$filter
114  *
115  * @return $this
116  * @throws ArgumentException
117  */
118  public function where()
119  {
120  $filter = func_get_args();
121 
122  // subfilter
123  if (count($filter) == 1 && $filter[0] instanceof ConditionTree)
124  {
125  $this->conditions[] = $filter[0];
126  return $this;
127  }
128 
129  // ready condition
130  if (count($filter) == 1 && $filter[0] instanceof Condition)
131  {
132  $this->conditions[] = $filter[0];
133  return $this;
134  }
135 
136  // array of conditions
137  if (count($filter) == 1 && is_array($filter[0]))
138  {
139  foreach ($filter[0] as $condition)
140  {
141  // call `where` for each condition
142  call_user_func_array(array($this, 'where'), $condition);
143  }
144  return $this;
145  }
146 
147  // regular conditions
148  if (count($filter) == 3)
149  {
150  // everything is clear
151  list($column, $operator, $value) = $filter;
152  }
153  elseif (count($filter) == 2)
154  {
155  // equal by default
156  list($column, $value) = $filter;
157  $operator = '=';
158  }
159  elseif (count($filter) == 1)
160  {
161  // suppose it is boolean field with true value
162  $column = $filter[0];
163  $operator = '=';
164  $value = true;
165  }
166  else
167  {
168  throw new ArgumentException('Wrong arguments');
169  }
170 
171  // validate operator
172  $operators = Operator::get();
173  if (!isset($operators[$operator]))
174  {
175  throw new ArgumentException("Unknown operator `{$operator}`");
176  }
177 
178  // add condition
179  $this->conditions[] = new Condition($column, $operator, $value);
180 
181  return $this;
182  }
183 
184  /**
185  * Sets NOT before any conditions or subfilter.
186  * @see ConditionTree::where()
187  *
188  * @param array ...$filter
189  *
190  * @return $this
191  */
192  public function whereNot()
193  {
194  $filter = func_get_args();
195 
196  $subFilter = new static();
197  call_user_func_array(array($subFilter, 'where'), $filter);
198 
199  $this->conditions[] = $subFilter->negative();
200  return $this;
201  }
202 
203  /**
204  * The same logic as where(), but value will be taken as another column name.
205  * @see ConditionTree::where()
206  *
207  * @param array ...$filter
208  *
209  * @return $this
210  * @throws ArgumentException
211  */
212  public function whereColumn()
213  {
214  $filter = func_get_args();
215 
216  if (count($filter) == 3)
217  {
218  list($column, $operator, $value) = $filter;
219  }
220  elseif (count($filter) == 2)
221  {
222  list($column, $value) = $filter;
223  $operator = '=';
224  }
225  else
226  {
227  throw new ArgumentException('Wrong arguments');
228  }
229 
230  // convert value to column format
231  $value = new Expressions\ColumnExpression($value);
232 
233  // put through general method
234  $this->where($column, $operator, $value);
235 
236  return $this;
237  }
238 
239  /**
240  * Compares column with NULL.
241  *
242  * @param string $column
243  *
244  * @return $this
245  */
246  public function whereNull($column)
247  {
248  $this->conditions[] = new Condition($column, '=', null);
249 
250  return $this;
251  }
252 
253  /**
254  * Compares column with NOT NULL.
255  *
256  * @param string $column
257  *
258  * @return $this
259  */
260  public function whereNotNull($column)
261  {
262  $this->conditions[] = new Condition($column, '<>', null);
263 
264  return $this;
265  }
266 
267  /**
268  * IN() condition.
269  *
270  * @param string $column
271  * @param array|Query|SqlExpression $values
272  *
273  * @return $this
274  */
275  public function whereIn($column, $values)
276  {
277  $this->conditions[] = new Condition($column, 'in', $values);
278 
279  return $this;
280  }
281 
282  /**
283  * Negative IN() condition.
284  * @see ConditionTree::whereIn()
285  *
286  * @param string $column
287  * @param array|Query|SqlExpression $values
288  *
289  * @return $this
290  */
291  public function whereNotIn($column, $values)
292  {
293  $subFilter = new static();
294  $this->conditions[] = $subFilter->whereIn($column, $values)->negative();
295 
296  return $this;
297  }
298 
299  /**
300  * BETWEEN condition.
301  *
302  * @param $column
303  * @param $valueMin
304  * @param $valueMax
305  *
306  * @return $this
307  */
308  public function whereBetween($column, $valueMin, $valueMax)
309  {
310  $this->conditions[] = new Condition($column, 'between', array($valueMin, $valueMax));
311 
312  return $this;
313  }
314 
315  /**
316  * Negative BETWEEN condition.
317  * @see ConditionTree::whereBetween()
318  *
319  * @param $column
320  * @param $valueMin
321  * @param $valueMax
322  *
323  * @return $this
324  */
325  public function whereNotBetween($column, $valueMin, $valueMax)
326  {
327  $subFilter = new static();
328  $this->conditions[] = $subFilter->whereBetween($column, $valueMin, $valueMax)->negative();
329 
330  return $this;
331  }
332 
333  /**
334  * LIKE condition, without default % placement.
335  *
336  * @param $column
337  * @param $value
338  *
339  * @return $this
340  */
341  public function whereLike($column, $value)
342  {
343  $this->conditions[] = new Condition($column, 'like', $value);
344 
345  return $this;
346  }
347 
348  /**
349  * Negative LIKE condition, without default % placement.
350  * @see ConditionTree::whereLike()
351  *
352  * @param $column
353  * @param $value
354  *
355  * @return $this
356  */
357  public function whereNotLike($column, $value)
358  {
359  $subFilter = new static();
360  $this->conditions[] = $subFilter->whereLike($column, $value)->negative();
361 
362  return $this;
363  }
364 
365  /**
366  * Exists() condition. Can be used with Query object or plain sql wrapped with SqlExpression.
367  *
368  * @param Query|SqlExpression $query
369  *
370  * @return $this
371  */
372  public function whereExists($query)
373  {
374  $this->conditions[] = new Condition(null, 'exists', $query);
375 
376  return $this;
377  }
378 
379  /**
380  * Negative Exists() condition. Can be used with Query object or plain sql wrapped with SqlExpression.
381  * @see ConditionTree::whereExists()
382  *
383  * @param Query|SqlExpression $query
384  *
385  * @return $this
386  * @throws ArgumentException
387  */
388  public function whereNotExists($query)
389  {
390  if ($query instanceof Query || $query instanceof SqlExpression)
391  {
392  $subFilter = new static();
393  $this->conditions[] = $subFilter->whereExists($query)->negative();
394 
395  return $this;
396  }
397 
398  throw new ArgumentException('Unknown type of query '.gettype($query));
399  }
400 
401  /**
402  * Fulltext search condition.
403  * @see Helper::matchAgainstWildcard() for preparing $value for AGAINST.
404  *
405  * @param $column
406  * @param $value
407  *
408  * @return $this
409  */
410  public function whereMatch($column, $value)
411  {
412  $this->conditions[] = new Condition($column, 'match', $value);
413 
414  return $this;
415  }
416 
417  /**
418  * Negative fulltext search condition.
419  * @see Helper::matchAgainstWildcard() for preparing $value for AGAINST.
420  *
421  * @param $column
422  * @param $value
423  *
424  * @return $this
425  */
426  public function whereNotMatch($column, $value)
427  {
428  $subFilter = new static();
429  $this->conditions[] = $subFilter->whereMatch($column, $value)->negative();
430 
431  return $this;
432  }
433 
434  /**
435  * Returns SQL for all conditions and subfilters.
436  *
437  * @param Chain[] $chains
438  *
439  * @return string
440  * @throws ArgumentException
441  * @throws SystemException
442  */
443  public function getSql($chains)
444  {
445  // save chains
446  $this->chains = $chains;
447 
448  $finalSql = array();
449 
450  // build sql
451  foreach ($this->conditions as $condition)
452  {
453  if ($condition instanceof ConditionTree)
454  {
455  // subfilter
456  $subFilter = $condition;
457  $sql = $subFilter->getSql($chains);
458 
459  if (count($subFilter->getConditions()) > 1)
460  {
461  $sql = "({$sql})";
462  }
463  }
464  else
465  {
466  // regular condition
467  $columnSqlDefinition = null;
468  $columnField = null;
469 
470  // define column field
471  if ($condition->getColumn() !== null)
472  {
473  $chain = $chains[$condition->getDefinition()];
474  $columnSqlDefinition = $chain->getSqlDefinition();
475 
476  /** @var IReadable $columnField */
477  $columnField = $chain->getLastElement()->getValue();
478  }
479 
480  // final value's sql
481  if (in_array($condition->getOperator(), array('in', 'between'), true) && is_array($condition->getValue()))
482  {
483  // value is in array of atomic values
484  $finalValue = $this->convertValues($condition->getValue(), $columnField);
485  }
486  else
487  {
488  $finalValue = $this->convertValue($condition->getValue(), $columnField);
489  }
490 
491  // operation method
492  $operators = Operator::get();
493  $operator = $operators[$condition->getOperator()];
494 
495  // final sql
496  $sql = call_user_func(
497  array('Bitrix\Main\ORM\Query\Filter\Operator', $operator),
498  $columnSqlDefinition, $finalValue
499  );
500  }
501 
502  $finalSql[] = $sql;
503  }
504 
505  // concat with $this->logic
506  $sql = join(" ".strtoupper($this->logic)." ", $finalSql);
507 
508  // and put NOT if negative
509  if ($this->isNegative)
510  {
511  $sql = count($finalSql) > 1 ? "NOT ({$sql})" : "NOT {$sql}";
512  }
513 
514  return $sql;
515  }
516 
517  /**
518  * Returns all conditions and subfilters.
519  *
520  * @return ConditionTree[]|Condition[]
521  */
522  public function getConditions()
523  {
524  return $this->conditions;
525  }
526 
527  /**
528  * Adds prepared condition.
529  *
530  * @param Condition|ConditionTree $condition
531  *
532  * @return $this
533  * @throws ArgumentException
534  */
535  public function addCondition($condition)
536  {
537  if ($condition instanceof Condition || $condition instanceof ConditionTree)
538  {
539  $this->conditions[] = $condition;
540  return $this;
541  }
542 
543  throw new ArgumentException('Unknown type of condition '.gettype($condition));
544  }
545 
546  /**
547  * Checks if filter is not empty.
548  *
549  * @return bool
550  */
551  public function hasConditions()
552  {
553  return !empty($this->conditions);
554  }
555 
556  /**
557  * Replaces condition with a new one.
558  *
559  * @param $currentCondition
560  * @param $newCondition
561  *
562  * @return bool
563  */
564  public function replaceCondition($currentCondition, $newCondition)
565  {
566  foreach ($this->conditions as $k => $condition)
567  {
568  if ($condition === $currentCondition)
569  {
570  $this->conditions[$k] = $newCondition;
571  return true;
572  }
573  }
574 
575  return false;
576  }
577 
578  /**
579  * Removes one condition
580  *
581  * @param $condition
582  *
583  * @return bool
584  */
585  public function removeCondition($condition)
586  {
587  foreach ($this->conditions as $k => $_condition)
588  {
589  if ($condition === $_condition)
590  {
591  unset($this->conditions[$k]);
592  return true;
593  }
594  }
595 
596  return false;
597  }
598 
599  /**
600  * Removes all conditions
601  */
602  public function removeAllConditions()
603  {
604  $this->conditions = [];
605  }
606 
607  /**
608  * Converts any value to raw SQL, except of NULL, which is supposed to be handled in Operator.
609  *
610  * @param mixed $value
611  * @param IReadable $field
612  *
613  * @return mixed|null|string
614  * @throws ArgumentException
615  * @throws SystemException
616  */
617  protected function convertValue($value, IReadable $field = null)
618  {
619  // any sql expression
620  if ($value instanceof SqlExpression)
621  {
622  return $value->compile();
623  }
624 
625  // subquery
626  if ($value instanceof Query)
627  {
628  return $value->getQuery();
629  }
630 
631  // subfilter
632  if ($value instanceof ConditionTree)
633  {
634  return $value->getSql($this->chains);
635  }
636 
637  // nulls
638  if ($value === null)
639  {
640  return new Expressions\NullExpression;
641  }
642 
643  if ($value instanceof Expressions\ColumnExpression)
644  {
645  /** @var Chain $valueChain */
646  $valueChain = $this->chains[$value->getDefinition()];
647  return $valueChain->getSqlDefinition();
648  }
649 
650  return $field->convertValueToDb($value); // give them current sql helper
651  }
652 
653  /**
654  * Converts array of values to raw SQL.
655  * @see ConditionTree::convertValue()
656  *
657  * @param array $values
658  * @param \Bitrix\Main\ORM\Fields\IReadable|null $field
659  *
660  * @return array
661  * @throws ArgumentException
662  * @throws SystemException
663  */
664  protected function convertValues($values, IReadable $field = null)
665  {
666  foreach ($values as $k => $value)
667  {
668  $values[$k] = $this->convertValue($value, $field);
669  }
670 
671  return $values;
672  }
673 
674  public function __clone()
675  {
676  $newConditions = array();
677 
678  foreach ($this->conditions as $condition)
679  {
680  $newConditions[] = clone $condition;
681  }
682 
683  $this->conditions = $newConditions;
684  }
685 }
Bitrix\Main\ORM\Query\Filter
Definition: main/lib/orm/query/filter/condition.php:9
Bitrix\Main\ORM\Query\Filter\ConditionTree\convertValue
convertValue($value, IReadable $field=null)
Converts any value to raw SQL, except of NULL, which is supposed to be handled in Operator.
Definition: conditiontree.php:617
Bitrix\Main\ORM\Query\Filter\ConditionTree\$logic
$logic
Definition: conditiontree.php:35
Bitrix\Main\ORM\Query\Filter\ConditionTree\whereNull
whereNull($column)
Compares column with NULL.
Definition: conditiontree.php:246
Bitrix\Main\ORM\Query\Filter\ConditionTree\$chains
$chains
Definition: conditiontree.php:26
Bitrix\Main\ORM\Fields\IReadable
Definition: ireadable.php:15
Bitrix\Main\ORM\Query\Filter\ConditionTree\removeCondition
removeCondition($condition)
Removes one condition.
Definition: conditiontree.php:585
Bitrix\Main\ORM\Query\Query\$filter
$filter
Definition: main/lib/orm/query/query.php:127
Bitrix\Main\ORM\Query\Filter\Expressions\ColumnExpression
Definition: columnexpression.php:16
Bitrix\Main\ORM\Query\Filter\ConditionTree\LOGIC_OR
const LOGIC_OR
Definition: conditiontree.php:37
Bitrix\Main\ORM\Query\Filter\ConditionTree\negative
negative($negative=true)
Sets NOT before all the conditions.
Definition: conditiontree.php:87
Bitrix\Main\ORM\Query\Filter\ConditionTree\logic
logic($logic=null)
All conditions will be imploded by this logic: static::LOGIC_AND or static::LOGIC_OR.
Definition: conditiontree.php:63
Bitrix\Main\ORM\Query\Filter\ConditionTree\whereColumn
whereColumn()
The same logic as where(), but value will be taken as another column name.
Definition: conditiontree.php:212
Bitrix\Main\ORM\Query\Filter\ConditionTree\whereNotExists
whereNotExists($query)
Negative Exists() condition.
Definition: conditiontree.php:388
Bitrix\Main\DB\SqlExpression
Definition: sqlexpression.php:18
Bitrix\Main\ORM\Query\Filter\ConditionTree\whereLike
whereLike($column, $value)
LIKE condition, without default % placement.
Definition: conditiontree.php:341
Bitrix\Main\ORM\Query\Filter\ConditionTree\__construct
__construct()
ConditionTree constructor.
Definition: conditiontree.php:50
Bitrix\Main\ORM\Query\Filter\ConditionTree\addCondition
addCondition($condition)
Adds prepared condition.
Definition: conditiontree.php:535
Bitrix\Main\ORM\Query\Filter\ConditionTree\$conditions
$conditions
Definition: conditiontree.php:29
Bitrix\Main\ORM\Query\Filter\ConditionTree\convertValues
convertValues($values, IReadable $field=null)
Converts array of values to raw SQL.
Definition: conditiontree.php:664
Bitrix\Main\ORM\Query\Filter\ConditionTree\LOGIC_AND
const LOGIC_AND
Definition: conditiontree.php:39
Bitrix\Main\ORM\Query\Filter\ConditionTree\getSql
getSql($chains)
Returns SQL for all conditions and subfilters.
Definition: conditiontree.php:443
Bitrix\Main\ArgumentException
Exception is thrown when function argument is not valid.
Definition: main/lib/exception.php:33
Bitrix\Main\ORM\Query\Filter\ConditionTree
Definition: conditiontree.php:23
Bitrix\Main\ORM\Query\Filter\ConditionTree\whereMatch
whereMatch($column, $value)
Fulltext search condition.
Definition: conditiontree.php:410
Bitrix\Main\ORM\Query\Query
Definition: main/lib/orm/query/query.php:112
Bitrix\Main\SystemException
Base class for fatal exceptions.
Definition: main/lib/exception.php:7
Bitrix\Main\ORM\Query\Chain
Definition: main/lib/orm/query/chain.php:13
Bitrix\Main\ORM\Query\Filter\ConditionTree\whereIn
whereIn($column, $values)
IN() condition.
Definition: conditiontree.php:275
Bitrix\Main\ORM\Query\Filter\ConditionTree\whereNotLike
whereNotLike($column, $value)
Negative LIKE condition, without default % placement.
Definition: conditiontree.php:357
Bitrix\Main\ORM\Query\Filter\Expressions\NullExpression
Definition: nullexpression.php:16
Bitrix\Main\ORM\Query\Filter\ConditionTree\whereNotBetween
whereNotBetween($column, $valueMin, $valueMax)
Negative BETWEEN condition.
Definition: conditiontree.php:325
Bitrix\Main\ORM\Query\Filter\ConditionTree\getConditions
getConditions()
Returns all conditions and subfilters.
Definition: conditiontree.php:522
Bitrix\Main\ORM\Query\Filter\ConditionTree\where
where()
General condition.
Definition: conditiontree.php:118
Bitrix\Main\ORM\Query\Filter\ConditionTree\hasConditions
hasConditions()
Checks if filter is not empty.
Definition: conditiontree.php:551
Bitrix\Main\ORM\Query\Filter\ConditionTree\whereNotMatch
whereNotMatch($column, $value)
Negative fulltext search condition.
Definition: conditiontree.php:426
Bitrix\Main\ORM\Query\Filter\Condition
Definition: main/lib/orm/query/filter/condition.php:20
Bitrix\Main\ORM\Query\Filter\ConditionTree\whereNotIn
whereNotIn($column, $values)
Negative IN() condition.
Definition: conditiontree.php:291
Bitrix\Main\ORM\Query\Filter\ConditionTree\whereBetween
whereBetween($column, $valueMin, $valueMax)
BETWEEN condition.
Definition: conditiontree.php:308
Bitrix\Main\ORM\Query\Filter\ConditionTree\whereExists
whereExists($query)
Exists() condition.
Definition: conditiontree.php:372
Bitrix\Main\ORM\Query\Filter\ConditionTree\whereNot
whereNot()
Sets NOT before any conditions or subfilter.
Definition: conditiontree.php:192
Bitrix\Main\ORM\Query\Filter\ConditionTree\$isNegative
$isNegative
Definition: conditiontree.php:45
Bitrix\Main\ORM\Query\Filter\ConditionTree\removeAllConditions
removeAllConditions()
Removes all conditions.
Definition: conditiontree.php:602
Bitrix\Main\ORM\Query\Filter\ConditionTree\__clone
__clone()
Definition: conditiontree.php:674
Bitrix\Main\ORM\Query\Filter\Operator\get
static get()
List of available operators code => method.
Definition: operator.php:43
Bitrix\Main\ORM\Query\Filter\ConditionTree\replaceCondition
replaceCondition($currentCondition, $newCondition)
Replaces condition with a new one.
Definition: conditiontree.php:564
Bitrix\Main\ORM\Query\Filter\ConditionTree\whereNotNull
whereNotNull($column)
Compares column with NOT NULL.
Definition: conditiontree.php:260