1C-Bitrix 25.700.0
Загрузка...
Поиск...
Не найдено
collection.php
См. документацию.
1<?php
8
10
23
32abstract class Collection implements \ArrayAccess, \Iterator, \Countable
33{
38 static public $dataClass;
39
41 protected $_entity;
42
44 protected $_objectClass;
45
47 protected $_objects = [];
48
50 protected $_isFilled = false;
51
54
57
60
63
65 const OBJECT_ADDED = 1;
66
68 const OBJECT_REMOVED = 2;
69
78 final public function __construct(Entity $entity = null)
79 {
80 if (empty($entity))
81 {
82 if (__CLASS__ !== get_called_class())
83 {
84 // custom collection class
85 $dataClass = static::$dataClass;
86 $this->_entity = $dataClass::getEntity();
87 }
88 else
89 {
90 throw new ArgumentException('Entity required when constructing collection');
91 }
92 }
93 else
94 {
95 $this->_entity = $entity;
96 }
97
98 $this->_objectClass = $this->_entity->getObjectClass();
99 $this->_isSinglePrimary = count($this->_entity->getPrimaryArray()) == 1;
100 }
101
102 public function __clone()
103 {
104 $this->_objects = \Bitrix\Main\Type\Collection::clone((array)$this->_objects);
105 $this->_objectsRemoved = \Bitrix\Main\Type\Collection::clone((array)$this->_objectsRemoved);
106 $this->_iterableObjects = null;
107 }
108
115 final public function add(EntityObject $object)
116 {
117 // check object class
118 if (!($object instanceof $this->_objectClass))
119 {
120 throw new ArgumentException(sprintf(
121 'Invalid object class %s for %s collection, expected "%s".',
122 get_class($object), get_class($this), $this->_objectClass
123 ));
124 }
125
126 $srPrimary = $this->sysGetPrimaryKey($object);
127
128 if (!$object->sysHasPrimary())
129 {
130 // object is new and there is no primary yet
131 $object->sysAddOnPrimarySetListener([$this, 'sysOnObjectPrimarySet']);
132 }
133
134 if (empty($this->_objects[$srPrimary])
135 && (!isset($this->_objectsChanges[$srPrimary]) || $this->_objectsChanges[$srPrimary] != static::OBJECT_REMOVED))
136 {
137 $this->_objects[$srPrimary] = $object;
138 $this->_objectsChanges[$srPrimary] = static::OBJECT_ADDED;
139 }
140 elseif (isset($this->_objectsChanges[$srPrimary]) && $this->_objectsChanges[$srPrimary] == static::OBJECT_REMOVED)
141 {
142 // silent add for removed runtime
143 $this->_objects[$srPrimary] = $object;
144
145 unset($this->_objectsChanges[$srPrimary]);
146 unset($this->_objectsRemoved[$srPrimary]);
147 }
148 }
149
157 final public function has(EntityObject $object)
158 {
159 // check object class
160 if (!($object instanceof $this->_objectClass))
161 {
162 throw new ArgumentException(sprintf(
163 'Invalid object class %s for %s collection, expected "%s".',
164 get_class($object), get_class($this), $this->_objectClass
165 ));
166 }
167
168 return array_key_exists($this->sysGetPrimaryKey($object), $this->_objects);
169 }
170
177 final public function hasByPrimary($primary)
178 {
179 $normalizedPrimary = $this->sysNormalizePrimary($primary);
180 return array_key_exists($this->sysSerializePrimaryKey($normalizedPrimary), $this->_objects);
181 }
182
189 final public function getByPrimary($primary)
190 {
191 $normalizedPrimary = $this->sysNormalizePrimary($primary);
192 $serializePrimaryKey = $this->sysSerializePrimaryKey($normalizedPrimary);
193
194 if (isset($this->_objects[$serializePrimaryKey]))
195 {
196 return $this->_objects[$serializePrimaryKey];
197 }
198
199 return null;
200 }
201
205 final public function getAll()
206 {
207 return array_values($this->_objects);
208 }
209
217 final public function remove(EntityObject $object)
218 {
219 // check object class
220 if (!($object instanceof $this->_objectClass))
221 {
222 throw new ArgumentException(sprintf(
223 'Invalid object class %s for %s collection, expected "%s".',
224 get_class($object), get_class($this), $this->_objectClass
225 ));
226 }
227
228 // ignore deleted objects
229 if ($object->state === State::DELETED)
230 {
231 return;
232 }
233
234 $srPrimary = $this->sysGetPrimaryKey($object);
235 $this->sysRemove($srPrimary);
236 }
237
243 final public function removeByPrimary($primary)
244 {
245 $normalizedPrimary = $this->sysNormalizePrimary($primary);
246 $srPrimary = $this->sysSerializePrimaryKey($normalizedPrimary);
247
248 $this->sysRemove($srPrimary);
249 }
250
251 public function sysRemove($srPrimary)
252 {
253 $object = $this->_objects[$srPrimary] ?? null;
254
255 if (empty($object))
256 {
257 $object = $this->entity->wakeUpObject($srPrimary);
258 }
259
260 unset($this->_objects[$srPrimary]);
261
262 if (!isset($this->_objectsChanges[$srPrimary]) || $this->_objectsChanges[$srPrimary] != static::OBJECT_ADDED)
263 {
264 // regular remove
265 $this->_objectsChanges[$srPrimary] = static::OBJECT_REMOVED;
266 $this->_objectsRemoved[$srPrimary] = $object;
267 }
268 elseif (isset($this->_objectsChanges[$srPrimary]) && $this->_objectsChanges[$srPrimary] == static::OBJECT_ADDED)
269 {
270 // silent remove for added runtime
271 unset($this->_objectsChanges[$srPrimary]);
272 unset($this->_objectsRemoved[$srPrimary]);
273 }
274 }
275
285 final public function fill($fields = FieldTypeMask::ALL)
286 {
287 $entityPrimary = $this->_entity->getPrimaryArray();
288
289 $primaryValues = [];
290 $fieldsToSelect = [];
291
292 // if field is the only one
293 if (is_scalar($fields) && !is_numeric($fields))
294 {
295 $fields = [$fields];
296 }
297
298 // collect custom fields to select
299 foreach ($this->_objects as $object)
300 {
301 $idleFields = is_array($fields)
302 ? $object->sysGetIdleFields($fields)
303 : $object->sysGetIdleFieldsByMask($fields);
304
305 if (!empty($idleFields))
306 {
307 $fieldsToSelect = array_unique(array_merge($fieldsToSelect, $idleFields));
308
309 // add object to query
310 $objectPrimary = $object->sysRequirePrimary();
311
312 $primaryValues[] = count($objectPrimary) == 1
313 ? current($objectPrimary)
314 : $objectPrimary;
315 }
316 }
317
318 // add primary to select
319 if (!empty($fieldsToSelect))
320 {
321 $fieldsToSelect = array_unique(array_merge($entityPrimary, $fieldsToSelect));
322
323 // build primary filter
324 $primaryFilter = Query::filter();
325
326 if (count($entityPrimary) == 1)
327 {
328 // IN for single-primary objects
329 $primaryFilter->whereIn($entityPrimary[0], $primaryValues);
330 }
331 else
332 {
333 // OR for multi-primary objects
334 $primaryFilter->logic('or');
335
336 foreach ($primaryValues as $objectPrimary)
337 {
338 // add each object as a separate condition
339 $oneObjectFilter = Query::filter();
340
341 foreach ($objectPrimary as $primaryName => $primaryValue)
342 {
343 $oneObjectFilter->where($primaryName, $primaryValue);
344 }
345
346 $primaryFilter->where($oneObjectFilter);
347 }
348 }
349
350 // build query
351 $dataClass = $this->_entity->getDataClass();
352 $result = $dataClass::query()->setSelect($fieldsToSelect)->where($primaryFilter)->exec();
353
354 // set object to identityMap of result, and it will be partially completed by fetch
355 $im = new IdentityMap;
356
357 foreach ($this->_objects as $object)
358 {
359 $im->put($object);
360 }
361
362 $result->setIdentityMap($im);
363 $result->fetchCollection();
364 }
365
366 // return field value it it was only one
367 if (is_array($fields) && count($fields) == 1 && $this->entity->hasField(current($fields)))
368 {
369 $fieldName = current($fields);
370 $field = $this->entity->getField($fieldName);
371
372 return ($field instanceof Relation)
373 ? $this->sysGetCollection($fieldName)
374 : $this->sysGetList($fieldName);
375 }
376 }
377
378 final public function save($ignoreEvents = false)
379 {
380 $result = new Result;
381
383 $addObjects = [];
384
386 $updateObjects = [];
387
388 foreach ($this->_objects as $object)
389 {
390 if ($object->sysGetState() === State::RAW)
391 {
392 $addObjects[] = ['__object' => $object];
393 }
394 elseif ($object->sysGetState() === State::CHANGED)
395 {
396 $updateObjects[] = $object;
397 }
398 }
399
400 $dataClass = static::$dataClass;
401
402 // multi add
403 if (!empty($addObjects))
404 {
405 $result = $dataClass::addMulti($addObjects, $ignoreEvents);
406 }
407
408 // multi update
409 if (!empty($updateObjects))
410 {
411 $areEqual = true;
412 $primaries = [];
413
414 $dataSample = $updateObjects[0]->collectValues(Values::CURRENT, FieldTypeMask::SCALAR | FieldTypeMask::USERTYPE);
415 asort($dataSample);
416
417 // get only scalar & uf data and check its uniqueness
418 foreach ($updateObjects as $object)
419 {
420 $objectData = $object->collectValues(Values::CURRENT, FieldTypeMask::SCALAR | FieldTypeMask::USERTYPE);
421 asort($objectData);
422
423 if ($dataSample !== $objectData)
424 {
425 $areEqual = false;
426 break;
427 }
428
429 $primaries[] = $object->primary;
430 }
431
432 if ($areEqual)
433 {
434 // one query
435 $result = $dataClass::updateMulti($primaries, $dataSample, $ignoreEvents);
436
437 // post save
438 foreach ($updateObjects as $object)
439 {
440 $object->sysSaveRelations($result);
441 $object->sysPostSave();
442 }
443 }
444 else
445 {
446 // each object separately
447 foreach ($updateObjects as $object)
448 {
449 $objectResult = $object->save();
450
451 if (!$objectResult->isSuccess())
452 {
453 $result->addErrors($objectResult->getErrors());
454 }
455 }
456 }
457 }
458
459 return $result;
460 }
461
471 final public static function wakeUp($rows)
472 {
473 // define object class
474 $dataClass = static::$dataClass;
475 $objectClass = $dataClass::getObjectClass();
476
477 // complete collection
478 $collection = new static;
479
480 foreach ($rows as $row)
481 {
482 $collection->sysAddActual($objectClass::wakeUp($row));
483 }
484
485 return $collection;
486 }
487
498 final public function collectValues(int $valuesType = Values::ALL, int $fieldsMask = FieldTypeMask::ALL, bool $recursive = false): array
499 {
500 $data = [];
501
502 foreach ($this as $item)
503 {
504 $data[$this->sysGetPrimaryKey($item)] = $item->collectValues($valuesType, $fieldsMask, $recursive);
505 }
506
507 return $data;
508 }
509
518 public function __get($name)
519 {
520 switch ($name)
521 {
522 case 'entity':
523 return $this->_entity;
524 case 'dataClass':
525 throw new SystemException('Property `dataClass` should be received as static.');
526 }
527
528 throw new SystemException(sprintf(
529 'Unknown property `%s` for collection `%s`', $name, get_called_class()
530 ));
531 }
532
541 public function __set($name, $value)
542 {
543 switch ($name)
544 {
545 case 'entity':
546 case 'dataClass':
547 throw new SystemException(sprintf(
548 'Property `%s` for collection `%s` is read-only', $name, get_called_class()
549 ));
550 }
551
552 throw new SystemException(sprintf(
553 'Unknown property `%s` for collection `%s`', $name, get_called_class()
554 ));
555 }
556
567 public function __call($name, $arguments)
568 {
569 $first3 = substr($name, 0, 3);
570 $last4 = substr($name, -4);
571
572 // group getter
573 if ($first3 == 'get' && $last4 == 'List')
574 {
575 $fieldName = EntityObject::sysMethodToFieldCase(substr($name, 3, -4));
576
577 if ($fieldName == '')
578 {
579 $fieldName = StringHelper::strtoupper($arguments[0]);
580
581 // check if custom method exists
582 $personalMethodName = $first3.EntityObject::sysFieldToMethodCase($fieldName).$last4;
583
584 if (method_exists($this, $personalMethodName))
585 {
586 return $this->$personalMethodName(...array_slice($arguments, 1));
587 }
588
589 // hard field check
590 $this->entity->getField($fieldName);
591 }
592
593 // check if field exists
594 if ($this->_entity->hasField($fieldName))
595 {
596 return $this->sysGetList($fieldName);
597 }
598 }
599
600 $last10 = substr($name, -10);
601
602 if ($first3 == 'get' && $last10 == 'Collection')
603 {
604 $fieldName = EntityObject::sysMethodToFieldCase(substr($name, 3, -10));
605
606 if ($fieldName == '')
607 {
608 $fieldName = StringHelper::strtoupper($arguments[0]);
609
610 // check if custom method exists
611 $personalMethodName = $first3.EntityObject::sysFieldToMethodCase($fieldName).$last10;
612
613 if (method_exists($this, $personalMethodName))
614 {
615 return $this->$personalMethodName(...array_slice($arguments, 1));
616 }
617
618 // hard field check
619 $this->entity->getField($fieldName);
620 }
621
622 // check if field exists
623 if ($this->_entity->hasField($fieldName) && $this->_entity->getField($fieldName) instanceof Relation)
624 {
625 return $this->sysGetCollection($fieldName);
626 }
627 }
628
629 $first4 = substr($name, 0, 4);
630
631 // filler
632 if ($first4 == 'fill')
633 {
634 $fieldName = EntityObject::sysMethodToFieldCase(substr($name, 4));
635
636 // check if field exists
637 if ($this->_entity->hasField($fieldName))
638 {
639 return $this->fill([$fieldName]);
640 }
641 }
642
643 throw new SystemException(sprintf(
644 'Unknown method `%s` for object `%s`', $name, get_called_class()
645 ));
646 }
647
656 public function sysAddActual(EntityObject $object)
657 {
658 $this->_objects[$this->sysGetPrimaryKey($object)] = $object;
659 }
660
666 public function sysOnObjectPrimarySet($object)
667 {
668 $srHash = spl_object_hash($object);
669 $srPrimary = $this->sysSerializePrimaryKey($object->primary);
670
671 if (isset($this->_objects[$srHash]))
672 {
673 // rewrite object
674 unset($this->_objects[$srHash]);
675 $this->_objects[$srPrimary] = $object;
676
677 // rewrite changes
678 if (isset($this->_objectsChanges[$srHash]))
679 {
680 $this->_objectsChanges[$srPrimary] = $this->_objectsChanges[$srHash];
681 unset($this->_objectsChanges[$srHash]);
682 }
683
684 // rewrite removed registry
685 if (isset($this->_objectsRemoved[$srHash]))
686 {
687 $this->_objectsRemoved[$srPrimary] = $this->_objectsRemoved[$srHash];
688 unset($this->_objectsRemoved[$srHash]);
689 }
690 }
691 }
692
698 public function sysIsFilled()
699 {
700 return $this->_isFilled;
701 }
702
708 public function sysIsChanged()
709 {
710 return !empty($this->_objectsChanges);
711 }
712
719 public function sysGetChanges()
720 {
721 $changes = [];
722
723 foreach ($this->_objectsChanges as $srPrimary => $changeCode)
724 {
725 if (isset($this->_objects[$srPrimary]))
726 {
727 $changedObject = $this->_objects[$srPrimary];
728 }
729 elseif (isset($this->_objectsRemoved[$srPrimary]))
730 {
731 $changedObject = $this->_objectsRemoved[$srPrimary];
732 }
733 else
734 {
735 $changedObject = null;
736 }
737
738 if (empty($changedObject))
739 {
740 throw new SystemException(sprintf(
741 'Object with primary `%s` was not found in `%s` collection', $srPrimary, get_class($this)
742 ));
743 }
744
745 $changes[] = [$changedObject, $changeCode];
746 }
747
748 return $changes;
749 }
750
756 public function sysResetChanges($rollback = false)
757 {
758 if ($rollback)
759 {
760 foreach ($this->_objectsChanges as $srPrimary => $changeCode)
761 {
762 if ($changeCode === static::OBJECT_ADDED)
763 {
764 unset($this->_objects[$srPrimary]);
765 }
766 elseif ($changeCode === static::OBJECT_REMOVED)
767 {
768 $this->_objects[$srPrimary] = $this->_objectsRemoved[$srPrimary];
769 }
770 }
771 }
772
773 $this->_objectsChanges = [];
774 $this->_objectsRemoved = [];
775 }
776
783 protected function sysGetList($fieldName)
784 {
785 $values = [];
786
787 // collect field values
788 foreach ($this->_objects as $objectPrimary => $object)
789 {
790 $values[] = $object->sysGetValue($fieldName);
791 }
792
793 return $values;
794 }
795
803 protected function sysGetCollection($fieldName)
804 {
806 $field = $this->_entity->getField($fieldName);
807
809 $values = $field->getRefEntity()->createCollection();
810
811 // collect field values
812 foreach ($this->_objects as $objectPrimary => $object)
813 {
814 $value = $object->sysGetValue($fieldName);
815
816 if ($value instanceof EntityObject)
817 {
818 $values[] = $value;
819 }
820 elseif ($value instanceof Collection)
821 {
822 foreach ($value->getAll() as $remoteObject)
823 {
824 $values[] = $remoteObject;
825 }
826 }
827 }
828
829 return $values;
830 }
831
835 public function sysReviseDeletedObjects()
836 {
837 // clear from deleted objects
838 foreach ($this->_objects as $k => $object)
839 {
840 if ($object->state === State::DELETED)
841 {
842 unset($this->_objects[$k]);
843 }
844 }
845 }
846
852 public function sysSetFilled($value = true)
853 {
854 $this->_isFilled = $value;
855 }
856
865 protected function sysNormalizePrimary($primary)
866 {
867 // normalize primary
868 $primaryNames = $this->_entity->getPrimaryArray();
869
870 if (!is_array($primary))
871 {
872 if (count($primaryNames) > 1)
873 {
874 throw new ArgumentException(sprintf(
875 'Only one value of primary found, when entity %s has %s primary keys',
876 $this->_entity->getDataClass(), count($primaryNames)
877 ));
878 }
879
880 $primary = [$primaryNames[0] => $primary];
881 }
882
883 // check in $this->objects
884 $normalizedPrimary = [];
885
886 foreach ($primaryNames as $primaryName)
887 {
889 $field = $this->_entity->getField($primaryName);
890 $normalizedPrimary[$primaryName] = $field->cast($primary[$primaryName]);
891 }
892
893 return $normalizedPrimary;
894 }
895
905 protected function sysGetPrimaryKey(EntityObject $object)
906 {
907 if ($object->sysHasPrimary())
908 {
909 return $this->sysSerializePrimaryKey($object->primary);
910 }
911 else
912 {
913 return spl_object_hash($object);
914 }
915 }
916
925 protected function sysSerializePrimaryKey($primary)
926 {
927 if ($this->_isSinglePrimary)
928 {
929 return current($primary);
930 }
931
932 return Json::encode(array_values($primary));
933 }
934
944 public function offsetSet($offset, $value): void
945 {
946 $this->add($value);
947 }
948
957 public function offsetExists($offset): bool
958 {
959 throw new NotImplementedException;
960 }
961
969 public function offsetUnset($offset): void
970 {
971 throw new NotImplementedException;
972 }
973
982 #[\ReturnTypeWillChange]
983 public function offsetGet($offset)
984 {
985 throw new NotImplementedException;
986 }
987
991 public function rewind(): void
992 {
993 $this->_iterableObjects = $this->_objects;
994 reset($this->_iterableObjects);
995 }
996
1002 #[\ReturnTypeWillChange]
1003 public function current()
1004 {
1005 if ($this->_iterableObjects === null)
1006 {
1007 $this->_iterableObjects = $this->_objects;
1008 }
1009
1010 return current($this->_iterableObjects);
1011 }
1012
1018 #[\ReturnTypeWillChange]
1019 public function key()
1020 {
1021 return key($this->_iterableObjects);
1022 }
1023
1027 public function next(): void
1028 {
1029 next($this->_iterableObjects);
1030 }
1031
1037 public function valid(): bool
1038 {
1039 return key($this->_iterableObjects) !== null;
1040 }
1041
1047 public function count(): int
1048 {
1049 return count($this->_objects);
1050 }
1051
1056 public function merge(?self $collection): self
1057 {
1058 if (is_null($collection))
1059 {
1060 return $this;
1061 }
1062
1063 if (get_class($this) !== get_class($collection))
1064 {
1065 throw new ArgumentException(
1066 'Invalid object class ' . get_class($collection) . ' for merge, ' . get_class($this) . ' expected .'
1067 );
1068 }
1069
1070 foreach ($collection as $item)
1071 {
1072 $this->add($item);
1073 }
1074
1075 return $this;
1076 }
1077
1078 public function isEmpty(): bool
1079 {
1080 return $this->count() === 0;
1081 }
1082}
merge(?self $collection)
Определения collection.php:1056
collectValues(int $valuesType=Values::ALL, int $fieldsMask=FieldTypeMask::ALL, bool $recursive=false)
Определения collection.php:498
hasByPrimary($primary)
Определения collection.php:177
sysAddActual(EntityObject $object)
Определения collection.php:656
__call($name, $arguments)
Определения collection.php:567
removeByPrimary($primary)
Определения collection.php:243
sysGetPrimaryKey(EntityObject $object)
Определения collection.php:905
has(EntityObject $object)
Определения collection.php:157
sysRemove($srPrimary)
Определения collection.php:251
sysOnObjectPrimarySet($object)
Определения collection.php:666
__construct(Entity $entity=null)
Определения collection.php:78
__set($name, $value)
Определения collection.php:541
add(EntityObject $object)
Определения collection.php:115
static wakeUp($rows)
Определения collection.php:471
sysResetChanges($rollback=false)
Определения collection.php:756
getByPrimary($primary)
Определения collection.php:189
offsetSet($offset, $value)
Определения collection.php:944
fill($fields=FieldTypeMask::ALL)
Определения collection.php:285
sysGetList($fieldName)
Определения collection.php:783
sysSerializePrimaryKey($primary)
Определения collection.php:925
sysSetFilled($value=true)
Определения collection.php:852
const CHANGED
Определения state.php:20
const DELETED
Определения state.php:21
const RAW
Определения state.php:18
const CURRENT
Определения values.php:19
static clone(array $originalArray)
Определения collection.php:191
Определения json.php:9
$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
$name
Определения menu_edit.php:35
Определения ufield.php:9
Определения chain.php:3
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
$rows
Определения options.php:264
$k
Определения template_pdf.php:567
$fields
Определения yandex_run.php:501