1C-Bitrix 25.700.0
Загрузка...
Поиск...
Не найдено
result.php
См. документацию.
1<?php
8
9namespace Bitrix\Main\ORM\Query;
10
11use Bitrix\Main\ArgumentException;
12use Bitrix\Main\DB\ArrayResult;
13use Bitrix\Main\DB\Result as BaseResult;
14use Bitrix\Main\ORM\Fields\ExpressionField;
15use Bitrix\Main\ORM\Entity;
16use Bitrix\Main\ORM\Fields\Field;
17use Bitrix\Main\ORM\Fields\IReadable;
18use Bitrix\Main\ORM\Fields\Relations\ManyToMany;
19use Bitrix\Main\ORM\Fields\Relations\OneToMany;
20use Bitrix\Main\ORM\Objectify\Collection;
21use Bitrix\Main\ORM\Objectify\IdentityMap;
22use Bitrix\Main\ORM\Objectify\State;
23use Bitrix\Main\ORM\Objectify\EntityObject;
24use Bitrix\Main\ORM\Fields\Relations\Reference;
25use Bitrix\Main\ORM\Fields\ScalarField;
26use Bitrix\Main\SystemException;
27
33class Result extends BaseResult
34{
36 protected $result;
37
39 protected $query;
40
42 protected $selectChainsMap = [];
43
45 protected $objectClass;
46
48 protected $identityMap;
49
51 protected $objectInitPassed = false;
52
54 protected $primaryAliases = [];
55
58
59 public function __construct(Query $query, BaseResult $result)
60 {
61 $this->query = $query;
62 $this->result = $result;
63 }
64
69 {
70 $this->hiddenObjectFields = $hiddenObjectFields;
71 }
72
73 protected function hideObjectFields(&$row)
74 {
75 foreach ($this->hiddenObjectFields as $fieldName)
76 {
77 unset($row[$fieldName]);
78 }
79
80 return $row;
81 }
82
83 public function getFields()
84 {
85 return $this->result->getFields();
86 }
87
88 public function getSelectedRowsCount()
89 {
90 return $this->result->getSelectedRowsCount();
91 }
92
93 protected function fetchRowInternal()
94 {
95 return $this->result->fetchRowInternal();
96 }
97
103 final public function fetchObject()
104 {
105 // TODO when join, add primary and hide it in ARRAY result, but use for OBJECT fetch
106 // e.g. when first fetchObject, remove data modifier that cuts 'unexpected' primary fields
107
108 // TODO wakeup reference objects with only primary if there are enough data in result
109
110 // base object initialization
111 $this->initializeFetchObject();
112
113 // array data
114 $row = $this->result->fetch();
115
116 if (empty($row))
117 {
118 return null;
119 }
120
121 if (is_object($row) && $row instanceof EntityObject)
122 {
123 // all rows have already been fetched in initializeFetchObject
124 return $row;
125 }
126
127 // get primary of base object
128 $basePrimaryValues = [];
129
130 foreach ($this->primaryAliases as $primaryName => $primaryAlias)
131 {
133 $primaryField = $this->query->getEntity()->getField($primaryName);
134 $primaryValue = $primaryField->cast($row[$primaryAlias]);
135
136 $basePrimaryValues[$primaryName] = $primaryValue;
137 }
138
139 // check for object in identity map
140 $baseAddToIM = false;
141 $objectClass = $this->objectClass;
142
144 $object = $this->identityMap->get($objectClass, $basePrimaryValues);
145
146 if (empty($object))
147 {
148 $object = new $objectClass(false);
149
150 // set right state
151 $object->sysChangeState(State::ACTUAL);
152
153 // add to identityMap later, when primary is set
154 $baseAddToIM = true;
155 }
156
158 $relEntityCache = [];
159
160 // go through select chains
161 foreach ($this->query->getSelectChains() as $selectChain)
162 {
163 // object for current chain element, for the first element is object of init entity
164 $currentObject = $object;
165
166 // accumulated definition from the first to the current chain element
167 $currentDefinitionParts = [];
168 $currentDefinition = null;
169
170 // cut first element as long as it is init entity
171 $iterableElements = array_slice($selectChain->getAllElements(), 1);
172
173 // dive deep from the start to the end of chain
174 foreach ($iterableElements as $element)
175 {
176 if ($currentObject === null)
177 {
178 continue;
179 }
180
182 $field = $element->getValue();
183
184 if (!($field instanceof Field))
185 {
186 // ignore old-style back references, OneToMany is expected instead
187 // skip for the next chain
188 continue 2;
189 }
190
191 // actualize current definition
192 $currentDefinitionParts[] = $field->getName();
193 $currentDefinition = join('.', $currentDefinitionParts);
194
195 // is it runtime field? then ->tmpSet()
196 $isRuntimeField = !empty($this->query->getRuntimeChains()[$currentDefinition]);
197
198 if ($field instanceof IReadable)
199 {
200 // for remote objects all values have been already set during compose
201 if ($currentObject !== $object)
202 {
203 continue;
204 }
205
206 // normalize value
207 $value = $field->cast($row[$selectChain->getAlias()]);
208
209 // set value as actual to the object
210 $isRuntimeField
211 ? $currentObject->sysSetRuntime($field->getName(), $value)
212 : $currentObject->sysSetActual($field->getName(), $value);
213 }
214 else
215 {
216 // define remote entity definition
217 // check if this reference has already been woken up
218 // main part of current chain (w/o last element) should be the same
219 if (array_key_exists($currentDefinition, $relEntityCache))
220 {
221 $currentObject = $relEntityCache[$currentDefinition];
222 continue;
223 } // else it will be set after object identification
224
225 // define remote entity of reference
226 $remoteEntity = $field->getRefEntity();
227
228 // define values and primary of remote object
229 // we can set all values at one time and skip other iterations with values of this object
230 $remotePrimary = $remoteEntity->getPrimaryArray();
231 $remoteObjectValues = [];
232 $remotePrimaryValues = [];
233
234 foreach ($this->selectChainsMap[$currentDefinition] as $remoteChain)
235 {
237 $remoteField = $remoteChain->getLastElement()->getValue();
238 $remoteValue = $row[$remoteChain->getAlias()];
239
240 $remoteObjectValues[$remoteField->getName()] = $remoteValue;
241 }
242
243 foreach ($remotePrimary as $primaryName)
244 {
245 if (!array_key_exists($primaryName, $remoteObjectValues))
246 {
247 throw new SystemException(sprintf(
248 'Primary of %s was not found in database result', $remoteEntity->getDataClass()
249 ));
250 }
251
252 $remotePrimaryValues[$primaryName] = $remoteObjectValues[$primaryName];
253 }
254
255 // compose relative object
256 if ($field instanceof Reference)
257 {
258 // get object via identity map
259 $remoteObject = $this->composeRemoteObject($remoteEntity, $remotePrimaryValues, $remoteObjectValues);
260
261 // set remoteObject to baseObject
262 $isRuntimeField
263 ? $currentObject->sysSetRuntime($field->getName(), $remoteObject)
264 : $currentObject->sysSetActual($field->getName(), $remoteObject);
265 }
266 elseif ($field instanceof OneToMany || $field instanceof ManyToMany)
267 {
268 // get collection of remote objects
269 if ($isRuntimeField)
270 {
271 if (empty($currentObject->sysGetRuntime($field->getName())))
272 {
273 // create new collection and set as value for current object
275 $collection = $remoteEntity->createCollection();
276 $currentObject->sysSetRuntime($field->getName(), $collection);
277 }
278 else
279 {
280 $collection = $currentObject->sysGetRuntime($field->getName());
281 }
282 }
283 else
284 {
285 if (empty($currentObject->sysGetValue($field->getName())))
286 {
287 // create new collection and set as value for current object
289 $collection = $remoteEntity->createCollection();
290
291 // collection should be filled if there are no LIMIT and relation filter in query
292 if ($this->query->getLimit() === null)
293 {
294 // noting in filter should start with $currentDefinition
295 $noRelationInFilter = true;
296
297 foreach ($this->query->getFilterChains() as $chain)
298 {
299 if (str_starts_with($chain->getDefinition(), $currentDefinition))
300 {
301 $noRelationInFilter = false;
302 break;
303 }
304 }
305
306 if ($noRelationInFilter)
307 {
308 // now we are sure the set is complete
309 $collection->sysSetFilled();
310 }
311 }
312
313 $currentObject->sysSetActual($field->getName(), $collection);
314 }
315 else
316 {
317 $collection = $currentObject->sysGetValue($field->getName());
318 }
319 }
320
321 // define remote object
322 if (current($remotePrimaryValues) === null || !$collection->hasByPrimary($remotePrimaryValues))
323 {
324 // get object via identity map
325 $remoteObject = $this->composeRemoteObject($remoteEntity, $remotePrimaryValues, $remoteObjectValues);
326
327 // add to collection
328 if ($remoteObject !== null)
329 {
330 $collection->sysAddActual($remoteObject);
331 }
332 }
333 else
334 {
335 $remoteObject = $collection->getByPrimary($remotePrimaryValues);
336
337 // it may be necessary to add new values to the object
338 foreach ($remoteObjectValues as $fieldName => $objectValue)
339 {
340 if (!$remoteObject->sysHasValue($fieldName))
341 {
342 $field = $remoteEntity->getField($fieldName);
343 $castValue = $field->cast($objectValue);
344
345 $remoteObject->sysSetActual($fieldName, $castValue);
346 }
347 }
348 }
349 }
350 else
351 {
352 throw new SystemException('Unknown chain element value while fetching object');
353 }
354
355 // switch current object, further chain elements belong to this object
356 $currentObject = $remoteObject;
357
358 // save as ready object for current row
359 $relEntityCache[$currentDefinition] = $remoteObject;
360 }
361 }
362 }
363
364 if ($baseAddToIM)
365 {
366 // save to identityMap
367 $this->identityMap->put($object);
368 }
369
370 return $object;
371 }
372
377 final public function fetchCollection()
378 {
379 // base object initialization
380 $this->initializeFetchObject(true);
381
383 $collection = $this->query->getEntity()->createCollection();
384
385 while ($object = $this->fetchObject())
386 {
387 $collection->sysAddActual($object);
388 }
389
390 return $collection;
391 }
392
400 protected function initializeFetchObject($asCollection = false)
401 {
402 if (empty($this->objectInitPassed))
403 {
404 // validate query
405 if (!empty($this->query->getGroupChains()))
406 {
407 throw new SystemException(
408 'Result of query with aggregation could not be fetched as an object'
409 );
410 }
411
412 // initialize
413 if (empty($this->identityMap))
414 {
415 // identity map could have been set before first fetch
416 $this->identityMap = new IdentityMap;
417 }
418
419 $this->objectClass = $this->query->getEntity()->getObjectClass();
420
421 $this->buildSelectChainsMap();
422 $this->definePrimaryAliases();
423
424 // values will be cast anyway based on original fields, not just associated with column types
425 //$this->setStrictValueConverters();
426
427 $this->objectInitPassed = true;
428
429 // if there are back references, fetch everything and make virtual ArrayResult
430 if (!$asCollection && $this->query->hasBackReference())
431 {
433 $collection = $this->fetchCollection();
434
435 // remember original result
436 $originalResult = $this->result;
437
438 $this->result = new ArrayResult($collection->getAll());
439
440 // recover count total
441 try
442 {
443 if ($originalResult->getCount())
444 {
445 $this->result->setCount($originalResult->getCount());
446 }
447 }
448 catch (\Bitrix\Main\ObjectPropertyException $e) {}
449 }
450 }
451 }
452
456 protected function buildSelectChainsMap()
457 {
458 foreach ($this->query->getSelectChains() as $selectChain)
459 {
460 $this->selectChainsMap[$selectChain->getDefinition(-1)][] = $selectChain;
461 }
462 }
463
467 protected function definePrimaryAliases()
468 {
469 $primaryNames = $this->query->getEntity()->getPrimaryArray();
470
471 foreach ($this->query->getSelectChains() as $selectChain)
472 {
473 $field = $selectChain->getLastElement()->getValue();
474
475 // get 0-level simple fields: entity + field
476 if ($field->getEntity()->getDataClass() === $this->query->getEntity()->getDataClass()
477 && in_array($field->getName(), $primaryNames))
478 {
479 $this->primaryAliases[$field->getName()] = $selectChain->getAlias();
480
481 if (count($this->primaryAliases) == count($primaryNames))
482 {
483 break;
484 }
485 }
486 }
487
488 if (count($this->primaryAliases) != count($primaryNames))
489 {
490 throw new SystemException(sprintf(
491 'Primary of %s was not found in database result', $this->query->getEntity()->getDataClass()
492 ));
493 }
494 }
495
499 protected function setStrictValueConverters()
500 {
501 foreach ($this->query->getSelectChains() as $selectChain)
502 {
503 $alias = $selectChain->getAlias();
504
505 if (!isset($this->result->converters[$alias]))
506 {
507 $this->result->converters[$alias] = [
508 $this->result->getFields()[$alias],
509 'convertValueFromDb'
510 ];
511 }
512 }
513 }
514
524 protected function composeRemoteObject($entity, $primaryValues, $objectValues)
525 {
526 // if null primary then return null
527 if (current($primaryValues) === null)
528 {
529 return null;
530 }
531
532 // try to get remote object from identity map
534 $objectClass = $entity->getObjectClass();
535 $remoteObject = $this->identityMap->get($objectClass, $primaryValues);
536
537 // do we have a new object to add to identity map
538 $addToIM = false;
539
540 if (empty($remoteObject))
541 {
542 // define new object
543 $remoteObject = new $objectClass(false);
544
545 // set right state
546 $remoteObject->sysChangeState(State::ACTUAL);
547
548 // add to identityMap later, when primary is set
549 $addToIM = true;
550 }
551
552 // set all values of remote object
553 foreach ($objectValues as $fieldName => $objectValue)
554 {
556 $field = $entity->getField($fieldName);
557 $castValue = $field->cast($objectValue);
558
559 $remoteObject->sysSetActual($fieldName, $castValue);
560 }
561
562 // save to identityMap
563 if ($addToIM)
564 {
565 $this->identityMap->put($remoteObject);
566 }
567
568 return $remoteObject;
569 }
570
579 {
580 $this->identityMap = $map;
581
582 return $this;
583 }
584
588 public function getIdentityMap()
589 {
590 return $this->identityMap;
591 }
592
593 // decorate other methods
594 public function getResource()
595 {
596 return $this->result->getResource();
597 }
598
599 public function setReplacedAliases(array $replacedAliases)
600 {
601 $this->result->setReplacedAliases($replacedAliases);
602 }
603
604 public function addReplacedAliases(array $replacedAliases)
605 {
606 $this->result->addReplacedAliases($replacedAliases);
607 }
608
609 public function setSerializedFields(array $serializedFields)
610 {
611 $this->result->setSerializedFields($serializedFields);
612 }
613
614 public function addFetchDataModifier($fetchDataModifier)
615 {
616 $this->result->addFetchDataModifier($fetchDataModifier);
617 }
618
619 public function fetchRaw()
620 {
621 return $this->result->fetchRaw();
622 }
623
624 public function fetch(\Bitrix\Main\Text\Converter $converter = null)
625 {
626 $row = $this->result->fetch($converter);
627
628 if ($row && !empty($this->hiddenObjectFields))
629 {
630 return $this->hideObjectFields($row);
631 }
632
633 return $row;
634 }
635
636 public function fetchAll(\Bitrix\Main\Text\Converter $converter = null)
637 {
638 if (empty($this->hiddenObjectFields))
639 {
640 return $this->result->fetchAll($converter);
641 }
642 else
643 {
644 $data = $this->result->fetchAll($converter);
645
646 foreach ($data as &$row)
647 {
648 $this->hideObjectFields($row);
649 }
650
651 return $data;
652 }
653
654 }
655
656 public function getTrackerQuery()
657 {
658 return $this->result->getTrackerQuery();
659 }
660
661 public function getConverters()
662 {
663 return $this->result->getConverters();
664 }
665
666 public function setConverters($converters)
667 {
668 $this->result->setConverters($converters);
669 }
670
671 public function setCount($n)
672 {
673 $this->result->setCount($n);
674 }
675
676 public function getCount()
677 {
678 return $this->result->getCount();
679 }
680
681 public function getIterator(): \Traversable
682 {
683 return $this->result->getIterator();
684 }
685
686}
setHiddenObjectFields($hiddenObjectFields)
Определения result.php:68
hideObjectFields(&$row)
Определения result.php:73
setIdentityMap(IdentityMap $map)
Определения result.php:578
setSerializedFields(array $serializedFields)
Определения result.php:609
$hiddenObjectFields
Определения result.php:57
getConverters()
Определения result.php:661
setConverters($converters)
Определения result.php:666
fetch(\Bitrix\Main\Text\Converter $converter=null)
Определения result.php:624
buildSelectChainsMap()
Определения result.php:456
addFetchDataModifier($fetchDataModifier)
Определения result.php:614
getIdentityMap()
Определения result.php:588
setReplacedAliases(array $replacedAliases)
Определения result.php:599
getSelectedRowsCount()
Определения result.php:88
fetchRowInternal()
Определения result.php:93
definePrimaryAliases()
Определения result.php:467
setStrictValueConverters()
Определения result.php:499
getIterator()
Определения result.php:681
setCount($n)
Определения result.php:671
getResource()
Определения result.php:594
getTrackerQuery()
Определения result.php:656
$primaryAliases
Определения result.php:54
$objectInitPassed
Определения result.php:51
__construct(Query $query, BaseResult $result)
Определения result.php:59
addReplacedAliases(array $replacedAliases)
Определения result.php:604
fetchAll(\Bitrix\Main\Text\Converter $converter=null)
Определения result.php:636
$selectChainsMap
Определения result.php:42
getFields()
Определения result.php:83
$data['IS_AVAILABLE']
Определения .description.php:13
</td ></tr ></table ></td ></tr >< tr >< td class="bx-popup-label bx-width30"><?=GetMessage("PAGE_NEW_TAGS")?> array( $site)
Определения file_new.php:804
$result
Определения get_property_values.php:14
$entity
$map
Определения config.php:5
$value
Определения Param.php:39
Определения chain.php:3
Определения base32.php:2
if( $daysToExpire >=0 &&$daysToExpire< 60 elseif)( $daysToExpire< 0)
Определения prolog_main_admin.php:393
</p ></td >< td valign=top style='border-top:none;border-left:none;border-bottom:solid windowtext 1.0pt;border-right:solid windowtext 1.0pt;padding:0cm 2.0pt 0cm 2.0pt;height:9.0pt'>< p class=Normal align=center style='margin:0cm;margin-bottom:.0001pt;text-align:center;line-height:normal'>< a name=ТекстовоеПоле54 ></a ><?=($taxRate > count( $arTaxList) > 0) ? $taxRate."%"
Определения waybill.php:936
$n
Определения update_log.php:107