32abstract class Collection implements \ArrayAccess, \Iterator, \Countable
82 if (__CLASS__ !== get_called_class())
86 $this->_entity = $dataClass::getEntity();
95 $this->_entity = $entity;
98 $this->_objectClass = $this->_entity->getObjectClass();
99 $this->_isSinglePrimary =
count($this->_entity->getPrimaryArray()) == 1;
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;
118 if (!($object instanceof $this->_objectClass))
121 'Invalid object class %s for %s collection, expected "%s".',
122 get_class($object), get_class($this), $this->_objectClass
128 if (!$object->sysHasPrimary())
131 $object->sysAddOnPrimarySetListener([$this,
'sysOnObjectPrimarySet']);
134 if (empty($this->_objects[$srPrimary])
135 && (!isset($this->_objectsChanges[$srPrimary]) || $this->_objectsChanges[$srPrimary] != static::OBJECT_REMOVED))
137 $this->_objects[$srPrimary] = $object;
138 $this->_objectsChanges[$srPrimary] = static::OBJECT_ADDED;
140 elseif (isset($this->_objectsChanges[$srPrimary]) && $this->_objectsChanges[$srPrimary] == static::OBJECT_REMOVED)
143 $this->_objects[$srPrimary] = $object;
145 unset($this->_objectsChanges[$srPrimary]);
146 unset($this->_objectsRemoved[$srPrimary]);
160 if (!($object instanceof $this->_objectClass))
163 'Invalid object class %s for %s collection, expected "%s".',
164 get_class($object), get_class($this), $this->_objectClass
179 $normalizedPrimary = $this->sysNormalizePrimary($primary);
191 $normalizedPrimary = $this->sysNormalizePrimary($primary);
194 if (isset($this->_objects[$serializePrimaryKey]))
196 return $this->_objects[$serializePrimaryKey];
207 return array_values($this->_objects);
220 if (!($object instanceof $this->_objectClass))
223 'Invalid object class %s for %s collection, expected "%s".',
224 get_class($object), get_class($this), $this->_objectClass
245 $normalizedPrimary = $this->sysNormalizePrimary($primary);
253 $object = $this->_objects[$srPrimary];
257 $object = $this->entity->wakeUpObject($srPrimary);
260 unset($this->_objects[$srPrimary]);
262 if (!isset($this->_objectsChanges[$srPrimary]) || $this->_objectsChanges[$srPrimary] != static::OBJECT_ADDED)
265 $this->_objectsChanges[$srPrimary] = static::OBJECT_REMOVED;
266 $this->_objectsRemoved[$srPrimary] = $object;
268 elseif (isset($this->_objectsChanges[$srPrimary]) && $this->_objectsChanges[$srPrimary] == static::OBJECT_ADDED)
271 unset($this->_objectsChanges[$srPrimary]);
272 unset($this->_objectsRemoved[$srPrimary]);
285 final public function fill($fields = FieldTypeMask::ALL)
287 $entityPrimary = $this->_entity->getPrimaryArray();
290 $fieldsToSelect = [];
293 if (is_scalar($fields) && !is_numeric($fields))
299 foreach ($this->_objects as $object)
301 $idleFields = is_array($fields)
302 ? $object->sysGetIdleFields($fields)
303 : $object->sysGetIdleFieldsByMask($fields);
305 if (!empty($idleFields))
307 $fieldsToSelect = array_unique(array_merge($fieldsToSelect, $idleFields));
310 $objectPrimary = $object->sysRequirePrimary();
312 $primaryValues[] =
count($objectPrimary) == 1
319 if (!empty($fieldsToSelect))
321 $fieldsToSelect = array_unique(array_merge($entityPrimary, $fieldsToSelect));
324 $primaryFilter = Query::filter();
326 if (
count($entityPrimary) == 1)
329 $primaryFilter->whereIn($entityPrimary[0], $primaryValues);
334 $primaryFilter->logic(
'or');
336 foreach ($primaryValues as $objectPrimary)
339 $oneObjectFilter = Query::filter();
341 foreach ($objectPrimary as $primaryName => $primaryValue)
343 $oneObjectFilter->where($primaryName, $primaryValue);
346 $primaryFilter->where($oneObjectFilter);
352 $result = $dataClass::query()->setSelect($fieldsToSelect)->where($primaryFilter)->exec();
357 foreach ($this->_objects as $object)
362 $result->setIdentityMap($im);
363 $result->fetchCollection();
367 if (is_array($fields) &&
count($fields) == 1 && $this->entity->hasField(
current($fields)))
370 $field = $this->entity->getField($fieldName);
373 ? $this->sysGetCollection($fieldName)
378 final public function save($ignoreEvents =
false)
388 foreach ($this->_objects as $object)
392 $addObjects[] = [
'__object' => $object];
396 $updateObjects[] = $object;
403 if (!empty($addObjects))
405 $result = $dataClass::addMulti($addObjects, $ignoreEvents);
409 if (!empty($updateObjects))
414 $dataSample = $updateObjects[0]->collectValues(
Values::CURRENT, FieldTypeMask::SCALAR | FieldTypeMask::USERTYPE);
418 foreach ($updateObjects as $object)
420 $objectData = $object->collectValues(
Values::CURRENT, FieldTypeMask::SCALAR | FieldTypeMask::USERTYPE);
423 if ($dataSample !== $objectData)
429 $primaries[] = $object->primary;
435 $result = $dataClass::updateMulti($primaries, $dataSample, $ignoreEvents);
438 foreach ($updateObjects as $object)
440 $object->sysSaveRelations($result);
441 $object->sysPostSave();
447 foreach ($updateObjects as $object)
449 $objectResult = $object->save();
451 if (!$objectResult->isSuccess())
453 $result->addErrors($objectResult->getErrors());
471 final public static function wakeUp($rows)
475 $objectClass = $dataClass::getObjectClass();
478 $collection =
new static;
480 foreach ($rows as $row)
482 $collection->sysAddActual($objectClass::wakeUp($row));
503 throw new SystemException(
'Property `dataClass` should be received as static.');
507 'Unknown property `%s` for collection `%s`', $name, get_called_class()
519 public function __set($name, $value)
526 'Property `%s` for collection `%s` is read-only', $name, get_called_class()
531 'Unknown property `%s` for collection `%s`', $name, get_called_class()
545 public function __call($name, $arguments)
547 $first3 = substr($name, 0, 3);
548 $last4 = substr($name, -4);
551 if ($first3 ==
'get' && $last4 ==
'List')
553 $fieldName = EntityObject::sysMethodToFieldCase(substr($name, 3, -4));
555 if ($fieldName ==
'')
557 $fieldName = StringHelper::strtoupper($arguments[0]);
560 $personalMethodName = $first3.EntityObject::sysFieldToMethodCase($fieldName).$last4;
562 if (method_exists($this, $personalMethodName))
564 return $this->$personalMethodName(...array_slice($arguments, 1));
568 $this->entity->getField($fieldName);
572 if ($this->_entity->hasField($fieldName))
578 $last10 = substr($name, -10);
580 if ($first3 ==
'get' && $last10 ==
'Collection')
582 $fieldName = EntityObject::sysMethodToFieldCase(substr($name, 3, -10));
584 if ($fieldName ==
'')
586 $fieldName = StringHelper::strtoupper($arguments[0]);
589 $personalMethodName = $first3.EntityObject::sysFieldToMethodCase($fieldName).$last10;
591 if (method_exists($this, $personalMethodName))
593 return $this->$personalMethodName(...array_slice($arguments, 1));
597 $this->entity->getField($fieldName);
601 if ($this->_entity->hasField($fieldName) && $this->_entity->getField($fieldName) instanceof
Relation)
603 return $this->sysGetCollection($fieldName);
607 $first4 = substr($name, 0, 4);
610 if ($first4 ==
'fill')
612 $fieldName = EntityObject::sysMethodToFieldCase(substr($name, 4));
615 if ($this->_entity->hasField($fieldName))
617 return $this->
fill([$fieldName]);
622 'Unknown method `%s` for object `%s`', $name, get_called_class()
646 $srHash = spl_object_hash($object);
649 if (isset($this->_objects[$srHash]))
652 unset($this->_objects[$srHash]);
653 $this->_objects[$srPrimary] = $object;
656 if (isset($this->_objectsChanges[$srHash]))
658 $this->_objectsChanges[$srPrimary] = $this->_objectsChanges[$srHash];
659 unset($this->_objectsChanges[$srHash]);
663 if (isset($this->_objectsRemoved[$srHash]))
665 $this->_objectsRemoved[$srPrimary] = $this->_objectsRemoved[$srHash];
666 unset($this->_objectsRemoved[$srHash]);
688 return !empty($this->_objectsChanges);
701 foreach ($this->_objectsChanges as $srPrimary => $changeCode)
703 if (isset($this->_objects[$srPrimary]))
705 $changedObject = $this->_objects[$srPrimary];
707 elseif (isset($this->_objectsRemoved[$srPrimary]))
709 $changedObject = $this->_objectsRemoved[$srPrimary];
713 $changedObject =
null;
716 if (empty($changedObject))
719 'Object with primary `%s` was not found in `%s` collection', $srPrimary, get_class($this)
723 $changes[] = [$changedObject, $changeCode];
738 foreach ($this->_objectsChanges as $srPrimary => $changeCode)
740 if ($changeCode === static::OBJECT_ADDED)
742 unset($this->_objects[$srPrimary]);
744 elseif ($changeCode === static::OBJECT_REMOVED)
746 $this->_objects[$srPrimary] = $this->_objectsRemoved[$srPrimary];
751 $this->_objectsChanges = [];
752 $this->_objectsRemoved = [];
766 foreach ($this->_objects as $objectPrimary => $object)
768 $values[] = $object->sysGetValue($fieldName);
781 protected function sysGetCollection($fieldName)
784 $field = $this->_entity->getField($fieldName);
787 $values = $field->getRefEntity()->createCollection();
790 foreach ($this->_objects as $objectPrimary => $object)
792 $value = $object->sysGetValue($fieldName);
800 foreach ($value->getAll() as $remoteObject)
802 $values[] = $remoteObject;
816 foreach ($this->_objects as $k => $object)
820 unset($this->_objects[$k]);
832 $this->_isFilled = $value;
843 protected function sysNormalizePrimary($primary)
846 $primaryNames = $this->_entity->getPrimaryArray();
848 if (!is_array($primary))
850 if (
count($primaryNames) > 1)
853 'Only one value of primary found, when entity %s has %s primary keys',
854 $this->_entity->getDataClass(),
count($primaryNames)
858 $primary = [$primaryNames[0] => $primary];
862 $normalizedPrimary = [];
864 foreach ($primaryNames as $primaryName)
867 $field = $this->_entity->getField($primaryName);
868 $normalizedPrimary[$primaryName] = $field->cast($primary[$primaryName]);
871 return $normalizedPrimary;
885 if ($object->sysHasPrimary())
891 return spl_object_hash($object);
905 if ($this->_isSinglePrimary)
910 return Json::encode(array_values($primary));
960 #[\ReturnTypeWillChange]
972 reset($this->_iterableObjects);
980 #[\ReturnTypeWillChange]
983 if ($this->_iterableObjects ===
null)
988 return current($this->_iterableObjects);
996 #[\ReturnTypeWillChange]
999 return key($this->_iterableObjects);
1007 next($this->_iterableObjects);
1017 return key($this->_iterableObjects) !==
null;
1027 return count($this->_objects);
1034 public function merge(?
self $collection): self
1036 if (is_null($collection))
1041 if (get_class($this) !== get_class($collection))
1044 'Invalid object class ' . get_class($collection) .
' for merge, ' . get_class($this) .
' expected .'
1048 foreach ($collection as $item)
1058 return $this->
count() === 0;
sysAddActual(EntityObject $object)
sysReviseDeletedObjects()
__call($name, $arguments)
removeByPrimary($primary)
sysGetPrimaryKey(EntityObject $object)
has(EntityObject $object)
sysOnObjectPrimarySet($object)
__construct(Entity $entity=null)
add(EntityObject $object)
sysResetChanges($rollback=false)
offsetSet($offset, $value)
fill($fields=FieldTypeMask::ALL)
sysSerializePrimaryKey($primary)
sysSetFilled($value=true)