121 if (is_array($setDefaultValues))
124 foreach ($setDefaultValues as $fieldName => $defaultValue)
126 $field = $this->entity->getField($fieldName);
130 if (is_array($defaultValue))
132 $defaultValue = $field->getRefEntity()->createObject($defaultValue);
135 $this->
set($fieldName, $defaultValue);
138 && is_array($defaultValue))
140 foreach ($defaultValue as $subValue)
142 if (is_array($subValue))
144 $subValue = $field->getRefEntity()->createObject($subValue);
147 $this->addTo($fieldName, $subValue);
152 $this->
set($fieldName, $defaultValue);
158 if ($setDefaultValues || is_array($setDefaultValues))
160 foreach ($this->entity->getScalarFields() as $fieldName => $field)
162 if ($this->sysHasValue($fieldName))
168 $defaultValue = $field->getDefaultValue($this);
170 if ($defaultValue !==
null)
172 $this->
set($fieldName, $defaultValue);
180 $this->_actualValues = $this->
cloneValues($this->_actualValues);
181 $this->_currentValues = $this->
cloneValues($this->_currentValues);
187 $valuesWithoutReferences = $this->
filterValuesByMask($values, FieldTypeMask::REFERENCE,
true);
188 $references = array_diff_key($values, $valuesWithoutReferences);
190 return array_merge($references, \
Bitrix\Main\
Type\Collection::clone($valuesWithoutReferences));
193 protected function filterValuesByMask(array $values,
int $fieldsMask,
bool $invertedFilter =
false): array
195 if ($fieldsMask === FieldTypeMask::ALL)
197 return $invertedFilter ? [] : $values;
200 return array_filter($values,
function($fieldName) use ($fieldsMask, $invertedFilter)
202 $maskOfSingleField = $this->entity->getField($fieldName)->getTypeMask();
203 $matchesMask = (bool)($fieldsMask & $maskOfSingleField);
205 return $invertedFilter ? !$matchesMask: $matchesMask;
206 }, ARRAY_FILTER_USE_KEY);
230 $objectValues = array_merge($this->_actualValues, $this->_currentValues);
234 if ($fieldsMask !== FieldTypeMask::ALL)
236 foreach ($objectValues as $fieldName => $value)
238 $fieldMask = $this->entity->getField($fieldName)->getTypeMask();
239 if (!($fieldsMask & $fieldMask))
241 unset($objectValues[$fieldName]);
249 foreach ($objectValues as $fieldName => $value)
253 $objectValues[$fieldName] = $value->collectValues($valuesType, $fieldsMask, $recursive);
257 $arrayCollection = [];
258 foreach ($value as $relationObject)
260 $arrayCollection[] = $relationObject->collectValues($valuesType, $fieldsMask, $recursive);
262 $objectValues[$fieldName] = $arrayCollection;
270 foreach ($objectValues as $k => $v)
272 $values[$this->entity->getField($k)->getName()] = $v;
289 switch ($this->state)
302 if ($this->_savingInProgress)
307 $this->_savingInProgress =
true;
312 foreach ($this->_currentValues as $fieldName => $currentValue)
314 $field = $this->entity->getField($fieldName);
318 $actualValue = $this->_actualValues[$fieldName];
320 if ($field->encode($currentValue) !== $field->encode($actualValue))
331 unset($this->_currentValues[$fieldName]);
340 $data[
'__object'] = $this;
343 $result = $dataClass::add($data);
346 if (!$result->isSuccess())
348 $this->_savingInProgress =
false;
354 foreach ($result->getPrimary() as $primaryName => $primaryValue)
356 $this->sysSetActual($primaryName, $primaryValue);
359 $this->sysSetValue($primaryName, $primaryValue);
363 $this->sysOnPrimarySet();
368 if (!empty($this->_currentValues))
371 $data[
'__object'] = $this;
374 $result = $dataClass::update($this->primary, $data);
377 if (!$result->isSuccess())
379 $this->_savingInProgress =
false;
387 $this->sysSaveRelations($result);
390 if (!$result->isSuccess())
395 $this->sysPostSave();
397 $this->_savingInProgress =
false;
409 final public function delete()
414 foreach ($this->entity->getFields() as $field)
418 if ($field->getCascadeDeletePolicy() === CascadePolicy::FOLLOW)
421 $remoteObject = $this->sysGetValue($field->getName());
422 $remoteObject->delete();
427 if ($field->getCascadeDeletePolicy() === CascadePolicy::FOLLOW)
430 $collection = $this->sysFillRelationCollection($field);
432 foreach ($collection as $object)
437 elseif ($field->getCascadeDeletePolicy() === CascadePolicy::SET_NULL)
440 $this->sysRemoveAllFromCollection($field->getName());
443 elseif ($field instanceof ManyToMany)
445 if ($field->getCascadeDeletePolicy() === CascadePolicy::FOLLOW_ORPHANS)
449 elseif ($field->getCascadeDeletePolicy() === CascadePolicy::SET_NULL)
455 $this->sysRemoveAllFromCollection($field->getName());
459 $this->sysSaveRelations($result);
463 $dataClass::setCurrentDeletingObject($this);
464 $deleteResult = $dataClass::delete($this->primary);
466 if (!$deleteResult->isSuccess())
468 $result->addErrors($deleteResult->getErrors());
472 foreach ($this->entity->getPrimaryArray()as $primaryName)
474 unset($this->_actualValues[$primaryName]);
491 final public static function wakeUp($row)
494 $objectClass = get_called_class();
499 $entity = $dataClass::getEntity();
500 $entityPrimary = $entity->getPrimaryArray();
508 if (count($entityPrimary) == 1)
510 $primary[$entityPrimary[0]] = $row;
515 throw new ArgumentException(sprintf(
516 'Multi-primary for %s was not found', $objectClass
522 foreach ($entityPrimary as $primaryName)
524 if (!isset($row[$primaryName]))
526 throw new ArgumentException(sprintf(
527 'Primary %s for %s was not found', $primaryName, $objectClass
531 $primary[$primaryName] = $row[$primaryName];
532 unset($row[$primaryName]);
538 $object =
new $objectClass(
false);
542 foreach ($primary as $primaryName => $primaryValue)
545 $primaryField = $entity->getField($primaryName);
546 $object->sysSetActual($primaryName, $primaryField->cast($primaryValue));
550 foreach ($row as $fieldName => $value)
553 $field = $entity->getField($fieldName);
555 if ($field instanceof IReadable)
557 $object->sysSetActual($fieldName, $field->cast($value));
562 if ($value instanceof
static || $value instanceof Collection)
565 $object->sysSetActual($fieldName, $value);
570 if ($field instanceof Reference)
573 $remoteObjectClass = $field->getRefEntity()->getObjectClass();
574 $remoteObject = $remoteObjectClass::wakeUp($value);
576 $object->sysSetActual($fieldName, $remoteObject);
578 elseif ($field instanceof OneToMany || $field instanceof ManyToMany)
581 $remoteCollectionClass = $field->getRefEntity()->getCollectionClass();
582 $remoteCollection = $remoteCollectionClass::wakeUp($value);
584 $object->sysSetActual($fieldName, $remoteCollection);
602 final public function fill($fields = FieldTypeMask::ALL)
605 $primaryFilter = Query::filter();
607 foreach ($this->sysRequirePrimary() as $primaryName => $primaryValue)
609 $primaryFilter->where($primaryName, $primaryValue);
613 if (is_array($fields))
616 $fieldsToSelect = $this->sysGetIdleFields($fields);
618 elseif (is_scalar($fields) && !is_numeric($fields))
622 $fieldsToSelect = $this->sysGetIdleFields($fields);
627 $fieldsToSelect = $this->sysGetIdleFieldsByMask($fields);
630 if (!empty($fieldsToSelect))
632 $fieldsToSelect = array_merge($this->entity->getPrimaryArray(), $fieldsToSelect);
636 $result = $dataClass::query()->setSelect($fieldsToSelect)->where($primaryFilter)->exec();
639 $im =
new IdentityMap;
642 $result->setIdentityMap($im);
643 $result->fetchObject();
646 foreach ($fieldsToSelect as $fieldName)
649 if ($this->entity->hasField($fieldName))
651 $field = $this->entity->getField($fieldName);
653 if ($field instanceof OneToMany || $field instanceof ManyToMany)
656 $collection = $this->sysGetValue($fieldName);
658 if (empty($collection))
660 $collection = $field->getRefEntity()->createCollection();
661 $this->_actualValues[$fieldName] = $collection;
664 $collection->sysSetFilled();
671 if (is_array($fields) && count($fields) == 1 && $this->entity->hasField(current($fields)))
673 return $this->sysGetValue(current($fields));
687 if (array_key_exists(
'ID', $this->_currentValues))
689 return $this->_currentValues[
'ID'];
691 elseif (array_key_exists(
'ID', $this->_actualValues))
693 return $this->_actualValues[
'ID'];
695 elseif (!$this->entity->hasField(
'ID'))
698 'Unknown method `%s` for object `%s`',
'getId', get_called_class()
714 final public function get($fieldName)
716 return $this->__call(__FUNCTION__, func_get_args());
728 return $this->__call(__FUNCTION__, func_get_args());
738 final public function require($fieldName)
740 return $this->__call(__FUNCTION__, func_get_args());
751 final public function set($fieldName, $value)
753 return $this->__call(__FUNCTION__, func_get_args());
763 final public function reset($fieldName)
765 return $this->__call(__FUNCTION__, func_get_args());
775 final public function unset($fieldName)
777 return $this->__call(__FUNCTION__, func_get_args());
787 final public function has($fieldName)
789 return $this->__call(__FUNCTION__, func_get_args());
799 final public function isFilled($fieldName)
801 return $this->__call(__FUNCTION__, func_get_args());
811 final public function isChanged($fieldName)
813 return $this->__call(__FUNCTION__, func_get_args());
824 final public function addTo($fieldName, $value)
826 return $this->__call(__FUNCTION__, func_get_args());
837 final public function removeFrom($fieldName, $value)
839 return $this->__call(__FUNCTION__, func_get_args());
849 final public function removeAll($fieldName)
851 return $this->__call(__FUNCTION__, func_get_args());
854 final public function defineAuthContext(Context $authContext)
856 $this->_authContext = $authContext;
868 public function __get($name)
873 return $this->sysGetEntity();
875 return $this->sysGetPrimary();
876 case 'primaryAsString':
877 return $this->sysGetPrimaryAsString();
879 return $this->sysGetState();
881 throw new SystemException(
'Property `dataClass` should be received as static.');
884 if ($this->_customData ===
null)
886 $this->_customData =
new Dictionary;
895 throw new SystemException(sprintf(
896 'Unknown property `%s` for object `%s`', $name, get_called_class()
908 public function __set($name, $value)
913 return $this->defineAuthContext($value);
919 throw new SystemException(sprintf(
920 'Property `%s` for object `%s` is read-only', $name, get_called_class()
924 throw new SystemException(sprintf(
925 'Unknown property `%s` for object `%s`', $name, get_called_class()
939 public function __call($name, $arguments)
941 $first3 = substr($name, 0, 3);
944 if ($first3 ==
'get')
946 $fieldName = self::sysMethodToFieldCase(substr($name, 3));
948 if ($fieldName ==
'')
950 $fieldName = StringHelper::strtoupper($arguments[0]);
953 if (array_key_exists($fieldName, $this->_runtimeValues))
955 return $this->sysGetRuntime($fieldName);
959 $personalMethodName = $name.static::sysFieldToMethodCase($fieldName);
961 if (method_exists($this, $personalMethodName))
963 return $this->$personalMethodName(...array_slice($arguments, 1));
967 $this->entity->getField($fieldName);
971 if ($this->entity->hasField($fieldName))
973 return $this->sysGetValue($fieldName);
978 if ($first3 ==
'set')
980 $fieldName = self::sysMethodToFieldCase(substr($name, 3));
981 $value = $arguments[0];
983 if ($fieldName ==
'')
985 $fieldName = StringHelper::strtoupper($arguments[0]);
986 $value = $arguments[1];
989 if (array_key_exists($fieldName, $this->_runtimeValues))
991 throw new SystemException(sprintf(
992 'Setting value for runtime field `%s` in `%s` is not allowed, it is read-only field',
993 $fieldName, get_called_class()
998 $personalMethodName = $name.static::sysFieldToMethodCase($fieldName);
1000 if (method_exists($this, $personalMethodName))
1002 return $this->$personalMethodName(...array_slice($arguments, 1));
1006 $this->entity->getField($fieldName);
1010 if ($this->entity->hasField($fieldName))
1012 $field = $this->entity->getField($fieldName);
1014 if ($field instanceof IReadable && !($value instanceof SqlExpression))
1016 $value = $field->cast($value);
1019 return $this->sysSetValue($fieldName, $value);
1023 if ($first3 ==
'has')
1025 $fieldName = self::sysMethodToFieldCase(substr($name, 3));
1027 if ($fieldName ==
'')
1029 $fieldName = StringHelper::strtoupper($arguments[0]);
1032 $personalMethodName = $name.static::sysFieldToMethodCase($fieldName);
1034 if (method_exists($this, $personalMethodName))
1036 return $this->$personalMethodName(...array_slice($arguments, 1));
1040 if (array_key_exists($fieldName, $this->_runtimeValues))
1046 $this->entity->getField($fieldName);
1049 if ($this->entity->hasField($fieldName))
1051 return $this->sysHasValue($fieldName);
1055 $first4 = substr($name, 0, 4);
1058 if ($first4 ==
'fill')
1060 $fieldName = self::sysMethodToFieldCase(substr($name, 4));
1065 if ($this->entity->hasField($fieldName))
1067 return $this->
fill([$fieldName]);
1071 $first5 = substr($name, 0, 5);
1074 if ($first5 ==
'addTo')
1076 $fieldName = self::sysMethodToFieldCase(substr($name, 5));
1077 $value = $arguments[0];
1079 if ($fieldName ==
'')
1081 $fieldName = StringHelper::strtoupper($arguments[0]);
1082 $value = $arguments[1];
1085 $personalMethodName = $name.static::sysFieldToMethodCase($fieldName);
1087 if (method_exists($this, $personalMethodName))
1089 return $this->$personalMethodName(...array_slice($arguments, 1));
1093 $this->entity->getField($fieldName);
1096 if ($this->entity->hasField($fieldName))
1098 return $this->sysAddToCollection($fieldName, $value);
1103 if ($first5 ==
'unset')
1105 $fieldName = self::sysMethodToFieldCase(substr($name, 5));
1107 if ($fieldName ==
'')
1109 $fieldName = StringHelper::strtoupper($arguments[0]);
1112 $personalMethodName = $name.static::sysFieldToMethodCase($fieldName);
1114 if (method_exists($this, $personalMethodName))
1116 return $this->$personalMethodName(...array_slice($arguments, 1));
1120 $this->entity->getField($fieldName);
1123 if ($this->entity->hasField($fieldName))
1125 return $this->sysUnset($fieldName);
1130 if ($first5 ==
'reset')
1132 $fieldName = self::sysMethodToFieldCase(substr($name, 5));
1134 if ($fieldName ==
'')
1136 $fieldName = StringHelper::strtoupper($arguments[0]);
1139 $personalMethodName = $name.static::sysFieldToMethodCase($fieldName);
1141 if (method_exists($this, $personalMethodName))
1143 return $this->$personalMethodName(...array_slice($arguments, 1));
1147 $this->entity->getField($fieldName);
1150 if ($this->entity->hasField($fieldName))
1152 $field = $this->entity->getField($fieldName);
1154 if ($field instanceof OneToMany || $field instanceof ManyToMany)
1156 return $this->sysResetRelation($fieldName);
1160 return $this->sysReset($fieldName);
1165 $first9 = substr($name, 0, 9);
1168 if ($first9 ==
'removeAll')
1170 $fieldName = self::sysMethodToFieldCase(substr($name, 9));
1172 if ($fieldName ==
'')
1174 $fieldName = StringHelper::strtoupper($arguments[0]);
1177 $personalMethodName = $name.static::sysFieldToMethodCase($fieldName);
1179 if (method_exists($this, $personalMethodName))
1181 return $this->$personalMethodName(...array_slice($arguments, 1));
1185 $this->entity->getField($fieldName);
1188 if ($this->entity->hasField($fieldName))
1190 return $this->sysRemoveAllFromCollection($fieldName);
1194 $first10 = substr($name, 0, 10);
1197 if ($first10 ==
'removeFrom')
1199 $fieldName = self::sysMethodToFieldCase(substr($name, 10));
1200 $value = $arguments[0];
1202 if ($fieldName ==
'')
1204 $fieldName = StringHelper::strtoupper($arguments[0]);
1205 $value = $arguments[1];
1208 $personalMethodName = $name.static::sysFieldToMethodCase($fieldName);
1210 if (method_exists($this, $personalMethodName))
1212 return $this->$personalMethodName(...array_slice($arguments, 1));
1216 $this->entity->getField($fieldName);
1219 if ($this->entity->hasField($fieldName))
1221 return $this->sysRemoveFromCollection($fieldName, $value);
1225 $first12 = substr($name, 0, 12);
1228 if ($first12 ==
'remindActual')
1230 $fieldName = self::sysMethodToFieldCase(substr($name, 12));
1232 if ($fieldName ==
'')
1234 $fieldName = StringHelper::strtoupper($arguments[0]);
1237 $personalMethodName = $name.static::sysFieldToMethodCase($fieldName);
1239 if (method_exists($this, $personalMethodName))
1241 return $this->$personalMethodName(...array_slice($arguments, 1));
1245 $this->entity->getField($fieldName);
1249 if ($this->entity->hasField($fieldName))
1251 return $this->_actualValues[$fieldName] ??
null;
1255 $first7 = substr($name, 0, 7);
1258 if ($first7 ==
'require')
1260 $fieldName = self::sysMethodToFieldCase(substr($name, 7));
1262 if ($fieldName ==
'')
1264 $fieldName = StringHelper::strtoupper($arguments[0]);
1267 $personalMethodName = $name.static::sysFieldToMethodCase($fieldName);
1269 if (method_exists($this, $personalMethodName))
1271 return $this->$personalMethodName(...array_slice($arguments, 1));
1275 $this->entity->getField($fieldName);
1279 if ($this->entity->hasField($fieldName))
1281 return $this->sysGetValue($fieldName,
true);
1285 $first2 = substr($name, 0, 2);
1286 $last6 = substr($name, -6);
1289 if ($first2 ==
'is' && $last6 ==
'Filled')
1291 $fieldName = self::sysMethodToFieldCase(substr($name, 2, -6));
1293 if ($fieldName ==
'')
1295 $fieldName = StringHelper::strtoupper($arguments[0]);
1298 $personalMethodName = $first2.static::sysFieldToMethodCase($fieldName).$last6;
1300 if (method_exists($this, $personalMethodName))
1302 return $this->$personalMethodName(...array_slice($arguments, 1));
1306 $this->entity->getField($fieldName);
1309 if ($this->entity->hasField($fieldName))
1311 $field = $this->entity->getField($fieldName);
1313 if ($field instanceof OneToMany || $field instanceof ManyToMany)
1315 return array_key_exists($fieldName, $this->_actualValues) && $this->_actualValues[$fieldName]->sysIsFilled();
1319 return $this->sysIsFilled($fieldName);
1324 $last7 = substr($name, -7);
1327 if ($first2 ==
'is' && $last7 ==
'Changed')
1329 $fieldName = self::sysMethodToFieldCase(substr($name, 2, -7));
1331 if ($fieldName ==
'')
1333 $fieldName = StringHelper::strtoupper($arguments[0]);
1336 $personalMethodName = $first2.static::sysFieldToMethodCase($fieldName).$last7;
1338 if (method_exists($this, $personalMethodName))
1340 return $this->$personalMethodName(...array_slice($arguments, 1));
1344 $this->entity->getField($fieldName);
1347 if ($this->entity->hasField($fieldName))
1349 $field = $this->entity->getField($fieldName);
1351 if ($field instanceof OneToMany || $field instanceof ManyToMany)
1353 return array_key_exists($fieldName, $this->_actualValues) && $this->_actualValues[$fieldName]->sysIsChanged();
1357 return $this->sysIsChanged($fieldName);
1362 throw new SystemException(sprintf(
1363 'Unknown method `%s` for object `%s`', $name, get_called_class()
1372 public function sysGetEntity()
1374 if ($this->_entity ===
null)
1378 $this->_entity = $dataClass::getEntity();
1391 public function sysGetPrimary()
1393 $primaryValues = [];
1395 foreach ($this->entity->getPrimaryArray() as $primaryName)
1397 $primaryValues[$primaryName] = $this->sysGetValue($primaryName);
1400 return $primaryValues;
1403 public function sysGetPrimaryAsString()
1405 return static::sysSerializePrimary($this->sysGetPrimary(), $this->_entity);
1416 public function sysGetRuntime($name)
1418 return $this->_runtimeValues[$name] ??
null;
1430 public function sysSetRuntime($name, $value)
1432 $this->_runtimeValues[$name] = $value;
1444 public function sysSetActual($fieldName, $value)
1446 $fieldName = StringHelper::strtoupper($fieldName);
1447 $this->_actualValues[$fieldName] = $value;
1451 if ($this->entity->getField($fieldName) instanceof ObjectField)
1453 $this->_currentValues[$fieldName] = clone $value;
1464 public function sysChangeState($state)
1466 if ($this->_state !== $state)
1482 $this->_state = $state;
1494 public function sysGetState()
1509 public function sysGetValue($fieldName, $require =
false)
1511 $fieldName = StringHelper::strtoupper($fieldName);
1513 if (array_key_exists($fieldName, $this->_currentValues))
1515 return $this->_currentValues[$fieldName];
1519 if ($require && !array_key_exists($fieldName, $this->_actualValues))
1521 throw new SystemException(sprintf(
1522 '%s value is required for further operations', $fieldName
1526 return $this->_actualValues[$fieldName] ??
null;
1541 public function sysSetValue($fieldName, $value)
1543 $fieldName = StringHelper::strtoupper($fieldName);
1544 $field = $this->entity->getField($fieldName);
1547 if ($field instanceof ScalarField)
1550 if ($this->_state !==
State::RAW && in_array($field->getName(), $this->entity->getPrimaryArray()))
1552 throw new SystemException(sprintf(
1553 'Setting value for Primary `%s` in `%s` is not allowed, it is read-only field',
1554 $field->getName(), get_called_class()
1560 if ($field instanceof ExpressionField && !($field instanceof UserTypeField))
1562 throw new SystemException(sprintf(
1563 'Setting value for ExpressionField `%s` in `%s` is not allowed, it is read-only field',
1564 $fieldName, get_called_class()
1568 if ($field instanceof Reference)
1573 $remoteObjectClass = $field->getRefEntity()->getObjectClass();
1575 if (!($value instanceof $remoteObjectClass))
1577 throw new ArgumentException(sprintf(
1578 'Expected instance of `%s`, got `%s` instead',
1580 is_object($value) ? get_class($value) : gettype($value)
1590 if (array_key_exists($fieldName, $this->_actualValues) && !($field instanceof UserTypeField))
1592 if ($field instanceof IReadable)
1594 if ($field->cast($value) === $this->_actualValues[$fieldName]
1596 || $field->convertValueToDb($field->modifyValueBeforeSave($value, []))
1597 === $field->convertValueToDb($field->modifyValueBeforeSave($this->_actualValues[$fieldName], []))
1601 unset($this->_currentValues[$fieldName]);
1605 elseif ($field instanceof Reference)
1608 if ($value->primary === $this->_actualValues[$fieldName]->primary)
1611 unset($this->_currentValues[$fieldName]);
1618 if ($field instanceof ScalarField || $field instanceof UserTypeField)
1620 $this->_currentValues[$fieldName] = $value;
1622 elseif ($field instanceof Reference)
1625 $this->_currentValues[$fieldName] = $value;
1628 $elementals = $field->getElementals();
1630 if (!empty($elementals))
1632 $elementalsChanged =
false;
1634 foreach ($elementals as $localFieldName => $remoteFieldName)
1636 if ($this->entity->getField($localFieldName)->isPrimary())
1645 if ($this->state ===
State::RAW && $this->entity->getField($localFieldName)->isAutocomplete())
1651 $remoteField = $field->getRefEntity()->getField($remoteFieldName);
1653 if (!empty($value) && !$value->sysHasValue($remoteField->getName())
1654 && $value->state ===
State::RAW && $remoteField->isPrimary() && $remoteField->isAutocomplete())
1657 $localObject = $this;
1658 $remoteObject = $value;
1660 $remoteObject->sysAddOnPrimarySetListener(
function () use (
1661 $localObject, $localFieldName, $remoteObject, $remoteFieldName
1663 $localObject->sysSetValue($localFieldName, $remoteObject->get($remoteFieldName));
1668 $elementalValue = empty($value) ? null : $value->sysGetValue($remoteFieldName);
1669 $this->sysSetValue($localFieldName, $elementalValue);
1672 $elementalsChanged =
true;
1675 if (!$elementalsChanged)
1684 throw new SystemException(sprintf(
1685 'Unknown field type `%s` in system setter of `%s`', get_class($field), get_called_class()
1695 if ($field instanceof ScalarField && $field->isPrimary() && $this->sysHasPrimary())
1697 $this->sysOnPrimarySet();
1710 public function sysHasValue($fieldName)
1712 $fieldName = StringHelper::strtoupper($fieldName);
1714 return $this->sysIsFilled($fieldName) || $this->sysIsChanged($fieldName);
1724 public function sysIsFilled($fieldName)
1726 $fieldName = StringHelper::strtoupper($fieldName);
1728 return array_key_exists($fieldName, $this->_actualValues);
1738 public function sysIsChanged($fieldName)
1740 $fieldName = StringHelper::strtoupper($fieldName);
1741 $field = $this->entity->getField($fieldName);
1743 if ($field instanceof ObjectField)
1745 $currentValue = $this->_currentValues[$fieldName];
1746 $actualValue = $this->_actualValues[$fieldName];
1748 return $field->encode($currentValue) !== $field->encode($actualValue);
1751 return array_key_exists($fieldName, $this->_currentValues);
1759 public function sysHasPrimary()
1761 foreach ($this->primary as $primaryValue)
1763 if ($primaryValue ===
null)
1775 public function sysOnPrimarySet()
1778 if ($this->sysHasPrimary())
1780 foreach ($this->_onPrimarySetListeners as $listener)
1782 call_user_func($listener, $this);
1792 public function sysAddOnPrimarySetListener($callback)
1795 $this->_onPrimarySetListeners[] = $callback;
1805 public function sysUnset($fieldName)
1807 $fieldName = StringHelper::strtoupper($fieldName);
1809 unset($this->_currentValues[$fieldName]);
1810 unset($this->_actualValues[$fieldName]);
1822 public function sysReset($fieldName)
1824 $fieldName = StringHelper::strtoupper($fieldName);
1826 unset($this->_currentValues[$fieldName]);
1838 public function sysResetRelation($fieldName)
1840 $fieldName = StringHelper::strtoupper($fieldName);
1842 if (isset($this->_actualValues[$fieldName]))
1845 $collection = $this->_actualValues[$fieldName];
1846 $collection->sysResetChanges(
true);
1859 public function sysRequirePrimary()
1861 $primaryValues = [];
1863 foreach ($this->entity->getPrimaryArray() as $primaryName)
1867 $primaryValues[$primaryName] = $this->sysGetValue($primaryName,
true);
1869 catch (SystemException $e)
1871 throw new SystemException(sprintf(
1872 'Primary `%s` value is required for further operations', $primaryName
1877 return $primaryValues;
1889 public function sysGetIdleFields($fields = [])
1896 $fields = array_keys($this->entity->getFields());
1899 foreach ($fields as $fieldName)
1901 $fieldName = StringHelper::strtoupper($fieldName);
1903 if (!isset($this->_actualValues[$fieldName]))
1906 $list[] = $fieldName;
1908 elseif ($this->_actualValues[$fieldName] instanceof Collection && !$this->_actualValues[$fieldName]->sysIsFilled())
1911 $list[] = $fieldName;
1927 public function sysGetIdleFieldsByMask($mask = FieldTypeMask::ALL)
1931 foreach ($this->entity->getFields() as $field)
1933 $fieldMask = $field->getTypeMask();
1935 if (!isset($this->_actualValues[StringHelper::strtoupper($field->getName())])
1936 && ($mask & $fieldMask)
1939 $list[] = $field->getName();
1954 public function sysSaveRelations(Result $result)
1956 $saveCascade =
true;
1958 foreach ($this->_actualValues as $fieldName => $value)
1960 $field = $this->entity->getField($fieldName);
1962 if ($field instanceof Reference && !array_key_exists($fieldName, $this->_currentValues))
1965 if ($saveCascade && !empty($value))
1970 elseif ($field instanceof OneToMany)
1972 $collection = $value;
1975 $objectsToSave = [];
1978 $objectsToDelete = [];
1980 if ($collection->sysIsChanged())
1983 foreach ($collection->sysGetChanges() as $change)
1985 list($remoteObject, $changeType) = $change;
1989 $objectsToSave[] = $remoteObject;
1993 if ($field->getCascadeDeletePolicy() == CascadePolicy::FOLLOW)
1995 $objectsToDelete[] = $remoteObject;
2000 $objectsToSave[] = $remoteObject;
2009 foreach ($collection->getAll() as $remoteObject)
2011 if (!in_array($remoteObject, $objectsToDelete) && !in_array($remoteObject, $objectsToSave))
2013 $objectsToSave[] = $remoteObject;
2019 foreach ($objectsToSave as $remoteObject)
2021 $remoteResult = $remoteObject->save();
2023 if (!$remoteResult->isSuccess())
2025 $result->addErrors($remoteResult->getErrors());
2030 foreach ($objectsToDelete as $remoteObject)
2032 $remoteResult = $remoteObject->delete();
2034 if (!$remoteResult->isSuccess())
2036 $result->addErrors($remoteResult->getErrors());
2041 if ($collection->sysIsChanged())
2043 $collection->sysResetChanges();
2046 elseif ($field instanceof ManyToMany)
2048 $collection = $value;
2050 if ($value->sysIsChanged())
2052 foreach ($collection->sysGetChanges() as $change)
2054 list($remoteObject, $changeType) = $change;
2057 $mediatorObjectClass = $field->getMediatorEntity()->getObjectClass();
2058 $localReferenceName = $field->getLocalReferenceName();
2059 $remoteReferenceName = $field->getRemoteReferenceName();
2062 $mediatorObject =
new $mediatorObjectClass;
2063 $mediatorObject->sysSetValue($localReferenceName, $this);
2064 $mediatorObject->sysSetValue($remoteReferenceName, $remoteObject);
2069 $mediatorObject->save();
2074 $mediatorDataClass = $field->getMediatorEntity()->getDataClass();
2075 $mediatorDataClass::delete($mediatorObject->primary);
2080 $collection->sysResetChanges();
2086 foreach ($collection->getAll() as $remoteObject)
2088 $remoteResult = $remoteObject->save();
2090 if (!$remoteResult->isSuccess())
2092 $result->addErrors($remoteResult->getErrors());
2099 if ($value instanceof Collection)
2101 $value->sysReviseDeletedObjects();
2107 $this->sysSaveCurrentReferences();
2111 public function sysSaveCurrentReferences()
2113 foreach ($this->_currentValues as $fieldName => $value)
2115 if ($this->entity->getField($fieldName) instanceof Reference && !empty($value))
2122 public function sysPostSave()
2125 foreach ($this->_currentValues as $k => $v)
2127 $field = $this->entity->getField($k);
2130 if ($v instanceof EntityObject)
2141 $this->sysSetActual($k, $v);
2144 elseif ($field instanceof ScalarField || $field instanceof UserTypeField)
2146 $v = $field->cast($v);
2148 if ($v instanceof SqlExpression)
2153 $this->sysSetActual($k, $v);
2157 unset($this->_currentValues[$k]);
2164 foreach ($this->_actualValues as $fieldName => $actualValue)
2166 if ($this->entity->getField($fieldName) instanceof ObjectField)
2168 $this->_currentValues[$fieldName] = clone $actualValue;
2182 public function sysAddToCollection($fieldName, $remoteObject)
2184 $fieldName = StringHelper::strtoupper($fieldName);
2187 $field = $this->entity->getField($fieldName);
2188 $remoteObjectClass = $field->getRefEntity()->getObjectClass();
2191 if (!($remoteObject instanceof $remoteObjectClass))
2193 throw new ArgumentException(sprintf(
2194 'Expected instance of `%s`, got `%s` instead', $remoteObjectClass, get_class($remoteObject)
2199 $collection = $this->sysGetValue($fieldName);
2201 if (empty($collection))
2203 $collection = $field->getRefEntity()->createCollection();
2204 $this->_actualValues[$fieldName] = $collection;
2207 if ($field instanceof OneToMany)
2210 $remoteFieldName = $field->getRefField()->getName();
2211 $remoteObject->sysSetValue($remoteFieldName, $this);
2216 $localObject = $this;
2218 $this->sysAddOnPrimarySetListener(
function () use ($localObject, $remoteObject, $remoteFieldName) {
2219 $remoteObject->sysSetValue($remoteFieldName, $localObject);
2225 $collection->add($remoteObject);
2243 public function sysRemoveFromCollection($fieldName, $remoteObject)
2245 $fieldName = StringHelper::strtoupper($fieldName);
2248 $field = $this->entity->getField($fieldName);
2249 $remoteObjectClass = $field->getRefEntity()->getObjectClass();
2252 if (!($remoteObject instanceof $remoteObjectClass))
2254 throw new ArgumentException(sprintf(
2255 'Expected instance of `%s`, got `%s` instead', $remoteObjectClass, get_class($remoteObject)
2260 $collection = $this->sysGetValue($fieldName);
2262 if (empty($collection))
2264 $collection = $field->getRefEntity()->createCollection();
2265 $this->_actualValues[$fieldName] = $collection;
2269 $collection->remove($remoteObject);
2271 if ($field instanceof OneToMany)
2274 if ($field->getCascadeDeletePolicy() == CascadePolicy::FOLLOW)
2281 $remoteFieldName = $field->getRefField()->getName();
2282 $remoteObject->sysSetValue($remoteFieldName,
null);
2302 public function sysRemoveAllFromCollection($fieldName)
2304 $fieldName = StringHelper::strtoupper($fieldName);
2305 $collection = $this->sysFillRelationCollection($fieldName);
2308 foreach ($collection as $remoteObject)
2310 $this->sysRemoveFromCollection($fieldName, $remoteObject);
2321 public function sysFillRelationCollection($field)
2323 if ($field instanceof Relation)
2325 $fieldName = $field->getName();
2329 $fieldName = $field;
2330 $field = $this->entity->getField($fieldName);
2334 $collection = $this->sysGetValue($fieldName);
2336 if (empty($collection))
2338 $collection = $field->getRefEntity()->createCollection();
2339 $this->_actualValues[$fieldName] = $collection;
2342 if (!$collection->sysIsFilled())
2345 $remotePrimaryDefinitions = [];
2347 foreach ($field->getRefEntity()->getPrimaryArray() as $primaryName)
2349 $remotePrimaryDefinitions[] = $fieldName.
'.'.$primaryName;
2352 $this->
fill($remotePrimaryDefinitions);
2355 $collection->sysSetFilled();
2368 public static function sysMethodToFieldCase($methodName)
2370 if (!isset(static::$_camelToSnakeCache[$methodName]))
2372 static::$_camelToSnakeCache[$methodName] = StringHelper::strtoupper(
2373 StringHelper::camel2snake($methodName)
2377 return static::$_camelToSnakeCache[$methodName];
2387 public static function sysFieldToMethodCase($fieldName)
2389 if (!isset(static::$_snakeToCamelCache[$fieldName]))
2391 static::$_snakeToCamelCache[$fieldName] = StringHelper::snake2camel($fieldName);
2394 return static::$_snakeToCamelCache[$fieldName];
2404 public static function sysSerializePrimary($primary, $entity)
2406 if (count($entity->getPrimaryArray()) == 1)
2408 return (
string) current($primary);
2411 return (
string) Json::encode(array_values($primary));
2425 return $this->sysHasValue($offset) && $this->sysGetValue($offset) !==
null;
2437 #[\ReturnTypeWillChange]
2443 return $this->
get($offset);
2445 elseif (array_key_exists($offset, $this->_runtimeValues))
2448 return $this->sysGetRuntime($offset);
2451 return $this->
offsetExists($offset) ? $this->
get($offset) : null;
2463 public function offsetSet($offset, $value): void
2465 if (is_null($offset))
2467 throw new ArgumentException(
'Field name should be set');
2471 $this->
set($offset, $value);
2482 $this->unset($offset);