%PDF- %PDF-
| Direktori : /proc/self/root/home/bitrix/www/bitrix/modules/main/lib/orm/objectify/ |
| Current File : //proc/self/root/home/bitrix/www/bitrix/modules/main/lib/orm/objectify/entityobject.php |
<?php
/**
* Bitrix Framework
* @package bitrix
* @subpackage main
* @copyright 2001-2018 Bitrix
*/
namespace Bitrix\Main\ORM\Objectify;
use Bitrix\Main\NotImplementedException;
use Bitrix\Main\ORM\Data\DataManager;
use Bitrix\Main\ORM\Entity;
use Bitrix\Main\ORM\Fields\ExpressionField;
use Bitrix\Main\ORM\Fields\IReadable;
use Bitrix\Main\ORM\Fields\Relations\ManyToMany;
use Bitrix\Main\ORM\Fields\Relations\OneToMany;
use Bitrix\Main\ORM\Fields\UserTypeField;
use Bitrix\Main\ORM\Query\Query;
use Bitrix\Main\ORM\Fields\Relations\Reference;
use Bitrix\Main\ORM\Data\Result;
use Bitrix\Main\ORM\Fields\ScalarField;
use Bitrix\Main\ORM\Fields\FieldTypeMask;
use Bitrix\Main\SystemException;
use Bitrix\Main\ArgumentException;
/**
* Entity object
*
* @property-read \Bitrix\Main\ORM\Entity $entity
* @property-read array $primary
*
* @package bitrix
* @subpackage main
*/
abstract class EntityObject implements \ArrayAccess
{
/**
* Entity Table class. Read-only property.
* @var DataManager
*/
static public $dataClass;
/** @var Entity */
protected $_entity;
/**
* @var int
* @see State
*/
protected $_state = State::RAW;
/**
* Actual values fetched from DB and collections of relations
* @var mixed[]|static[]|Collection[]
*/
protected $_actualValues = [];
/**
* Current values - new or rewritten by setter (except changed collections - they are still in actual values)
* @var mixed[]|static[]
*/
protected $_currentValues = [];
/**
* Container for non-entity data
* @var mixed[]
*/
protected $_runtimeValues = [];
/**
* Cache for lastName => LAST_NAME transforming
* @var string[]
*/
static protected $_camelToSnakeCache = [];
/**
* Cache for LAST_NAME => lastName transforming
* @var string[]
*/
static protected $_snakeToCamelCache = [];
final public function __construct($setDefaultValues = true)
{
if ($setDefaultValues)
{
foreach ($this->entity->getScalarFields() as $fieldName => $field)
{
$defaultValue = $field->getDefaultValue($this);
if ($defaultValue !== null)
{
$this->sysSetValue($fieldName, $defaultValue);
}
}
}
}
/**
* Returns all objects values as an array
*
* @param int $valuesType
* @param int $fieldsMask
*
* @return array
* @throws ArgumentException
* @throws SystemException
*/
final public function collectValues($valuesType = Values::ALL, $fieldsMask = FieldTypeMask::ALL)
{
switch ($valuesType)
{
case Values::ACTUAL:
$objectValues = $this->_actualValues;
break;
case Values::CURRENT:
$objectValues = $this->_currentValues;
break;
default:
$objectValues = array_merge($this->_actualValues, $this->_currentValues);
}
// filter with field mask
if ($fieldsMask !== FieldTypeMask::ALL)
{
foreach ($objectValues as $fieldName => $value)
{
$fieldMask = $this->entity->getField($fieldName)->getTypeMask();
if (!($fieldsMask & $fieldMask))
{
unset($objectValues[$fieldName]);
}
}
}
// remap from uppercase to real field names
$values = [];
foreach ($objectValues as $k => $v)
{
$values[$this->entity->getField($k)->getName()] = $v;
}
return $values;
}
/**
* ActiveRecord save.
*
* @return Result
* @throws ArgumentException
* @throws SystemException
* @throws \Exception
*/
final public function save()
{
$result = new Result;
$dataClass = $this->entity->getDataClass();
if ($this->_state == State::RAW)
{
$data = $this->_currentValues;
$data['__object'] = $this;
// put secret key __object to array
$result = $dataClass::add($data);
// check for error
if (!$result->isSuccess())
{
return $result;
}
// set primary
foreach ($result->getPrimary() as $primaryName => $primaryValue)
{
$this->sysSetActual($primaryName, $primaryValue);
}
}
elseif ($this->_state == State::CHANGED)
{
// changed scalar and reference
if (!empty($this->_currentValues))
{
$data = $this->_currentValues;
$data['__object'] = $this;
// put secret key __object to array
$result = $dataClass::update($this->primary, $data);
// check for error
if (!$result->isSuccess())
{
return $result;
}
}
}
else
{
// nothing to do
return $result;
}
// set other fields, as long as some values could be added or modified in events
foreach ($result->getData() as $fieldName => $fieldValue)
{
$field = $this->entity->getField($fieldName);
if ($field instanceof ScalarField)
{
$fieldValue = $field->cast($fieldValue);
}
$this->sysSetActual($fieldName, $fieldValue);
}
// changed collections
$this->sysSaveRelations($result);
// return if there were errors
if (!$result->isSuccess())
{
return $result;
}
// clear current values
$this->_currentValues = [];
// change state
$this->sysChangeState(State::ACTUAL);
return $result;
}
/**
* ActiveRecord delete.
*
* @return Result
* @throws ArgumentException
* @throws SystemException
*/
final public function delete()
{
$result = new Result;
// delete relations
foreach ($this->entity->getFields() as $field)
{
if ($field instanceof OneToMany || $field instanceof ManyToMany)
{
$this->sysRemoveAllFromCollection($field->getName());
}
}
$this->sysSaveRelations($result);
// delete object itself
$dataClass = static::$dataClass;
$deleteResult = $dataClass::delete($this->primary);
if (!$deleteResult->isSuccess())
{
$result->addErrors($deleteResult->getErrors());
}
// clear status
foreach ($this->entity->getPrimaryArray()as $primaryName)
{
unset($this->_actualValues[$primaryName]);
}
$this->sysChangeState(State::RAW);
return $result;
}
/**
* Constructs existing object from pre-selected data, including references and relations.
*
* @param mixed $row Array of [field => value] or single scalar primary value.
*
* @return static
* @throws ArgumentException
* @throws SystemException
*/
final public static function wakeUp($row)
{
/** @var static $objectClass */
$objectClass = get_called_class();
/** @var \Bitrix\Main\ORM\Data\DataManager $dataClass */
$dataClass = static::$dataClass;
$entity = $dataClass::getEntity();
$entityPrimary = $entity->getPrimaryArray();
// normalize input data and primary
$primary = [];
if (!is_array($row))
{
// it could be single primary
if (count($entityPrimary) == 1)
{
$primary[$entityPrimary[0]] = $row;
$row = [];
}
else
{
throw new ArgumentException(sprintf(
'Multi-primary for %s was not found', $objectClass
));
}
}
else
{
foreach ($entityPrimary as $primaryName)
{
if (!isset($row[$primaryName]))
{
throw new ArgumentException(sprintf(
'Primary %s for %s was not found', $primaryName, $objectClass
));
}
$primary[$primaryName] = $row[$primaryName];
unset($row[$primaryName]);
}
}
// create object
/** @var static $object */
$object = new $objectClass(false); // here go with false to not set default values
$object->sysChangeState(State::ACTUAL);
// set primary
foreach ($primary as $primaryName => $primaryValue)
{
/** @var ScalarField $primaryField */
$primaryField = $entity->getField($primaryName);
$object->sysSetActual($primaryName, $primaryField->cast($primaryValue));
}
// set other data
foreach ($row as $fieldName => $value)
{
/** @var ScalarField $primaryField */
$field = $entity->getField($fieldName);
if ($field instanceof IReadable)
{
$object->sysSetActual($fieldName, $field->cast($value));
}
else
{
// we have a relation
if ($value instanceof static || $value instanceof Collection)
{
// it is ready data
$object->sysSetActual($fieldName, $value);
}
else
{
// wake up relation
if ($field instanceof Reference)
{
// wake up an object
$remoteObjectClass = $field->getRefEntity()->getObjectClass();
$remoteObject = $remoteObjectClass::wakeUp($value);
$object->sysSetActual($fieldName, $remoteObject);
}
elseif ($field instanceof OneToMany || $field instanceof ManyToMany)
{
// wake up collection
$remoteCollectionClass = $field->getRefEntity()->getCollectionClass();
$remoteCollection = $remoteCollectionClass::wakeUp($value);
$object->sysSetActual($fieldName, $remoteCollection);
}
}
}
}
return $object;
}
/**
* Fills all the values and relations of object
*
* @param int|string[] $fields Names of fields to fill
*
* @return mixed
* @throws ArgumentException
* @throws SystemException
*/
final public function fill($fields = FieldTypeMask::ALL)
{
// object must have primary
$primaryFilter = Query::filter();
foreach ($this->sysRequirePrimary() as $primaryName => $primaryValue)
{
$primaryFilter->where($primaryName, $primaryValue);
}
// collect fields to be selected
$fieldsToSelect = $this->entity->getPrimaryArray();
if (is_array($fields))
{
// get custom fields
$fieldsToSelect = array_merge($fieldsToSelect, $fields);
}
elseif (is_scalar($fields) && !is_numeric($fields))
{
// one custom field
$fieldsToSelect[] = $fields;
}
else
{
// get fields according to selector mask
$fieldsToSelect = array_merge($fieldsToSelect, $this->sysGetIdleFields($fields));
}
// 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;
$im->put($this);
$result->setIdentityMap($im);
$result->fetchObject();
// set filled flag to collections
foreach ($fieldsToSelect as $fieldName)
{
// check field before continue, it could be remote REF.ID definition so we skip it here
if ($this->entity->hasField($fieldName))
{
$field = $this->entity->getField($fieldName);
if ($field instanceof OneToMany || $field instanceof ManyToMany)
{
/** @var Collection $collection */
$collection = $this->sysGetValue($fieldName);
if (empty($collection))
{
$collection = $field->getRefEntity()->createCollection();
$this->_actualValues[$fieldName] = $collection;
}
$collection->sysSetFilled();
}
}
}
// return field value it it was only one
if (is_array($fields) && count($fields) == 1 && $this->entity->hasField(current($fields)))
{
return $this->sysGetValue(current($fields));
}
return null;
}
/**
* Fast popular alternative to __call().
*
* @return Collection|EntityObject|mixed
* @throws SystemException
*/
public function getId()
{
if (array_key_exists('ID', $this->_currentValues))
{
return $this->_currentValues['ID'];
}
elseif (array_key_exists('ID', $this->_actualValues))
{
return $this->_actualValues['ID'];
}
elseif (!$this->entity->hasField('ID'))
{
throw new SystemException(sprintf(
'Unknown method `%s` for object `%s`', 'getId', get_called_class()
));
}
else
{
return null;
}
}
/**
* @param $fieldName
*
* @return mixed
* @throws ArgumentException
* @throws SystemException
*/
final public function get($fieldName)
{
return $this->__call(__FUNCTION__, func_get_args());
}
/**
* @param $fieldName
*
* @return mixed
* @throws ArgumentException
* @throws SystemException
*/
final public function remindActual($fieldName)
{
return $this->__call(__FUNCTION__, func_get_args());
}
/**
* @param $fieldName
*
* @return mixed
* @throws ArgumentException
* @throws SystemException
*/
/* TODO PHP7 ONLY
final public function require($fieldName)
{
return $this->__call(__FUNCTION__, func_get_args());
}*/
/**
* @param $fieldName
* @param $value
*
* @return mixed
* @throws ArgumentException
* @throws SystemException
*/
final public function set($fieldName, $value)
{
return $this->__call(__FUNCTION__, func_get_args());
}
/**
* @param $fieldName
*
* @return mixed
* @throws ArgumentException
* @throws SystemException
*/
final public function reset($fieldName)
{
return $this->__call(__FUNCTION__, func_get_args());
}
/**
* @param $fieldName
*
* @return mixed
* @throws ArgumentException
* @throws SystemException
*/
/* TODO PHP7 ONLY
final public function unset($fieldName)
{
return $this->__call(__FUNCTION__, func_get_args());
}*/
/**
* @param $fieldName
* @param $value
*
* @return mixed
* @throws ArgumentException
* @throws SystemException
*/
final public function addTo($fieldName, $value)
{
return $this->__call(__FUNCTION__, func_get_args());
}
/**
* @param $fieldName
* @param $value
*
* @return mixed
* @throws ArgumentException
* @throws SystemException
*/
final public function removeFrom($fieldName, $value)
{
return $this->__call(__FUNCTION__, func_get_args());
}
/**
* @param $fieldName
*
* @return mixed
* @throws ArgumentException
* @throws SystemException
*/
final public function removeAll($fieldName)
{
return $this->__call(__FUNCTION__, func_get_args());
}
/**
* Magic read-only properties
*
* @param $name
*
* @return array|Entity
* @throws ArgumentException
* @throws SystemException
*/
public function __get($name)
{
switch ($name)
{
case 'entity':
return $this->sysGetEntity();
case 'primary':
return $this->sysGetPrimary();
case 'dataClass':
throw new SystemException('Property `dataClass` should be received as static.');
}
throw new SystemException(sprintf(
'Unknown property `%s` for object `%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 'primary':
case 'dataClass':
throw new SystemException(sprintf(
'Property `%s` for object `%s` is read-only', $name, get_called_class()
));
}
throw new SystemException(sprintf(
'Unknown property `%s` for object `%s`', $name, get_called_class()
));
}
/**
* Magic to handle getters, setters etc.
*
* @param $name
* @param $arguments
*
* @return mixed
* @throws ArgumentException
* @throws SystemException
*/
public function __call($name, $arguments)
{
$first3 = substr($name, 0, 3);
// regular getter
if ($first3 == 'get')
{
$fieldName = self::sysMethodToFieldCase(substr($name, 3));
if (!strlen($fieldName))
{
$fieldName = strtoupper($arguments[0]);
// check runtime
if (array_key_exists($fieldName, $this->_runtimeValues))
{
return $this->sysGetRuntime($fieldName);
}
// check if custom method exists
$personalMethodName = $name.static::sysFieldToMethodCase($fieldName);
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->sysGetValue($fieldName);
}
}
// regular setter
if ($first3 == 'set')
{
$fieldName = self::sysMethodToFieldCase(substr($name, 3));
$value = $arguments[0];
if (!strlen($fieldName))
{
$fieldName = strtoupper($arguments[0]);
$value = $arguments[1];
// check for runtime field
if (array_key_exists($fieldName, $this->_runtimeValues))
{
throw new SystemException(sprintf(
'Setting value for runtime field `%s` in `%s` is not allowed, it is read-only field',
$fieldName, get_called_class()
));
}
// check if custom method exists
$personalMethodName = $name.static::sysFieldToMethodCase($fieldName);
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))
{
$field = $this->entity->getField($fieldName);
if ($field instanceof IReadable)
{
$value = $field->cast($value);
}
return $this->sysSetValue($fieldName, $value);
}
}
$first4 = substr($name, 0, 4);
// filler
if ($first4 == 'fill')
{
$fieldName = self::sysMethodToFieldCase(substr($name, 4));
// no custom/personal method for fill
// check if field exists
if ($this->entity->hasField($fieldName))
{
return $this->fill([$fieldName]);
}
}
$first5 = substr($name, 0, 5);
// relation adder
if ($first5 == 'addTo')
{
$fieldName = self::sysMethodToFieldCase(substr($name, 5));
$value = $arguments[0];
if (!strlen($fieldName))
{
$fieldName = strtoupper($arguments[0]);
$value = $arguments[1];
// check if custom method exists
$personalMethodName = $name.static::sysFieldToMethodCase($fieldName);
if (method_exists($this, $personalMethodName))
{
return $this->$personalMethodName(...array_slice($arguments, 1));
}
}
if ($this->entity->hasField($fieldName))
{
return $this->sysAddToCollection($fieldName, $value);
}
}
// unsetter
if ($first5 == 'unset')
{
$fieldName = self::sysMethodToFieldCase(substr($name, 5));
if (!strlen($fieldName))
{
$fieldName = strtoupper($arguments[0]);
// check if custom method exists
$personalMethodName = $name.static::sysFieldToMethodCase($fieldName);
if (method_exists($this, $personalMethodName))
{
return $this->$personalMethodName(...array_slice($arguments, 1));
}
}
if ($this->entity->hasField($fieldName))
{
return $this->sysUnset($fieldName);
}
}
// resetter
if ($first5 == 'reset')
{
$fieldName = self::sysMethodToFieldCase(substr($name, 5));
if (!strlen($fieldName))
{
$fieldName = strtoupper($arguments[0]);
// check if custom method exists
$personalMethodName = $name.static::sysFieldToMethodCase($fieldName);
if (method_exists($this, $personalMethodName))
{
return $this->$personalMethodName(...array_slice($arguments, 1));
}
}
if ($this->entity->hasField($fieldName))
{
$field = $this->entity->getField($fieldName);
if ($field instanceof OneToMany || $field instanceof ManyToMany)
{
return $this->sysResetRelation($fieldName);
}
else
{
return $this->sysReset($fieldName);
}
}
}
$first9 = substr($name, 0, 9);
// relation mass remover
if ($first9 == 'removeAll')
{
$fieldName = self::sysMethodToFieldCase(substr($name, 9));
if (!strlen($fieldName))
{
$fieldName = strtoupper($arguments[0]);
// check if custom method exists
$personalMethodName = $name.static::sysFieldToMethodCase($fieldName);
if (method_exists($this, $personalMethodName))
{
return $this->$personalMethodName(...array_slice($arguments, 1));
}
}
if ($this->entity->hasField($fieldName))
{
return $this->sysRemoveAllFromCollection($fieldName);
}
}
$first10 = substr($name, 0, 10);
// relation remover
if ($first10 == 'removeFrom')
{
$fieldName = self::sysMethodToFieldCase(substr($name, 10));
$value = $arguments[0];
if (!strlen($fieldName))
{
$fieldName = strtoupper($arguments[0]);
$value = $arguments[1];
// check if custom method exists
$personalMethodName = $name.static::sysFieldToMethodCase($fieldName);
if (method_exists($this, $personalMethodName))
{
return $this->$personalMethodName(...array_slice($arguments, 1));
}
}
if ($this->entity->hasField($fieldName))
{
return $this->sysRemoveFromCollection($fieldName, $value);
}
}
$first12 = substr($name, 0, 12);
// actual value getter
if ($first12 == 'remindActual')
{
$fieldName = self::sysMethodToFieldCase(substr($name, 12));
if (!strlen($fieldName))
{
$fieldName = strtoupper($arguments[0]);
// check if custom method exists
$personalMethodName = $name.static::sysFieldToMethodCase($fieldName);
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->_actualValues[$fieldName];
}
}
$first7 = substr($name, 0, 7);
// strict getter
if ($first7 == 'require')
{
$fieldName = self::sysMethodToFieldCase(substr($name, 7));
if (!strlen($fieldName))
{
$fieldName = strtoupper($arguments[0]);
// check if custom method exists
$personalMethodName = $name.static::sysFieldToMethodCase($fieldName);
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->sysGetValue($fieldName, true);
}
}
throw new SystemException(sprintf(
'Unknown method `%s` for object `%s`', $name, get_called_class()
));
}
/**
* @return Entity
* @throws \Bitrix\Main\ArgumentException
* @throws \Bitrix\Main\SystemException
*/
public function sysGetEntity()
{
if ($this->_entity === null)
{
/** @var \Bitrix\Main\ORM\Data\DataManager $dataClass */
$dataClass = static::$dataClass;
$this->_entity = $dataClass::getEntity();
}
return $this->_entity;
}
/**
* Returns [primary => value] array.
*
* @return array
* @throws ArgumentException
* @throws SystemException
*/
public function sysGetPrimary()
{
$primaryValues = [];
foreach ($this->entity->getPrimaryArray() as $primaryName)
{
$primaryValues[$primaryName] = $this->sysGetValue($primaryName);
}
return $primaryValues;
}
/**
* Query Runtime Field values or just any runtime value getter
* @internal For internal system usage only.
*
* @param $name
*
* @return mixed
*/
public function sysGetRuntime($name)
{
return $this->_runtimeValues[$name];
}
/**
* Any runtime value setter
* @internal For internal system usage only.
*
* @param $name
* @param $value
*
* @return $this
*/
public function sysSetRuntime($name, $value)
{
$this->_runtimeValues[$name] = $value;
return $this;
}
/**
* Sets actual value.
* @internal For internal system usage only.
*
* @param $fieldName
* @param $value
*/
public function sysSetActual($fieldName, $value)
{
$this->_actualValues[strtoupper($fieldName)] = $value;
}
/**
* Changes state.
* @see State
* @internal For internal system usage only.
*
* @param $state
*/
public function sysChangeState($state)
{
if ($this->_state !== $state)
{
/* not sure if we need check or changes here
if ($state == State::RAW)
{
// actual should be empty
}
elseif ($state == State::ACTUAL)
{
// runtime values should be empty
}
elseif ($state == State::CHANGED)
{
// runtime values should not be empty
}*/
$this->_state = $state;
}
}
/**
* Returns current state.
* @see State
* @internal For internal system usage only.
*
* @return int
*/
public function sysGetState()
{
return $this->_state;
}
/**
* Regular getter, called by __call.
* @internal For internal system usage only.
*
* @param string $fieldName
* @param bool $require Throws an exception in the absence of value
*
* @return mixed
* @throws SystemException
*/
public function sysGetValue($fieldName, $require = false)
{
$fieldName = strtoupper($fieldName);
if (array_key_exists($fieldName, $this->_currentValues))
{
return $this->_currentValues[$fieldName];
}
else
{
if ($require && !array_key_exists($fieldName, $this->_actualValues))
{
throw new SystemException(sprintf(
'%s value is required for further operations', $fieldName
));
}
return $this->_actualValues[$fieldName];
}
}
/**
* Regular setter, called by __call. Doesn't validate values.
* @internal For internal system usage only.
*
* @param $fieldName
* @param $value
*
* @return $this
* @throws ArgumentException
* @throws SystemException
*/
public function sysSetValue($fieldName, $value)
{
$fieldName = strtoupper($fieldName);
$field = $this->entity->getField($fieldName);
// system validations
if ($field instanceof ScalarField)
{
// restrict updating primary
if ($this->_state !== State::RAW && in_array($field->getName(), $this->entity->getPrimaryArray()))
{
throw new SystemException(sprintf(
'Setting value for Primary `%s` in `%s` is not allowed, it is read-only field',
$field->getName(), get_called_class()
));
}
}
// no setter for expressions
if ($field instanceof ExpressionField && !($field instanceof UserTypeField))
{
throw new SystemException(sprintf(
'Setting value for ExpressionField `%s` in `%s` is not allowed, it is read-only field',
$fieldName, get_called_class()
));
}
if ($field instanceof Reference)
{
if (!empty($value))
{
// validate object class and skip null
$remoteObjectClass = $field->getRefEntity()->getObjectClass();
if (!($value instanceof $remoteObjectClass))
{
throw new ArgumentException(sprintf(
'Expected instance of `%s`, got `%s` instead', $remoteObjectClass, get_class($value)
));
}
}
}
// change only if value is different from actual
if (array_key_exists($fieldName, $this->_actualValues))
{
if ($field instanceof IReadable)
{
if ($field->cast($value) === $this->_actualValues[$fieldName])
{
// forget previous runtime change
unset($this->_currentValues[$fieldName]);
return $this;
}
}
elseif ($field instanceof Reference)
{
/** @var static $value */
if ($value->primary === $this->_actualValues[$fieldName]->primary)
{
// forget previous runtime change
unset($this->_currentValues[$fieldName]);
return $this;
}
}
}
// set value
if ($field instanceof ScalarField || $field instanceof UserTypeField)
{
$this->_currentValues[$fieldName] = $value;
}
elseif ($field instanceof Reference)
{
/** @var static $value */
$this->_currentValues[$fieldName] = $value;
// set elemental fields if there are any
$elementals = $field->getElementals();
if (!empty($elementals))
{
foreach ($elementals as $localFieldName => $remoteFieldName)
{
$elementalValue = empty($value) ? null : $value->sysGetValue($remoteFieldName);
$this->sysSetValue($localFieldName, $elementalValue);
}
}
}
else
{
throw new SystemException(sprintf(
'Unknown field type `%s` in system setter of `%s`', get_class($field), get_called_class()
));
}
if ($this->_state == State::ACTUAL)
{
$this->sysChangeState(State::CHANGED);
}
return $this;
}
/**
* @internal For internal system usage only.
*
* @param $fieldName
*
* @return $this
*/
public function sysUnset($fieldName)
{
$fieldName = strtoupper($fieldName);
unset($this->_currentValues[$fieldName]);
unset($this->_actualValues[$fieldName]);
return $this;
}
/**
* @internal For internal system usage only.
*
* @param $fieldName
*
* @return $this
*/
public function sysReset($fieldName)
{
$fieldName = strtoupper($fieldName);
unset($this->_currentValues[$fieldName]);
return $this;
}
/**
* @internal For internal system usage only.
*
* @param $fieldName
*
* @return $this
*/
public function sysResetRelation($fieldName)
{
$fieldName = strtoupper($fieldName);
if (isset($this->_actualValues[$fieldName]))
{
/** @var Collection $collection */
$collection = $this->_actualValues[$fieldName];
$collection->sysResetChanges(true);
}
return $this;
}
/**
* @internal For internal system usage only.
*
* @return array
* @throws ArgumentException
* @throws SystemException
*/
public function sysRequirePrimary()
{
$primaryValues = [];
foreach ($this->entity->getPrimaryArray() as $primaryName)
{
try
{
$primaryValues[$primaryName] = $this->sysGetValue($primaryName, true);
}
catch (SystemException $e)
{
throw new SystemException(sprintf(
'Primary `%s` value is required for further operations', $primaryName
));
}
}
return $primaryValues;
}
/**
* @internal For internal system usage only.
*
* @param int $mask
*
* @return array
* @throws ArgumentException
* @throws SystemException
*/
public function sysGetIdleFields($mask = FieldTypeMask::ALL)
{
$list = [];
foreach ($this->entity->getFields() as $field)
{
$fieldMask = $field->getTypeMask();
if (!isset($this->_actualValues[strtoupper($field->getName())])
&& ($mask & $fieldMask)
)
{
$list[] = $field->getName();
}
}
return $list;
}
/**
* @internal For internal system usage only.
*
* @param Result $result
*
* @throws ArgumentException
* @throws SystemException
*/
protected function sysSaveRelations(Result $result)
{
foreach ($this->_actualValues as $fieldName => $value)
{
$field = $this->entity->getField($fieldName);
if ($field instanceof OneToMany && $value->sysIsChanged())
{
// save changed elements of collection
$collection = $value;
foreach ($collection->sysGetChanges() as $change)
{
list($remoteObject,) = $change;
// no matter what changeType is, just save the remote object
// elementals will be changed after add or nulled after remove
/** @var static $remoteObject */
$remoteResult = $remoteObject->save();
if (!$remoteResult->isSuccess())
{
$result->addErrors($remoteResult->getErrors());
}
}
// forget collection changes
$collection->sysResetChanges();
}
elseif ($field instanceof ManyToMany && $value->sysIsChanged())
{
$collection = $value;
foreach ($collection->sysGetChanges() as $change)
{
list($remoteObject, $changeType) = $change;
// initialize mediator object
$mediatorObjectClass = $field->getMediatorEntity()->getObjectClass();
$localReferenceName = $field->getLocalReferenceName();
$remoteReferenceName = $field->getRemoteReferenceName();
/** @var static $mediatorObject */
$mediatorObject = new $mediatorObjectClass;
$mediatorObject->sysSetValue($localReferenceName, $this);
$mediatorObject->sysSetValue($remoteReferenceName, $remoteObject);
// add or remove mediator depending on changeType
if ($changeType == Collection::OBJECT_ADDED)
{
$mediatorObject->save();
}
elseif ($changeType == Collection::OBJECT_REMOVED)
{
// destroy directly through data class
$mediatorDataClass = $field->getMediatorEntity()->getDataClass();
$mediatorDataClass::delete($mediatorObject->primary);
}
}
// forget collection changes
$collection->sysResetChanges();
}
}
}
/**
* @internal For internal system usage only.
*
* @param $fieldName
* @param $remoteObject
*
* @throws ArgumentException
* @throws SystemException
*/
public function sysAddToCollection($fieldName, $remoteObject)
{
$fieldName = strtoupper($fieldName);
/** @var OneToMany $field */
$field = $this->entity->getField($fieldName);
$remoteObjectClass = $field->getRefEntity()->getObjectClass();
// validate object class
if (!($remoteObject instanceof $remoteObjectClass))
{
throw new ArgumentException(sprintf(
'Expected instance of `%s`, got `%s` instead', $remoteObjectClass, get_class($remoteObject)
));
}
// initialize collection
$collection = $this->sysGetValue($fieldName);
if (empty($collection))
{
$collection = $field->getRefEntity()->createCollection();
$this->_actualValues[$fieldName] = $collection;
}
// add to collection
$collection->add($remoteObject);
if ($field instanceof OneToMany)
{
// set self to the object
$remoteFieldName = $field->getRefField()->getName();
$remoteObject->sysSetValue($remoteFieldName, $this);
}
// mark object as changed
if ($this->_state == State::ACTUAL)
{
$this->sysChangeState(State::CHANGED);
}
}
/**
* @internal For internal system usage only.
*
* @param $fieldName
* @param $remoteObject
*
* @throws ArgumentException
* @throws SystemException
*/
public function sysRemoveFromCollection($fieldName, $remoteObject)
{
$fieldName = strtoupper($fieldName);
/** @var OneToMany $field */
$field = $this->entity->getField($fieldName);
$remoteObjectClass = $field->getRefEntity()->getObjectClass();
// validate object class
if (!($remoteObject instanceof $remoteObjectClass))
{
throw new ArgumentException(sprintf(
'Expected instance of `%s`, got `%s` instead', $remoteObjectClass, get_class($remoteObject)
));
}
/** @var Collection $collection Initialize collection */
$collection = $this->sysGetValue($fieldName);
if (empty($collection))
{
$collection = $field->getRefEntity()->createCollection();
$this->_actualValues[$fieldName] = $collection;
}
// remove from collection
$collection->remove($remoteObject);
if ($field instanceof OneToMany)
{
// remove self from the object
$remoteFieldName = $field->getRefField()->getName();
$remoteObject->sysSetValue($remoteFieldName, null);
}
// mark object as changed
if ($this->_state == State::ACTUAL)
{
$this->sysChangeState(State::CHANGED);
}
}
/**
* @internal For internal system usage only.
*
* @param $fieldName
*
* @throws ArgumentException
* @throws SystemException
*/
public function sysRemoveAllFromCollection($fieldName)
{
$fieldName = strtoupper($fieldName);
/** @var OneToMany|ManyToMany $field */
$field = $this->entity->getField($fieldName);
/** @var Collection $collection initialize collection */
$collection = $this->sysGetValue($fieldName);
if (empty($collection))
{
$collection = $field->getRefEntity()->createCollection();
$this->_actualValues[$fieldName] = $collection;
}
// check collection fullness
if (!$collection->sysIsFilled())
{
// we need only primary here
$remotePrimaryDefinitions = [];
foreach ($field->getRefEntity()->getPrimaryArray() as $primaryName)
{
$remotePrimaryDefinitions[] = $fieldName.'.'.$primaryName;
}
$this->fill($remotePrimaryDefinitions);
// we can set fullness flag here
$collection->sysSetFilled();
}
// remove one by one
foreach ($collection as $remoteObject)
{
$this->sysRemoveFromCollection($fieldName, $remoteObject);
}
}
/**
* @internal For internal system usage only.
*
* @param $methodName
*
* @return string
*/
public static function sysMethodToFieldCase($methodName)
{
if (!isset(static::$_camelToSnakeCache[$methodName]))
{
static::$_camelToSnakeCache[$methodName] = strtoupper(
preg_replace('/(.)([A-Z])/', '$1_$2', $methodName)
);
}
return static::$_camelToSnakeCache[$methodName];
}
/**
* @internal For internal system usage only.
*
* @param $fieldName
*
* @return string
*/
public static function sysFieldToMethodCase($fieldName)
{
if (!isset(static::$_snakeToCamelCache[$fieldName]))
{
static::$_snakeToCamelCache[$fieldName] = str_replace(' ', '', ucwords(
str_replace('_', ' ', strtolower($fieldName))
));
}
return static::$_snakeToCamelCache[$fieldName];
}
/**
* ArrayAccess interface implementation.
*
* @param mixed $offset
*
* @return bool
* @throws ArgumentException
* @throws SystemException
*/
public function offsetExists($offset)
{
return $this->entity->hasField($offset);
}
/**
* ArrayAccess interface implementation.
*
* @param mixed $offset
*
* @return mixed|null
* @throws ArgumentException
* @throws SystemException
*/
public function offsetGet($offset)
{
if ($this->offsetExists($offset))
{
// regular field
return $this->get($offset);
}
elseif (array_key_exists($offset, $this->_runtimeValues))
{
// runtime field
return $this->sysGetRuntime($offset);
}
return $this->offsetExists($offset) ? $this->get($offset) : null;
}
/**
* ArrayAccess interface implementation.
*
* @param mixed $offset
* @param mixed $value
*
* @throws ArgumentException
* @throws SystemException
*/
public function offsetSet($offset, $value)
{
if (is_null($offset))
{
throw new ArgumentException('Field name should be set');
}
else
{
$this->set($offset, $value);
}
}
/**
* ArrayAccess interface implementation.
*
* @param mixed $offset
*/
public function offsetUnset($offset)
{
$this->unset($offset);
}
}