Bitrix-D7  20.5.0
main/lib/orm/query/chain.php
См. документацию.
1 <?php
2 
4 
5 use Bitrix\Main;
12 
13 class Chain
14 {
15  /** @var ChainElement[] */
16  protected $chain;
17 
18  protected $size = 0;
19 
20  /** @var string[] Definition caches ([0] - full length) */
21  protected $definition;
22 
23  /** @var array Definition cache */
24  protected $definitionParts;
25 
26  protected $alias;
27 
28  protected $custom_alias;
29 
30  /** @var boolean */
31  protected $forcesDataDoublingOff = false;
32 
33  /** @var ChainElement */
34  protected $last_element;
35 
36  public function __construct()
37  {
38  $this->chain = array();
39  }
40 
41  /**
42  * @param ChainElement $element
43  *
44  * @throws SystemException
45  */
46  public function addElement(ChainElement $element)
47  {
48  if (empty($this->chain) && !($element->getValue() instanceof Entity))
49  {
50  throw new SystemException('The first element of chain should be Entity only.');
51  }
52 
53  $this->chain[] = $element;
54  $this->definition = null;
55  $this->definitionParts = null;
56  $this->alias = null;
57 
58  $this->last_element = $element;
59  $this->size++;
60  }
61 
62  /**
63  * @param ChainElement $element
64  */
65  public function prependElement(ChainElement $element)
66  {
67  $this->chain = array_merge(array($element), $this->chain);
68  $this->definition = null;
69  $this->definitionParts = null;
70  $this->alias = null;
71 
72  $this->size++;
73  }
74 
75  /**
76  * @param Chain $chain
77  */
78  public function prepend(Chain $chain)
79  {
80  $elements = $chain->getAllElements();
81 
82  for ($i=count($elements)-1; $i>=0; $i--)
83  {
84  $this->prependElement($elements[$i]);
85  }
86  }
87 
88  public function getFirstElement()
89  {
90  return $this->chain[0];
91  }
92 
93  /**
94  * @return ChainElement
95  */
96  public function getLastElement()
97  {
98  return $this->last_element;
99  }
100 
101  /**
102  * @return array|ChainElement[]
103  */
104  public function getAllElements()
105  {
106  return $this->chain;
107  }
108 
109  public function removeLastElement()
110  {
111  $this->chain = array_slice($this->chain, 0, -1);
112  $this->definition = null;
113  $this->definitionParts = null;
114  $this->alias = null;
115 
116  $this->last_element = end($this->chain);
117  $this->size--;
118  }
119 
120  public function removeFirstElement()
121  {
122  $this->chain = array_slice($this->chain, 1);
123  $this->definition = null;
124  $this->definitionParts = null;
125  $this->alias = null;
126 
127  $this->size--;
128  }
129 
130  public function hasBackReference()
131  {
132  foreach ($this->chain as $element)
133  {
134  if ($element->isBackReference())
135  {
136  return true;
137  }
138  }
139 
140  return false;
141  }
142 
143  public function getSize()
144  {
145  return $this->size;
146  }
147 
148  /**
149  * @param int $elementsSlice Definition length, e.g. -1 would exclude last element.
150  *
151  * @return string
152  */
153  public function getDefinition($elementsSlice = 0)
154  {
155  if (!isset($this->definition[$elementsSlice]))
156  {
157  $this->definition[$elementsSlice] = join('.',
158  $elementsSlice == 0
159  ? $this->getDefinitionParts()
160  : array_slice($this->getDefinitionParts(), 0, $elementsSlice)
161  );
162  }
163 
164  return $this->definition[$elementsSlice];
165  }
166 
167  /**
168  * @return array
169  */
170  public function getDefinitionParts()
171  {
172  if (is_null($this->definitionParts))
173  {
174  $this->definitionParts = static::getDefinitionPartsByChain($this);
175  }
176 
177  return $this->definitionParts;
178  }
179 
180  public function getAlias()
181  {
182  if ($this->custom_alias !== null)
183  {
184  return $this->custom_alias;
185  }
186 
187  if ($this->alias === null)
188  {
189  $this->alias = self::getAliasByChain($this);
190  }
191 
192  return $this->alias;
193  }
194 
195  public function setCustomAlias($alias)
196  {
197  $this->custom_alias = $alias;
198  }
199 
200  /**
201  * @param Entity $init_entity
202  * @param $definition
203  *
204  * @return Chain
205  * @throws Main\ArgumentException
206  * @throws SystemException
207  */
208  public static function getChainByDefinition(Entity $init_entity, $definition)
209  {
210  if (!is_string($definition))
211  {
212  throw new Main\ArgumentException('String expected, but `'.gettype($definition).'` is given.');
213  }
214 
215  $chain = new Chain;
216  $chain->addElement(new ChainElement($init_entity));
217 
218  $def_elements = explode('.', $definition);
219  $def_elements_size = count($def_elements);
220 
221  $prev_entity = $init_entity;
222 
223  $i = 0;
224 
225  foreach ($def_elements as &$def_element)
226  {
227  $is_last_elem = (++$i == $def_elements_size);
228 
229  $not_found = false;
230 
231  // all elements should be a Reference field or Entity
232  // normal (scalar) field can only be the last element
233 
234  if ($prev_entity->hasField($def_element))
235  {
236  // field has been found at current entity
237  $field = $prev_entity->getField($def_element);
238 
239  if ($field instanceof Reference)
240  {
241  $prev_entity = $field->getRefEntity();
242  }
243  elseif ($field instanceof ExpressionField)
244  {
245  // expr can be in the middle too
246  }
247  elseif ($field instanceof OneToMany)
248  {
249  $prev_entity = $field->getRefEntity();
250  }
251  elseif ($field instanceof ManyToMany)
252  {
253  $prev_entity = $field->getRefEntity();
254  }
255  elseif (!$is_last_elem)
256  {
257  throw new SystemException(sprintf(
258  'Normal fields can be only the last in chain, `%s` %s is not the last.',
259  $field->getName(), get_class($field)
260  ));
261  }
262 
263  if ($is_last_elem && $field instanceof ExpressionField)
264  {
265  // we should have own copy of build_from_chains to set join aliases there
266  $field = clone $field;
267  }
268 
269  $chain->addElement(new ChainElement($field));
270  }
271  elseif (Entity::isExists($def_element)
272  && Entity::getInstance($def_element)->getReferencesCountTo($prev_entity->getName()) == 1
273  )
274  {
275  // def_element is another entity with only 1 reference to current entity
276  // need to identify Reference field
277  $ref_entity = Entity::getInstance($def_element);
278  $field = end($ref_entity->getReferencesTo($prev_entity->getName()));
279 
280  $prev_entity = $ref_entity;
281 
282  $chain->addElement(new ChainElement(
283  array($ref_entity, $field)
284  ));
285  }
286  elseif ( ($pos_wh = strpos($def_element, ':')) > 0 )
287  {
288  $ref_entity_name = substr($def_element, 0, $pos_wh);
289 
290  if (strpos($ref_entity_name, '\\') === false)
291  {
292  // if reference has no namespace, then it'is in the namespace of previous entity
293  $ref_entity_name = $prev_entity->getNamespace().$ref_entity_name;
294  }
295 
296  if (
297  Entity::isExists($ref_entity_name)
298  && Entity::getInstance($ref_entity_name)->hasField($ref_field_name = substr($def_element, $pos_wh + 1))
299  && Entity::getInstance($ref_entity_name)->getField($ref_field_name) instanceof Reference
300  )
301  {
302  /** @var Reference $reference */
303  $reference = Entity::getInstance($ref_entity_name)->getField($ref_field_name);
304 
305  if (
306  $reference->getRefEntity()->getFullName() == $prev_entity->getFullName() ||
307  is_subclass_of(
308  $prev_entity->getDataClass(),
309  $reference->getRefEntity()->getDataClass()
310  )
311  )
312  {
313  // chain element is another entity with >1 references to current entity
314  // def like NewsArticle:AUTHOR, NewsArticle:LAST_COMMENTER
315  // NewsArticle - entity, AUTHOR and LAST_COMMENTER - Reference fields
316  $chain->addElement(new ChainElement(array(
317  Entity::getInstance($ref_entity_name),
318  Entity::getInstance($ref_entity_name)->getField($ref_field_name)
319  )));
320 
321  $prev_entity = Entity::getInstance($ref_entity_name);
322  }
323  else
324  {
325  $not_found = true;
326  }
327  }
328  else
329  {
330  $not_found = true;
331  }
332 
333  }
334  elseif ($def_element == '*' && $is_last_elem)
335  {
336  continue;
337  }
338  else
339  {
340  // unknown chain
341  $not_found = true;
342  }
343 
344  if ($not_found)
345  {
346  throw new SystemException(sprintf(
347  'Unknown field definition `%s` (%s) for %s Entity.',
348  $def_element, $definition, $prev_entity->getFullName()
349  ), 100);
350  }
351  }
352 
353  return $chain;
354  }
355 
356  public static function getDefinitionByChain(Chain $chain)
357  {
358  return join('.', static::getDefinitionPartsByChain($chain));
359  }
360 
361  public static function getDefinitionPartsByChain(Chain $chain)
362  {
363  $def = array();
364 
365  // add members of chain except of init entity
366  /** @var $elements ChainElement[] */
367  $elements = array_slice($chain->getAllElements(), 1);
368 
369  foreach ($elements as $element)
370  {
371  //if ($element->getValue() instanceof ExpressionField && $element !== end($elements))
372  {
373  // skip non-last expressions
374  //continue;
375  }
376 
377  $def[] = $element->getDefinitionFragment();
378  }
379 
380  return $def;
381  }
382 
383  public static function appendDefinition($currentDefinition, $newDefinitionPart)
384  {
385  if ($currentDefinition !== '')
386  {
387  $currentDefinition .= '.';
388  }
389 
390  return $currentDefinition.$newDefinitionPart;
391  }
392 
393  public static function getAliasByChain(Chain $chain)
394  {
395  $alias = array();
396 
397  $elements = $chain->getAllElements();
398 
399  // add prefix of init entity
400  if (count($elements) > 2)
401  {
402  $alias[] = $chain->getFirstElement()->getAliasFragment();
403  }
404 
405  // add other members of chain
406  /** @var ChainElement[] $elements */
407  $elements = array_slice($elements, 1);
408 
409  foreach ($elements as $element)
410  {
411  $fragment = $element->getAliasFragment();
412 
413  if($fragment <> '')
414  {
415  $alias[] = $fragment;
416  }
417  }
418 
419  return join('_', $alias);
420  }
421 
422  /**
423  * @param Entity $entity
424  * @param $definition
425  *
426  * @return string
427  * @throws Main\ArgumentException
428  * @throws SystemException
429  */
431  {
432  return self::getChainByDefinition($entity, $definition)->getAlias();
433  }
434 
435  /**
436  * @return bool
437  * @throws SystemException
438  */
439  public function hasAggregation()
440  {
441  $elements = array_reverse($this->chain);
442 
443  foreach ($elements as $element)
444  {
445  /** @var $element ChainElement */
446  if ($element->getValue() instanceof ExpressionField && $element->getValue()->isAggregated())
447  {
448  return true;
449  }
450  }
451 
452  return false;
453  }
454 
455  /**
456  * @return bool
457  * @throws SystemException
458  */
459  public function hasSubquery()
460  {
461  $elements = array_reverse($this->chain);
462 
463  foreach ($elements as $element)
464  {
465  /** @var $element ChainElement */
466  if ($element->getValue() instanceof ExpressionField && $element->getValue()->hasSubquery())
467  {
468  return true;
469  }
470  }
471 
472  return false;
473  }
474 
475  public function isConstant()
476  {
477  return ($this->getLastElement()->getValue() instanceof ExpressionField
478  && $this->getLastElement()->getValue()->isConstant());
479  }
480 
481  public function forceDataDoublingOff()
482  {
483  $this->forcesDataDoublingOff = true;
484  }
485 
486  public function forcesDataDoublingOff()
487  {
489  }
490 
491  /**
492  * @param bool $with_alias
493  *
494  * @return mixed|string
495  * @throws SystemException
496  */
497  public function getSqlDefinition($with_alias = false)
498  {
499  $sql_def = $this->getLastElement()->getSqlDefinition();
500 
501  if ($with_alias)
502  {
503  $helper = $this->getLastElement()->getValue()->getEntity()->getConnection()->getSqlHelper();
504  $sql_def .= ' AS ' . $helper->quote($this->getAlias());
505  }
506 
507  return $sql_def;
508  }
509 
510  public function __clone()
511  {
512  $this->custom_alias = null;
513  }
514 
515  public function dump()
516  {
517  echo ' '.' forcesDataDoublingOff: '.($this->forcesDataDoublingOff()?'true':'false');
518  echo PHP_EOL;
519 
520  $i = 0;
521  foreach ($this->chain as $elem)
522  {
523  echo ' '.++$i.'. ';
524  $elem->dump();
525  echo PHP_EOL;
526  }
527  }
528 }
Exception is thrown when function argument is not valid.
static getInstance($entityName)
static appendDefinition($currentDefinition, $newDefinitionPart)
static getDefinitionPartsByChain(Chain $chain)
static getChainByDefinition(Entity $init_entity, $definition)
prependElement(ChainElement $element)
static getAliasByDefinition(Entity $entity, $definition)
Base class for fatal exceptions.