AnonSec Shell
Server IP : 85.193.89.191  /  Your IP : 18.221.100.11
Web Server : Apache
System : Linux 956367-cx40159.tmweb.ru 3.10.0-1160.105.1.el7.x86_64 #1 SMP Thu Dec 7 15:39:45 UTC 2023 x86_64
User : bitrix ( 600)
PHP Version : 8.1.27
Disable Function : NONE
MySQL : OFF  |  cURL : OFF  |  WGET : ON  |  Perl : ON  |  Python : ON  |  Sudo : ON  |  Pkexec : ON
Directory :  /home/bitrix/www/bitrix/modules/main/lib/orm/objectify/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ HOME ]     

Current File : /home/bitrix/www/bitrix/modules/main/lib/orm/objectify/collection.php
<?php
/**
 * Bitrix Framework
 * @package    bitrix
 * @subpackage main
 * @copyright  2001-2018 Bitrix
 */

namespace Bitrix\Main\ORM\Objectify;

use Bitrix\Main\ArgumentException;
use Bitrix\Main\ORM\Data\DataManager;
use Bitrix\Main\ORM\Data\Result;
use Bitrix\Main\ORM\Entity;
use Bitrix\Main\ORM\Fields\Relations\Relation;
use Bitrix\Main\ORM\Fields\ScalarField;
use Bitrix\Main\ORM\Query\Query;
use Bitrix\Main\NotImplementedException;
use Bitrix\Main\ORM\Fields\FieldTypeMask;
use Bitrix\Main\SystemException;
use Bitrix\Main\Text\StringHelper;
use Bitrix\Main\Web\Json;

/**
 * Collection of entity objects. Used to hold 1:N and N:M object collections.
 *
 * @property-read \Bitrix\Main\ORM\Entity $entity
 *
 * @package    bitrix
 * @subpackage main
 */
abstract class Collection implements \ArrayAccess, \Iterator, \Countable
{
	/**
	 * Entity Table class. Read-only property.
	 * @var DataManager
	 */
	static public $dataClass;

	/** @var Entity */
	protected $_entity;

	/** @var EntityObject */
	protected $_objectClass;

	/** @var  EntityObject[] */
	protected $_objects = [];

	/** @var bool */
	protected $_isFilled = false;

	/** @var bool */
	protected $_isSinglePrimary;

	/** @var array [SerializedPrimary => OBJECT_CHANGE_CODE] */
	protected $_objectsChanges;

	/** @var  EntityObject[] */
	protected $_objectsRemoved;

	/** @var EntityObject[] Used for Iterator interface, allows to delete elements during foreach loop */
	protected $_iterableObjects;

	/** @var int Code for $objectsChanged */
	const OBJECT_ADDED = 1;

	/** @var int Code for $objectsChanged */
	const OBJECT_REMOVED = 2;

	/**
	 * Collection constructor.
	 *
	 * @param Entity $entity
	 *
	 * @throws ArgumentException
	 * @throws SystemException
	 */
	final public function __construct(Entity $entity = null)
	{
		if (empty($entity))
		{
			if (__CLASS__ !== get_called_class())
			{
				// custom collection class
				$dataClass = static::$dataClass;
				$this->_entity = $dataClass::getEntity();
			}
			else
			{
				throw new ArgumentException('Entity required when constructing collection');
			}
		}
		else
		{
			$this->_entity = $entity;
		}

		$this->_objectClass = $this->_entity->getObjectClass();
		$this->_isSinglePrimary = count($this->_entity->getPrimaryArray()) == 1;
	}

	public function __clone()
	{
		$this->_objects = \Bitrix\Main\Type\Collection::clone((array)$this->_objects);
		$this->_objectsRemoved = \Bitrix\Main\Type\Collection::clone((array)$this->_objectsRemoved);
		$this->_iterableObjects = null;
	}

	/**
	 * @param EntityObject $object
	 *
	 * @throws ArgumentException
	 * @throws SystemException
	 */
	final public function add(EntityObject $object)
	{
		// check object class
		if (!($object instanceof $this->_objectClass))
		{
			throw new ArgumentException(sprintf(
				'Invalid object class %s for %s collection, expected "%s".',
				get_class($object), get_class($this), $this->_objectClass
			));
		}

		$srPrimary = $this->sysGetPrimaryKey($object);

		if (!$object->sysHasPrimary())
		{
			// object is new and there is no primary yet
			$object->sysAddOnPrimarySetListener([$this, 'sysOnObjectPrimarySet']);
		}

		if (empty($this->_objects[$srPrimary])
			&& (!isset($this->_objectsChanges[$srPrimary]) || $this->_objectsChanges[$srPrimary] != static::OBJECT_REMOVED))
		{
			$this->_objects[$srPrimary] = $object;
			$this->_objectsChanges[$srPrimary] = static::OBJECT_ADDED;
		}
		elseif (isset($this->_objectsChanges[$srPrimary]) && $this->_objectsChanges[$srPrimary] == static::OBJECT_REMOVED)
		{
			// silent add for removed runtime
			$this->_objects[$srPrimary] = $object;

			unset($this->_objectsChanges[$srPrimary]);
			unset($this->_objectsRemoved[$srPrimary]);
		}
	}

	/**
	 * @param EntityObject $object
	 *
	 * @return bool
	 * @throws ArgumentException
	 * @throws SystemException
	 */
	final public function has(EntityObject $object)
	{
		// check object class
		if (!($object instanceof $this->_objectClass))
		{
			throw new ArgumentException(sprintf(
				'Invalid object class %s for %s collection, expected "%s".',
				get_class($object), get_class($this), $this->_objectClass
			));
		}

		return array_key_exists($this->sysGetPrimaryKey($object), $this->_objects);
	}

	/**
	 * @param $primary
	 *
	 * @return bool
	 * @throws ArgumentException
	 */
	final public function hasByPrimary($primary)
	{
		$normalizedPrimary = $this->sysNormalizePrimary($primary);
		return array_key_exists($this->sysSerializePrimaryKey($normalizedPrimary), $this->_objects);
	}

	/**
	 * @param $primary
	 *
	 * @return EntityObject
	 * @throws ArgumentException
	 */
	final public function getByPrimary($primary)
	{
		$normalizedPrimary = $this->sysNormalizePrimary($primary);
		$serializePrimaryKey = $this->sysSerializePrimaryKey($normalizedPrimary);

		if (isset($this->_objects[$serializePrimaryKey]))
		{
			return $this->_objects[$serializePrimaryKey];
		}

		return null;
	}

	/**
	 * @return EntityObject[]
	 */
	final public function getAll()
	{
		return array_values($this->_objects);
	}

	/**
	 * @param EntityObject $object
	 *
	 * @return void
	 * @throws ArgumentException
	 * @throws SystemException
	 */
	final public function remove(EntityObject $object)
	{
		// check object class
		if (!($object instanceof $this->_objectClass))
		{
			throw new ArgumentException(sprintf(
				'Invalid object class %s for %s collection, expected "%s".',
				get_class($object), get_class($this), $this->_objectClass
			));
		}

		// ignore deleted objects
		if ($object->state === State::DELETED)
		{
			return;
		}

		$srPrimary = $this->sysGetPrimaryKey($object);
		$this->sysRemove($srPrimary);
	}

	/**
	 * @param $primary
	 *
	 * @throws ArgumentException
	 */
	final public function removeByPrimary($primary)
	{
		$normalizedPrimary = $this->sysNormalizePrimary($primary);
		$srPrimary = $this->sysSerializePrimaryKey($normalizedPrimary);

		$this->sysRemove($srPrimary);
	}

	public function sysRemove($srPrimary)
	{
		$object = $this->_objects[$srPrimary];

		if (empty($object))
		{
			$object = $this->entity->wakeUpObject($srPrimary);
		}

		unset($this->_objects[$srPrimary]);

		if (!isset($this->_objectsChanges[$srPrimary]) || $this->_objectsChanges[$srPrimary] != static::OBJECT_ADDED)
		{
			// regular remove
			$this->_objectsChanges[$srPrimary] = static::OBJECT_REMOVED;
			$this->_objectsRemoved[$srPrimary] = $object;
		}
		elseif (isset($this->_objectsChanges[$srPrimary]) && $this->_objectsChanges[$srPrimary] == static::OBJECT_ADDED)
		{
			// silent remove for added runtime
			unset($this->_objectsChanges[$srPrimary]);
			unset($this->_objectsRemoved[$srPrimary]);
		}
	}

	/**
	 * Fills all the values and relations of object
	 *
	 * @param int|string[] $fields Names of fields to fill
	 *
	 * @return array|Collection
	 * @throws ArgumentException
	 * @throws SystemException
	 */
	final public function fill($fields = FieldTypeMask::ALL)
	{
		$entityPrimary = $this->_entity->getPrimaryArray();

		$primaryValues = [];
		$fieldsToSelect = [];

		// if field is the only one
		if (is_scalar($fields) && !is_numeric($fields))
		{
			$fields = [$fields];
		}

		// collect custom fields to select
		foreach ($this->_objects as $object)
		{
			$idleFields = is_array($fields)
				? $object->sysGetIdleFields($fields)
				: $object->sysGetIdleFieldsByMask($fields);

			if (!empty($idleFields))
			{
				$fieldsToSelect = array_unique(array_merge($fieldsToSelect, $idleFields));

				// add object to query
				$objectPrimary = $object->sysRequirePrimary();

				$primaryValues[] = count($objectPrimary) == 1
					? current($objectPrimary)
					: $objectPrimary;
			}
		}

		// add primary to select
		if (!empty($fieldsToSelect))
		{
			$fieldsToSelect = array_unique(array_merge($entityPrimary, $fieldsToSelect));

			// build primary filter
			$primaryFilter = Query::filter();

			if (count($entityPrimary) == 1)
			{
				// IN for single-primary objects
				$primaryFilter->whereIn($entityPrimary[0], $primaryValues);
			}
			else
			{
				// OR for multi-primary objects
				$primaryFilter->logic('or');

				foreach ($primaryValues as $objectPrimary)
				{
					// add each object as a separate condition
					$oneObjectFilter = Query::filter();

					foreach ($objectPrimary as $primaryName => $primaryValue)
					{
						$oneObjectFilter->where($primaryName, $primaryValue);
					}

					$primaryFilter->where($oneObjectFilter);
				}
			}

			// build query
			$dataClass = $this->_entity->getDataClass();
			$result = $dataClass::query()->setSelect($fieldsToSelect)->where($primaryFilter)->exec();

			// set object to identityMap of result, and it will be partially completed by fetch
			$im = new IdentityMap;

			foreach ($this->_objects as $object)
			{
				$im->put($object);
			}

			$result->setIdentityMap($im);
			$result->fetchCollection();
		}

		// return field value it it was only one
		if (is_array($fields) && count($fields) == 1 && $this->entity->hasField(current($fields)))
		{
			$fieldName = current($fields);
			$field = $this->entity->getField($fieldName);

			return ($field instanceof Relation)
				? $this->sysGetCollection($fieldName)
				: $this->sysGetList($fieldName);
		}
	}

	final public function save($ignoreEvents = false)
	{
		$result = new Result;

		/** @var EntityObject[] $addObjects */
		$addObjects = [];

		/** @var EntityObject[] $updateObjects */
		$updateObjects = [];

		foreach ($this->_objects as $object)
		{
			if ($object->sysGetState() === State::RAW)
			{
				$addObjects[] = ['__object' => $object];
			}
			elseif ($object->sysGetState() === State::CHANGED)
			{
				$updateObjects[] = $object;
			}
		}

		$dataClass = static::$dataClass;

		// multi add
		if (!empty($addObjects))
		{
			$result = $dataClass::addMulti($addObjects, $ignoreEvents);
		}

		// multi update
		if (!empty($updateObjects))
		{
			$areEqual = true;
			$primaries = [];

			$dataSample = $updateObjects[0]->collectValues(Values::CURRENT, FieldTypeMask::SCALAR | FieldTypeMask::USERTYPE);
			asort($dataSample);

			// get only scalar & uf data and check its uniqueness
			foreach ($updateObjects as $object)
			{
				$objectData = $object->collectValues(Values::CURRENT, FieldTypeMask::SCALAR | FieldTypeMask::USERTYPE);
				asort($objectData);

				if ($dataSample !== $objectData)
				{
					$areEqual = false;
					break;
				}

				$primaries[] = $object->primary;
			}

			if ($areEqual)
			{
				// one query
				$result = $dataClass::updateMulti($primaries, $dataSample, $ignoreEvents);

				// post save
				foreach ($updateObjects as $object)
				{
					$object->sysSaveRelations($result);
					$object->sysPostSave();
				}
			}
			else
			{
				// each object separately
				foreach ($updateObjects as $object)
				{
					$objectResult = $object->save();

					if (!$objectResult->isSuccess())
					{
						$result->addErrors($objectResult->getErrors());
					}
				}
			}
		}

		return $result;
	}

	/**
	 * Constructs set of existing objects from pre-selected data, including references and relations.
	 *
	 * @param $rows
	 *
	 * @return array|static
	 * @throws ArgumentException
	 * @throws SystemException
	 */
	final public static function wakeUp($rows)
	{
		// define object class
		$dataClass = static::$dataClass;
		$objectClass = $dataClass::getObjectClass();

		// complete collection
		$collection = new static;

		foreach ($rows as $row)
		{
			$collection->sysAddActual($objectClass::wakeUp($row));
		}

		return $collection;
	}

	/**
	 * Magic read-only properties
	 *
	 * @param $name
	 *
	 * @return array|Entity
	 * @throws SystemException
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'entity':
				return $this->_entity;
			case 'dataClass':
				throw new SystemException('Property `dataClass` should be received as static.');
		}

		throw new SystemException(sprintf(
			'Unknown property `%s` for collection `%s`', $name, get_called_class()
		));
	}

	/**
	 * Magic read-only properties
	 *
	 * @param $name
	 * @param $value
	 *
	 * @throws SystemException
	 */
	public function __set($name, $value)
	{
		switch ($name)
		{
			case 'entity':
			case 'dataClass':
				throw new SystemException(sprintf(
					'Property `%s` for collection `%s` is read-only', $name, get_called_class()
				));
		}

		throw new SystemException(sprintf(
			'Unknown property `%s` for collection `%s`', $name, get_called_class()
		));
	}

	/**
	 * Magic to handle getters, setters etc.
	 *
	 * @param $name
	 * @param $arguments
	 *
	 * @return array
	 * @throws ArgumentException
	 * @throws SystemException
	 */
	public function __call($name, $arguments)
	{
		$first3 = substr($name, 0, 3);
		$last4 = substr($name, -4);

		// group getter
		if ($first3 == 'get' && $last4 == 'List')
		{
			$fieldName = EntityObject::sysMethodToFieldCase(substr($name, 3, -4));

			if ($fieldName == '')
			{
				$fieldName = StringHelper::strtoupper($arguments[0]);

				// check if custom method exists
				$personalMethodName = $first3.EntityObject::sysFieldToMethodCase($fieldName).$last4;

				if (method_exists($this, $personalMethodName))
				{
					return $this->$personalMethodName(...array_slice($arguments, 1));
				}

				// hard field check
				$this->entity->getField($fieldName);
			}

			// check if field exists
			if ($this->_entity->hasField($fieldName))
			{
				return $this->sysGetList($fieldName);
			}
		}

		$last10 = substr($name, -10);

		if ($first3 == 'get' && $last10 == 'Collection')
		{
			$fieldName = EntityObject::sysMethodToFieldCase(substr($name, 3, -10));

			if ($fieldName == '')
			{
				$fieldName = StringHelper::strtoupper($arguments[0]);

				// check if custom method exists
				$personalMethodName = $first3.EntityObject::sysFieldToMethodCase($fieldName).$last10;

				if (method_exists($this, $personalMethodName))
				{
					return $this->$personalMethodName(...array_slice($arguments, 1));
				}

				// hard field check
				$this->entity->getField($fieldName);
			}

			// check if field exists
			if ($this->_entity->hasField($fieldName) && $this->_entity->getField($fieldName) instanceof Relation)
			{
				return $this->sysGetCollection($fieldName);
			}
		}

		$first4 = substr($name, 0, 4);

		// filler
		if ($first4 == 'fill')
		{
			$fieldName = EntityObject::sysMethodToFieldCase(substr($name, 4));

			// check if field exists
			if ($this->_entity->hasField($fieldName))
			{
				return $this->fill([$fieldName]);
			}
		}

		throw new SystemException(sprintf(
			'Unknown method `%s` for object `%s`', $name, get_called_class()
		));
	}

	/**
	 * @internal For internal system usage only.
	 *
	 * @param \Bitrix\Main\ORM\Objectify\EntityObject $object
	 *
	 * @throws ArgumentException
	 * @throws SystemException
	 */
	public function sysAddActual(EntityObject $object)
	{
		$this->_objects[$this->sysGetPrimaryKey($object)] = $object;
	}

	/**
	 * Callback for object event when it gets primary
	 *
	 * @param $object
	 */
	public function sysOnObjectPrimarySet($object)
	{
		$srHash = spl_object_hash($object);
		$srPrimary = $this->sysSerializePrimaryKey($object->primary);

		if (isset($this->_objects[$srHash]))
		{
			// rewrite object
			unset($this->_objects[$srHash]);
			$this->_objects[$srPrimary] = $object;

			// rewrite changes
			if (isset($this->_objectsChanges[$srHash]))
			{
				$this->_objectsChanges[$srPrimary] = $this->_objectsChanges[$srHash];
				unset($this->_objectsChanges[$srHash]);
			}

			// rewrite removed registry
			if (isset($this->_objectsRemoved[$srHash]))
			{
				$this->_objectsRemoved[$srPrimary] = $this->_objectsRemoved[$srHash];
				unset($this->_objectsRemoved[$srHash]);
			}
		}
	}

	/**
	 * @internal For internal system usage only.
	 *
	 * @return bool
	 */
	public function sysIsFilled()
	{
		return $this->_isFilled;
	}

	/**
	 * @internal For internal system usage only.
	 *
	 * @return bool
	 */
	public function sysIsChanged()
	{
		return !empty($this->_objectsChanges);
	}

	/**
	 * @internal For internal system usage only.
	 *
	 * @return array
	 * @throws SystemException
	 */
	public function sysGetChanges()
	{
		$changes = [];

		foreach ($this->_objectsChanges as $srPrimary => $changeCode)
		{
			if (isset($this->_objects[$srPrimary]))
			{
				$changedObject = $this->_objects[$srPrimary];
			}
			elseif (isset($this->_objectsRemoved[$srPrimary]))
			{
				$changedObject = $this->_objectsRemoved[$srPrimary];
			}
			else
			{
				$changedObject = null;
			}

			if (empty($changedObject))
			{
				throw new SystemException(sprintf(
					'Object with primary `%s` was not found in `%s` collection', $srPrimary, get_class($this)
				));
			}

			$changes[] = [$changedObject, $changeCode];
		}

		return $changes;
	}

	/**
	 * @internal For internal system usage only.
	 *
	 * @param bool $rollback
	 */
	public function sysResetChanges($rollback = false)
	{
		if ($rollback)
		{
			foreach ($this->_objectsChanges as $srPrimary => $changeCode)
			{
				if ($changeCode === static::OBJECT_ADDED)
				{
					unset($this->_objects[$srPrimary]);
				}
				elseif ($changeCode === static::OBJECT_REMOVED)
				{
					$this->_objects[$srPrimary] = $this->_objectsRemoved[$srPrimary];
				}
			}
		}

		$this->_objectsChanges = [];
		$this->_objectsRemoved = [];
	}

	/**
	 * @param $fieldName
	 *
	 * @return array
	 * @throws SystemException
	 */
	protected function sysGetList($fieldName)
	{
		$values = [];

		// collect field values
		foreach ($this->_objects as $objectPrimary => $object)
		{
			$values[] = $object->sysGetValue($fieldName);
		}

		return $values;
	}

	/**
	 * @param $fieldName
	 *
	 * @return Collection
	 * @throws ArgumentException
	 * @throws SystemException
	 */
	protected function sysGetCollection($fieldName)
	{
		/** @var Relation $field */
		$field = $this->_entity->getField($fieldName);

		/** @var Collection $values */
		$values = $field->getRefEntity()->createCollection();

		// collect field values
		foreach ($this->_objects as $objectPrimary => $object)
		{
			$value = $object->sysGetValue($fieldName);

			if ($value instanceof EntityObject)
			{
				$values[] = $value;
			}
			elseif ($value instanceof Collection)
			{
				foreach ($value->getAll() as $remoteObject)
				{
					$values[] = $remoteObject;
				}
			}
		}

		return $values;
	}

	/**
	 * @internal For internal system usage only.
	 */
	public function sysReviseDeletedObjects()
	{
		// clear from deleted objects
		foreach ($this->_objects as $k => $object)
		{
			if ($object->state === State::DELETED)
			{
				unset($this->_objects[$k]);
			}
		}
	}

	/**
	 * @internal For internal system usage only.
	 *
	 * @param bool $value
	 */
	public function sysSetFilled($value = true)
	{
		$this->_isFilled = $value;
	}

	/**
	 * @internal For internal system usage only.
	 *
	 * @param $primary
	 *
	 * @return array
	 * @throws ArgumentException
	 */
	protected function sysNormalizePrimary($primary)
	{
		// normalize primary
		$primaryNames = $this->_entity->getPrimaryArray();

		if (!is_array($primary))
		{
			if (count($primaryNames) > 1)
			{
				throw new ArgumentException(sprintf(
					'Only one value of primary found, when entity %s has %s primary keys',
					$this->_entity->getDataClass(), count($primaryNames)
				));
			}

			$primary = [$primaryNames[0] => $primary];
		}

		// check in $this->objects
		$normalizedPrimary = [];

		foreach ($primaryNames as $primaryName)
		{
			/** @var ScalarField $field */
			$field = $this->_entity->getField($primaryName);
			$normalizedPrimary[$primaryName] = $field->cast($primary[$primaryName]);
		}

		return $normalizedPrimary;
	}

	/**
	 * @internal For internal system usage only.
	 *
	 * @param \Bitrix\Main\ORM\Objectify\EntityObject $object
	 *
	 * @return false|mixed|string
	 * @throws ArgumentException
	 * @throws SystemException
	 */
	protected function sysGetPrimaryKey(EntityObject $object)
	{
		if ($object->sysHasPrimary())
		{
			return $this->sysSerializePrimaryKey($object->primary);
		}
		else
		{
			return spl_object_hash($object);
		}
	}

	/**
	 * @internal For internal system usage only.
	 *
	 * @param $primary
	 *
	 * @return false|mixed|string
	 * @throws ArgumentException
	 */
	protected function sysSerializePrimaryKey($primary)
	{
		if ($this->_isSinglePrimary)
		{
			return current($primary);
		}

		return Json::encode(array_values($primary));
	}

	/**
	 * ArrayAccess implementation
	 *
	 * @param mixed $offset
	 * @param mixed $value
	 *
	 * @throws ArgumentException
	 * @throws SystemException
	 */
	public function offsetSet($offset, $value): void
	{
		$this->add($value);
	}

	/**
	 * ArrayAccess implementation
	 *
	 * @param mixed $offset
	 *
	 * @return bool
	 * @throws NotImplementedException
	 */
	public function offsetExists($offset): bool
	{
		throw new NotImplementedException;
	}

	/**
	 * ArrayAccess implementation
	 *
	 * @param mixed $offset
	 *
	 * @throws NotImplementedException
	 */
	public function offsetUnset($offset): void
	{
		throw new NotImplementedException;
	}

	/**
	 * ArrayAccess implementation
	 *
	 * @param mixed $offset
	 *
	 * @return mixed|void
	 * @throws NotImplementedException
	 */
	#[\ReturnTypeWillChange]
	public function offsetGet($offset)
	{
		throw new NotImplementedException;
	}

	/**
	 * Iterator implementation
	 */
	public function rewind(): void
	{
		$this->_iterableObjects = $this->_objects;
		reset($this->_iterableObjects);
	}

	/**
	 * Iterator implementation
	 *
	 * @return EntityObject|mixed
	 */
	#[\ReturnTypeWillChange]
	public function current()
	{
		if ($this->_iterableObjects === null)
		{
			$this->_iterableObjects = $this->_objects;
		}

		return current($this->_iterableObjects);
	}

	/**
	 * Iterator implementation
	 *
	 * @return mixed
	 */
	#[\ReturnTypeWillChange]
	public function key()
	{
		return key($this->_iterableObjects);
	}

	/**
	 * Iterator implementation
	 */
	public function next(): void
	{
		next($this->_iterableObjects);
	}

	/**
	 * Iterator implementation
	 *
	 * @return bool
	 */
	public function valid(): bool
	{
		return key($this->_iterableObjects) !== null;
	}

	/**
	 * Countable implementation
	 *
	 * @return int
	 */
	public function count(): int
	{
		return count($this->_objects);
	}

	/**
	 * @throws SystemException
	 * @throws ArgumentException
	 */
	public function merge(?self $collection): self
	{
		if (is_null($collection))
		{
			return $this;
		}

		if (get_class($this) !== get_class($collection))
		{
			throw new ArgumentException(
				'Invalid object class ' . get_class($collection) . ' for merge, ' . get_class($this) . ' expected .'
			);
		}

		foreach ($collection as $item)
		{
			$this->add($item);
		}

		return $this;
	}

	public function isEmpty(): bool
	{
		return $this->count() === 0;
	}
}

Anon7 - 2022
AnonSec Team