%PDF- %PDF-
| Direktori : /home/bitrix/www/bitrix/modules/main/lib/orm/objectify/ |
| 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\Entity;
use Bitrix\Main\ORM\Query\Query;
use Bitrix\Main\NotImplementedException;
use Bitrix\Main\ORM\Fields\FieldTypeMask;
use Bitrix\Main\SystemException;
/**
* 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 $_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->_isSinglePrimary = count($this->_entity->getPrimaryArray()) == 1;
}
/**
* @param EntityObject $object
*
* @throws ArgumentException
* @throws SystemException
*/
final public function add(EntityObject $object)
{
$srPrimary = $this->sysGetPrimaryKey($object);
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
unset($this->_objectsChanges[$srPrimary]);
unset($this->_objectsRemoved[$srPrimary]);
}
}
/**
* @param EntityObject $object
*
* @return bool
* @throws ArgumentException
* @throws SystemException
*/
final public function has(EntityObject $object)
{
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);
return $this->_objects[$this->sysSerializePrimaryKey($normalizedPrimary)];
}
/**
* @return EntityObject[]
*/
final public function getAll()
{
return array_values($this->_objects);
}
/**
* @param EntityObject $object
*
* @throws ArgumentException
* @throws SystemException
*/
final public function remove(EntityObject $object)
{
return $this->removeByPrimary($object->primary);
}
/**
* @param $primary
*
* @throws ArgumentException
*/
final public function removeByPrimary($primary)
{
$normalizedPrimary = $this->sysNormalizePrimary($primary);
$srPrimary = $this->sysSerializePrimaryKey($normalizedPrimary);
$object = $this->_objects[$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
*
* @throws ArgumentException
* @throws SystemException
*/
final public function fill($fields = FieldTypeMask::ALL)
{
$entityPrimary = $this->_entity->getPrimaryArray();
$primaryValues = [];
$fieldsToSelect = $entityPrimary;
if (is_scalar($fields) && !is_numeric($fields))
{
$fields = [$fields];
}
// collect custom fields to select
if (is_array($fields))
{
$fieldsToSelect = array_merge($fieldsToSelect, $fields);
}
foreach ($this->_objects as $object)
{
// collect primary
$objectPrimary = $object->sysRequirePrimary();
$primaryValues[] = count($objectPrimary) == 1
? current($objectPrimary)
: $objectPrimary;
// collect fields to select if there is a fields flag instead of custom list
if (!is_array($fields))
{
$diff = array_diff($object->sysGetIdleFields($fields), $fieldsToSelect);
$fieldsToSelect = array_merge($fieldsToSelect, $diff);
}
}
// 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();
}
/**
* 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));
// check if field exists
if ($this->_entity->hasField($fieldName))
{
$values = [];
// collect field values
foreach ($this->_objects as $objectPrimary => $object)
{
$values[$objectPrimary] = $object->sysGetValue($fieldName);
}
return $values;
}
}
$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;
}
/**
* @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 = [];
}
/**
* @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)
{
$normalizedPrimary[$primaryName] = $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)
{
return $this->sysSerializePrimaryKey($object->primary);
}
/**
* @internal For internal system usage only.
*
* @param $primary
*
* @return false|mixed|string
*/
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)
{
$this->add($value);
}
/**
* ArrayAccess implementation
*
* @param mixed $offset
*
* @return bool|void
* @throws NotImplementedException
*/
public function offsetExists($offset)
{
throw new NotImplementedException;
}
/**
* ArrayAccess implementation
*
* @param mixed $offset
*
* @throws NotImplementedException
*/
public function offsetUnset($offset)
{
throw new NotImplementedException;
}
/**
* ArrayAccess implementation
*
* @param mixed $offset
*
* @return mixed|void
* @throws NotImplementedException
*/
public function offsetGet($offset)
{
throw new NotImplementedException;
}
/**
* Iterator implementation
*/
public function rewind()
{
$this->_iterableObjects = $this->_objects;
reset($this->_iterableObjects);
}
/**
* Iterator implementation
*
* @return EntityObject|mixed
*/
public function current()
{
return current($this->_iterableObjects);
}
/**
* Iterator implementation
*
* @return int|mixed|null|string
*/
public function key()
{
return key($this->_iterableObjects);
}
/**
* Iterator implementation
*/
public function next()
{
next($this->_iterableObjects);
}
/**
* Iterator implementation
*
* @return bool
*/
public function valid()
{
return key($this->_iterableObjects) !== null;
}
/**
* Countable implementation
*
* @return int
*/
public function count()
{
return count($this->_objects);
}
}