Bitrix-D7 23.9
 
Загрузка...
Поиск...
Не найдено
result.php
1<?php
10
13use \Bitrix\Main\DB\Result as BaseResult;
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
68 public function setHiddenObjectFields($hiddenObjectFields)
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 has 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 (strpos($chain->getDefinition(), $currentDefinition) === 0)
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 }
338 else
339 {
340 throw new SystemException('Unknown chain element value while fetching object');
341 }
342
343 // switch current object, further chain elements belong to this object
344 $currentObject = $remoteObject;
345
346 // save as ready object for current row
347 $relEntityCache[$currentDefinition] = $remoteObject;
348 }
349 }
350 }
351
352 if ($baseAddToIM)
353 {
354 // save to identityMap
355 $this->identityMap->put($object);
356 }
357
358 return $object;
359 }
360
365 final public function fetchCollection()
366 {
367 // base object initialization
368 $this->initializeFetchObject(true);
369
371 $collection = $this->query->getEntity()->createCollection();
372
373 while ($object = $this->fetchObject())
374 {
375 $collection->sysAddActual($object);
376 }
377
378 return $collection;
379 }
380
388 protected function initializeFetchObject($asCollection = false)
389 {
390 if (empty($this->objectInitPassed))
391 {
392 // validate query
393 if (!empty($this->query->getGroupChains()))
394 {
395 throw new SystemException(
396 'Result of query with aggregation could not be fetched as an object'
397 );
398 }
399
400 // initialize
401 if (empty($this->identityMap))
402 {
403 // identity map could have been set before first fetch
404 $this->identityMap = new IdentityMap;
405 }
406
407 $this->objectClass = $this->query->getEntity()->getObjectClass();
408
409 $this->buildSelectChainsMap();
410 $this->definePrimaryAliases();
411
412 // values will be cast anyway based on original fields, not just associated with column types
413 //$this->setStrictValueConverters();
414
415 $this->objectInitPassed = true;
416
417 // if there are back references, fetch everything and make virtual ArrayResult
418 if (!$asCollection && $this->query->hasBackReference())
419 {
421 $collection = $this->fetchCollection();
422
423 // remember original result
424 $originalResult = $this->result;
425
426 $this->result = new ArrayResult($collection->getAll());
427
428 // recover count total
429 try
430 {
431 if ($originalResult->getCount())
432 {
433 $this->result->setCount($originalResult->getCount());
434 }
435 }
436 catch (\Bitrix\Main\ObjectPropertyException $e) {}
437 }
438 }
439 }
440
444 protected function buildSelectChainsMap()
445 {
446 foreach ($this->query->getSelectChains() as $selectChain)
447 {
448 $this->selectChainsMap[$selectChain->getDefinition(-1)][] = $selectChain;
449 }
450 }
451
455 protected function definePrimaryAliases()
456 {
457 $primaryNames = $this->query->getEntity()->getPrimaryArray();
458
459 foreach ($this->query->getSelectChains() as $selectChain)
460 {
461 $field = $selectChain->getLastElement()->getValue();
462
463 // get 0-level simple fields: entity + field
464 if ($field->getEntity()->getDataClass() === $this->query->getEntity()->getDataClass()
465 && in_array($field->getName(), $primaryNames))
466 {
467 $this->primaryAliases[$field->getName()] = $selectChain->getAlias();
468
469 if (count($this->primaryAliases) == count($primaryNames))
470 {
471 break;
472 }
473 }
474 }
475
476 if (count($this->primaryAliases) != count($primaryNames))
477 {
478 throw new SystemException(sprintf(
479 'Primary of %s was not found in database result', $this->query->getEntity()->getDataClass()
480 ));
481 }
482 }
483
487 protected function setStrictValueConverters()
488 {
489 foreach ($this->query->getSelectChains() as $selectChain)
490 {
491 $alias = $selectChain->getAlias();
492
493 if (!isset($this->result->converters[$alias]))
494 {
495 $this->result->converters[$alias] = [
496 $this->result->getFields()[$alias],
497 'convertValueFromDb'
498 ];
499 }
500 }
501 }
502
512 protected function composeRemoteObject($entity, $primaryValues, $objectValues)
513 {
514 // if null primary then return null
515 if (current($primaryValues) === null)
516 {
517 return null;
518 }
519
520 // try to get remote object from identity map
522 $objectClass = $entity->getObjectClass();
523 $remoteObject = $this->identityMap->get($objectClass, $primaryValues);
524
525 // do we have a new object to add to identity map
526 $addToIM = false;
527
528 if (empty($remoteObject))
529 {
530 // define new object
531 $remoteObject = new $objectClass(false);
532
533 // set right state
534 $remoteObject->sysChangeState(State::ACTUAL);
535
536 // add to identityMap later, when primary is set
537 $addToIM = true;
538 }
539
540 // set all values of remote object
541 foreach ($objectValues as $fieldName => $objectValue)
542 {
544 $field = $entity->getField($fieldName);
545 $castValue = $field->cast($objectValue);
546
547 $remoteObject->sysSetActual($fieldName, $castValue);
548 }
549
550 // save to identityMap
551 if ($addToIM)
552 {
553 $this->identityMap->put($remoteObject);
554 }
555
556 return $remoteObject;
557 }
558
566 public function setIdentityMap(IdentityMap $map)
567 {
568 $this->identityMap = $map;
569
570 return $this;
571 }
572
576 public function getIdentityMap()
577 {
578 return $this->identityMap;
579 }
580
581 // decorate other methods
582 public function getResource()
583 {
584 return $this->result->getResource();
585 }
586
587 public function setReplacedAliases(array $replacedAliases)
588 {
589 $this->result->setReplacedAliases($replacedAliases);
590 }
591
592 public function addReplacedAliases(array $replacedAliases)
593 {
594 $this->result->addReplacedAliases($replacedAliases);
595 }
596
597 public function setSerializedFields(array $serializedFields)
598 {
599 $this->result->setSerializedFields($serializedFields);
600 }
601
602 public function addFetchDataModifier($fetchDataModifier)
603 {
604 $this->result->addFetchDataModifier($fetchDataModifier);
605 }
606
607 public function fetchRaw()
608 {
609 return $this->result->fetchRaw();
610 }
611
612 public function fetch(\Bitrix\Main\Text\Converter $converter = null)
613 {
614 $row = $this->result->fetch($converter);
615
616 if ($row && !empty($this->hiddenObjectFields))
617 {
618 return $this->hideObjectFields($row);
619 }
620
621 return $row;
622 }
623
624 public function fetchAll(\Bitrix\Main\Text\Converter $converter = null)
625 {
626 if (empty($this->hiddenObjectFields))
627 {
628 return $this->result->fetchAll($converter);
629 }
630 else
631 {
632 $data = $this->result->fetchAll($converter);
633
634 foreach ($data as &$row)
635 {
636 $this->hideObjectFields($row);
637 }
638
639 return $data;
640 }
641
642 }
643
644 public function getTrackerQuery()
645 {
646 return $this->result->getTrackerQuery();
647 }
648
649 public function getConverters()
650 {
651 return $this->result->getConverters();
652 }
653
654 public function setConverters($converters)
655 {
656 $this->result->setConverters($converters);
657 }
658
659 public function setCount($n)
660 {
661 $this->result->setCount($n);
662 }
663
664 public function getCount()
665 {
666 return $this->result->getCount();
667 }
668
669 public function getIterator(): \Traversable
670 {
671 return $this->result->getIterator();
672 }
673
674}
setHiddenObjectFields($hiddenObjectFields)
Definition result.php:68
setIdentityMap(IdentityMap $map)
Definition result.php:566
setSerializedFields(array $serializedFields)
Definition result.php:597
fetch(\Bitrix\Main\Text\Converter $converter=null)
Definition result.php:612
addFetchDataModifier($fetchDataModifier)
Definition result.php:602
setReplacedAliases(array $replacedAliases)
Definition result.php:587
__construct(Query $query, BaseResult $result)
Definition result.php:59
addReplacedAliases(array $replacedAliases)
Definition result.php:592
fetchAll(\Bitrix\Main\Text\Converter $converter=null)
Definition result.php:624