1C-Bitrix 25.700.0
Загрузка...
Поиск...
Не найдено
entityobject.php
См. документацию.
1<?php
8
9namespace Bitrix\Main\ORM\Objectify;
10
11use ArrayAccess;
12use Bitrix\Main\Authentication\Context;
13use Bitrix\Main\DB\SqlExpression;
14use Bitrix\Main\ORM\Data\AddResult;
15use Bitrix\Main\ORM\Data\DataManager;
16use Bitrix\Main\ORM\Data\UpdateResult;
17use Bitrix\Main\ORM\Entity;
18use Bitrix\Main\ORM\EntityError;
19use Bitrix\Main\ORM\Fields\ExpressionField;
20use Bitrix\Main\ORM\Fields\IReadable;
21use Bitrix\Main\ORM\Fields\ObjectField;
22use Bitrix\Main\ORM\Fields\Relations\CascadePolicy;
23use Bitrix\Main\ORM\Fields\Relations\ManyToMany;
24use Bitrix\Main\ORM\Fields\Relations\OneToMany;
25use Bitrix\Main\ORM\Fields\Relations\Relation;
26use Bitrix\Main\ORM\Fields\UserTypeField;
27use Bitrix\Main\ORM\Query\Query;
28use Bitrix\Main\ORM\Fields\Relations\Reference;
29use Bitrix\Main\ORM\Data\Result;
30use Bitrix\Main\ORM\Fields\ScalarField;
31use Bitrix\Main\ORM\Fields\FieldTypeMask;
32use Bitrix\Main\SystemException;
33use Bitrix\Main\ArgumentException;
34use Bitrix\Main\Text\StringHelper;
35use Bitrix\Main\Type\Dictionary;
36use Bitrix\Main\Web\Json;
37
51abstract class EntityObject implements ArrayAccess
52{
57 static public $dataClass;
58
60 protected $_entity;
61
66 protected $_state = State::RAW;
67
72 protected $_actualValues = [];
73
78 protected $_currentValues = [];
79
84 protected $_runtimeValues = [];
85
89 protected $_customData = null;
90
93
95 protected $_authContext;
96
98 protected $_savingInProgress = false;
99
104 static protected $_camelToSnakeCache = [];
105
110 static protected $_snakeToCamelCache = [];
111
120 final public function __construct($setDefaultValues = true)
121 {
122 if (is_array($setDefaultValues))
123 {
124 // we have custom default values
125 foreach ($setDefaultValues as $fieldName => $defaultValue)
126 {
127 $field = $this->entity->getField($fieldName);
128
129 if ($field instanceof Reference)
130 {
131 if (is_array($defaultValue))
132 {
133 $defaultValue = $field->getRefEntity()->createObject($defaultValue);
134 }
135
136 $this->set($fieldName, $defaultValue);
137 }
138 elseif (($field instanceof OneToMany || $field instanceof ManyToMany)
139 && is_array($defaultValue))
140 {
141 foreach ($defaultValue as $subValue)
142 {
143 if (is_array($subValue))
144 {
145 $subValue = $field->getRefEntity()->createObject($subValue);
146 }
147
148 $this->addTo($fieldName, $subValue);
149 }
150 }
151 else
152 {
153 $this->set($fieldName, $defaultValue);
154 }
155 }
156 }
157
158 // set map default values
159 if ($setDefaultValues || is_array($setDefaultValues))
160 {
161 foreach ($this->entity->getScalarFields() as $fieldName => $field)
162 {
163 if ($this->sysHasValue($fieldName))
164 {
165 // already set custom default value
166 continue;
167 }
168
169 $defaultValue = $field->getDefaultValue($this);
170
171 if ($defaultValue !== null)
172 {
173 $this->set($fieldName, $defaultValue);
174 }
175 }
176 }
177 }
178
179 public function __clone()
180 {
181 $this->_actualValues = $this->cloneValues($this->_actualValues);
182 $this->_currentValues = $this->cloneValues($this->_currentValues);
183 }
184
185 protected function cloneValues(array $values): array
186 {
187 // Do not clone References to avoid infinite recursion
188 $valuesWithoutReferences = $this->filterValuesByMask($values, FieldTypeMask::REFERENCE, true);
189 $references = array_diff_key($values, $valuesWithoutReferences);
190
191 return array_merge($references, \Bitrix\Main\Type\Collection::clone($valuesWithoutReferences));
192 }
193
194 protected function filterValuesByMask(array $values, int $fieldsMask, bool $invertedFilter = false): array
195 {
196 if ($fieldsMask === FieldTypeMask::ALL)
197 {
198 return $invertedFilter ? [] : $values;
199 }
200
201 return array_filter($values, function($fieldName) use ($fieldsMask, $invertedFilter)
202 {
203 $maskOfSingleField = $this->entity->getField($fieldName)->getTypeMask();
204 $matchesMask = (bool)($fieldsMask & $maskOfSingleField);
205
206 return $invertedFilter ? !$matchesMask: $matchesMask;
207 }, ARRAY_FILTER_USE_KEY);
208 }
209
220 final public function collectValues($valuesType = Values::ALL, $fieldsMask = FieldTypeMask::ALL, $recursive = false)
221 {
222 switch ($valuesType)
223 {
224 case Values::ACTUAL:
225 $objectValues = $this->_actualValues;
226 break;
227 case Values::CURRENT:
228 $objectValues = $this->_currentValues;
229 break;
230 default:
231 $objectValues = array_merge($this->_actualValues, $this->_currentValues);
232 }
233
234 // filter with field mask
235 if ($fieldsMask !== FieldTypeMask::ALL)
236 {
237 foreach ($objectValues as $fieldName => $value)
238 {
239 $fieldMask = $this->entity->getField($fieldName)->getTypeMask();
240 if (!($fieldsMask & $fieldMask))
241 {
242 unset($objectValues[$fieldName]);
243 }
244 }
245 }
246
247 // recursive convert object to array
248 if ($recursive)
249 {
250 foreach ($objectValues as $fieldName => $value)
251 {
252 if ($value instanceof EntityObject)
253 {
254 $objectValues[$fieldName] = $value->collectValues($valuesType, $fieldsMask, $recursive);
255 }
256 elseif ($value instanceof Collection)
257 {
258 $arrayCollection = [];
259 foreach ($value as $relationObject)
260 {
261 $arrayCollection[] = $relationObject->collectValues($valuesType, $fieldsMask, $recursive);
262 }
263 $objectValues[$fieldName] = $arrayCollection;
264 }
265 }
266 }
267
268 // remap from uppercase to real field names
269 $values = [];
270
271 foreach ($objectValues as $k => $v)
272 {
273 $values[$this->entity->getField($k)->getName()] = $v;
274 }
275
276 return $values;
277 }
278
287 final public function save()
288 {
289 // default empty result
290 switch ($this->state)
291 {
292 case State::RAW:
293 $result = new AddResult();
294 break;
295 case State::CHANGED:
296 case State::ACTUAL:
297 $result = new UpdateResult();
298 break;
299 default:
300 $result = new Result();
301 }
302
303 if ($this->_savingInProgress)
304 {
305 return $result;
306 }
307
308 $this->_savingInProgress = true;
309
310 $dataClass = $this->entity->getDataClass();
311
312 // check for object fields, it could be changed without notification
313 foreach ($this->_currentValues as $fieldName => $currentValue)
314 {
315 $field = $this->entity->getField($fieldName);
316
317 if ($field instanceof ObjectField)
318 {
319 $actualValue = $this->_actualValues[$fieldName];
320
321 if ($field->encode($currentValue) !== $field->encode($actualValue))
322 {
323 if ($this->_state === State::ACTUAL)
324 {
325 // value has changed, set new state
326 $this->_state = State::CHANGED;
327 }
328 }
329 else
330 {
331 // value has not changed, hide it until postSave
332 unset($this->_currentValues[$fieldName]);
333 }
334 }
335 }
336
337 // save data
338 if ($this->_state == State::RAW)
339 {
341 $data['__object'] = $this;
342
343 // put secret key __object to array
344 $result = $dataClass::add($data);
345
346 // check for error
347 if (!$result->isSuccess())
348 {
349 $this->_savingInProgress = false;
350
351 return $result;
352 }
353
354 // set primary
355 foreach ($result->getPrimary() as $primaryName => $primaryValue)
356 {
357 $this->sysSetActual($primaryName, $primaryValue);
358
359 // db value has priority in case of custom value for autocomplete
360 $this->sysSetValue($primaryName, $primaryValue);
361 }
362
363 // on primary gain event
364 $this->sysOnPrimarySet();
365 }
366 elseif ($this->_state == State::CHANGED)
367 {
368 // changed scalar and reference
369 if (!empty($this->_currentValues))
370 {
372 $data['__object'] = $this;
373
374 // put secret key __object to array
375 $result = $dataClass::update($this->primary, $data);
376
377 // check for error
378 if (!$result->isSuccess())
379 {
380 $this->_savingInProgress = false;
381
382 return $result;
383 }
384 }
385 }
386
387 // changed collections
388 $this->sysSaveRelations($result);
389
390 // return if there were errors
391 if (!$result->isSuccess())
392 {
393 return $result;
394 }
395
396 $this->sysPostSave();
397
398 $this->_savingInProgress = false;
399
400 return $result;
401 }
402
410 final public function delete()
411 {
412 $result = new Result;
413
414 // delete relations
415 foreach ($this->entity->getFields() as $field)
416 {
417 if ($field instanceof Reference)
418 {
419 if ($field->getCascadeDeletePolicy() === CascadePolicy::FOLLOW)
420 {
422 $remoteObject = $this->sysGetValue($field->getName());
423 $remoteObject->delete();
424 }
425 }
426 elseif ($field instanceof OneToMany)
427 {
428 if ($field->getCascadeDeletePolicy() === CascadePolicy::FOLLOW)
429 {
430 // delete
431 $collection = $this->sysFillRelationCollection($field);
432
433 foreach ($collection as $object)
434 {
435 $object->delete();
436 }
437 }
438 elseif ($field->getCascadeDeletePolicy() === CascadePolicy::SET_NULL)
439 {
440 // set null
441 $this->sysRemoveAllFromCollection($field->getName());
442 }
443 }
444 elseif ($field instanceof ManyToMany)
445 {
446 if ($field->getCascadeDeletePolicy() === CascadePolicy::FOLLOW_ORPHANS)
447 {
448 // delete
449 }
450 elseif ($field->getCascadeDeletePolicy() === CascadePolicy::SET_NULL)
451 {
452 // set null
453 }
454
455 // always delete mediator records
456 $this->sysRemoveAllFromCollection($field->getName());
457 }
458 }
459
460 $this->sysSaveRelations($result);
461
462 // delete object itself
463 $dataClass = static::$dataClass;
464 $dataClass::setCurrentDeletingObject($this);
465 $deleteResult = $dataClass::delete($this->primary);
466
467 if (!$deleteResult->isSuccess())
468 {
469 $result->addErrors($deleteResult->getErrors());
470 }
471
472 // clear status
473 foreach ($this->entity->getPrimaryArray()as $primaryName)
474 {
475 unset($this->_actualValues[$primaryName]);
476 }
477
478 $this->sysChangeState(State::DELETED);
479
480 return $result;
481 }
482
492 final public static function wakeUp($row)
493 {
495 $objectClass = get_called_class();
496
498 $dataClass = static::$dataClass;
499
500 $entity = $dataClass::getEntity();
501 $entityPrimary = $entity->getPrimaryArray();
502
503 // normalize input data and primary
504 $primary = [];
505
506 if (!is_array($row))
507 {
508 // it could be single primary
509 if (count($entityPrimary) == 1)
510 {
511 $primary[$entityPrimary[0]] = $row;
512 $row = [];
513 }
514 else
515 {
516 throw new ArgumentException(sprintf(
517 'Multi-primary for %s was not found', $objectClass
518 ));
519 }
520 }
521 else
522 {
523 foreach ($entityPrimary as $primaryName)
524 {
525 if (!isset($row[$primaryName]))
526 {
527 throw new ArgumentException(sprintf(
528 'Primary %s for %s was not found', $primaryName, $objectClass
529 ));
530 }
531
532 $primary[$primaryName] = $row[$primaryName];
533 unset($row[$primaryName]);
534 }
535 }
536
537 // create object
539 $object = new $objectClass(false); // here go with false to not set default values
540 $object->sysChangeState(State::ACTUAL);
541
542 // set primary
543 foreach ($primary as $primaryName => $primaryValue)
544 {
546 $primaryField = $entity->getField($primaryName);
547 $object->sysSetActual($primaryName, $primaryField->cast($primaryValue));
548 }
549
550 // set other data
551 foreach ($row as $fieldName => $value)
552 {
554 $field = $entity->getField($fieldName);
555
556 if ($field instanceof IReadable)
557 {
558 $object->sysSetActual($fieldName, $field->cast($value));
559 }
560 else
561 {
562 // we have a relation
563 if ($value instanceof static || $value instanceof Collection)
564 {
565 // it is ready data
566 $object->sysSetActual($fieldName, $value);
567 }
568 else
569 {
570 // wake up relation
571 if ($field instanceof Reference)
572 {
573 // wake up an object
574 $remoteObjectClass = $field->getRefEntity()->getObjectClass();
575 $remoteObject = $remoteObjectClass::wakeUp($value);
576
577 $object->sysSetActual($fieldName, $remoteObject);
578 }
579 elseif ($field instanceof OneToMany || $field instanceof ManyToMany)
580 {
581 // wake up collection
582 $remoteCollectionClass = $field->getRefEntity()->getCollectionClass();
583 $remoteCollection = $remoteCollectionClass::wakeUp($value);
584
585 $object->sysSetActual($fieldName, $remoteCollection);
586 }
587 }
588 }
589 }
590
591 return $object;
592 }
593
603 final public function fill($fields = FieldTypeMask::ALL)
604 {
605 // object must have primary
606 $primaryFilter = Query::filter();
607
608 foreach ($this->sysRequirePrimary() as $primaryName => $primaryValue)
609 {
610 $primaryFilter->where($primaryName, $primaryValue);
611 }
612
613 // collect fields to be selected
614 if (is_array($fields))
615 {
616 // go through IDLE fields
617 $fieldsToSelect = $this->sysGetIdleFields($fields);
618 }
619 elseif (is_scalar($fields) && !is_numeric($fields))
620 {
621 // one custom field
622 $fields = [$fields];
623 $fieldsToSelect = $this->sysGetIdleFields($fields);
624 }
625 else
626 {
627 // get fields according to selector mask
628 $fieldsToSelect = $this->sysGetIdleFieldsByMask($fields);
629 }
630
631 if (!empty($fieldsToSelect))
632 {
633 $fieldsToSelect = array_merge($this->entity->getPrimaryArray(), $fieldsToSelect);
634
635 // build query
636 $dataClass = $this->entity->getDataClass();
637 $result = $dataClass::query()->setSelect($fieldsToSelect)->where($primaryFilter)->exec();
638
639 // set object to identityMap of result, and it will be partially completed by fetch
640 $im = new IdentityMap;
641 $im->put($this);
642
643 $result->setIdentityMap($im);
644 $result->fetchObject();
645
646 // set filled flag to collections
647 foreach ($fieldsToSelect as $fieldName)
648 {
649 // check field before continue, it could be remote REF.ID definition so we skip it here
650 if ($this->entity->hasField($fieldName))
651 {
652 $field = $this->entity->getField($fieldName);
653
654 if ($field instanceof OneToMany || $field instanceof ManyToMany)
655 {
657 $collection = $this->sysGetValue($fieldName);
658
659 if (empty($collection))
660 {
661 $collection = $field->getRefEntity()->createCollection();
662 $this->_actualValues[$fieldName] = $collection;
663 }
664
665 $collection->sysSetFilled();
666 }
667 }
668 }
669 }
670
671 // return field value it it was only one
672 if (is_array($fields) && count($fields) == 1 && $this->entity->hasField(current($fields)))
673 {
674 return $this->sysGetValue(current($fields));
675 }
676
677 return null;
678 }
679
686 public function getId()
687 {
688 if (array_key_exists('ID', $this->_currentValues))
689 {
690 return $this->_currentValues['ID'];
691 }
692 elseif (array_key_exists('ID', $this->_actualValues))
693 {
694 return $this->_actualValues['ID'];
695 }
696 elseif (!$this->entity->hasField('ID'))
697 {
698 throw new SystemException(sprintf(
699 'Unknown method `%s` for object `%s`', 'getId', get_called_class()
700 ));
701 }
702 else
703 {
704 return null;
705 }
706 }
707
715 final public function get($fieldName)
716 {
717 return $this->__call(__FUNCTION__, func_get_args());
718 }
719
727 final public function remindActual($fieldName)
728 {
729 return $this->__call(__FUNCTION__, func_get_args());
730 }
731
739 final public function require($fieldName)
740 {
741 return $this->__call(__FUNCTION__, func_get_args());
742 }
743
752 final public function set($fieldName, $value)
753 {
754 return $this->__call(__FUNCTION__, func_get_args());
755 }
756
764 final public function reset($fieldName)
765 {
766 return $this->__call(__FUNCTION__, func_get_args());
767 }
768
776 final public function unset($fieldName)
777 {
778 return $this->__call(__FUNCTION__, func_get_args());
779 }
780
788 final public function has($fieldName)
789 {
790 return $this->__call(__FUNCTION__, func_get_args());
791 }
792
800 final public function isFilled($fieldName)
801 {
802 return $this->__call(__FUNCTION__, func_get_args());
803 }
804
812 final public function isChanged($fieldName)
813 {
814 return $this->__call(__FUNCTION__, func_get_args());
815 }
816
825 final public function addTo($fieldName, $value)
826 {
827 return $this->__call(__FUNCTION__, func_get_args());
828 }
829
838 final public function removeFrom($fieldName, $value)
839 {
840 return $this->__call(__FUNCTION__, func_get_args());
841 }
842
850 final public function removeAll($fieldName)
851 {
852 return $this->__call(__FUNCTION__, func_get_args());
853 }
854
855 final public function defineAuthContext(Context $authContext)
856 {
857 $this->_authContext = $authContext;
858 }
859
869 public function __get($name)
870 {
871 switch ($name)
872 {
873 case 'entity':
874 return $this->sysGetEntity();
875 case 'primary':
876 return $this->sysGetPrimary();
877 case 'primaryAsString':
878 return $this->sysGetPrimaryAsString();
879 case 'state':
880 return $this->sysGetState();
881 case 'dataClass':
882 throw new SystemException('Property `dataClass` should be received as static.');
883 case 'customData':
884
885 if ($this->_customData === null)
886 {
887 $this->_customData = new Dictionary;
888 }
889
890 return $this->_customData;
891
892 case 'authContext':
893 return $this->_authContext;
894 }
895
896 throw new SystemException(sprintf(
897 'Unknown property `%s` for object `%s`', $name, get_called_class()
898 ));
899 }
900
909 public function __set($name, $value)
910 {
911 switch ($name)
912 {
913 case 'authContext':
914 $this->defineAuthContext($value);
915 return;
916 case 'entity':
917 case 'primary':
918 case 'dataClass':
919 case 'customData':
920 case 'state':
921 throw new SystemException(sprintf(
922 'Property `%s` for object `%s` is read-only', $name, get_called_class()
923 ));
924 }
925
926 throw new SystemException(sprintf(
927 'Unknown property `%s` for object `%s`', $name, get_called_class()
928 ));
929 }
930
941 public function __call($name, $arguments)
942 {
943 $first3 = substr($name, 0, 3);
944
945 // regular getter
946 if ($first3 == 'get')
947 {
948 $fieldName = self::sysMethodToFieldCase(substr($name, 3));
949
950 if ($fieldName == '')
951 {
952 $fieldName = StringHelper::strtoupper($arguments[0]);
953
954 // check runtime
955 if (array_key_exists($fieldName, $this->_runtimeValues))
956 {
957 return $this->sysGetRuntime($fieldName);
958 }
959
960 // check if custom method exists
961 $personalMethodName = $name.static::sysFieldToMethodCase($fieldName);
962
963 if (method_exists($this, $personalMethodName))
964 {
965 return $this->$personalMethodName(...array_slice($arguments, 1));
966 }
967
968 // hard field check
969 $this->entity->getField($fieldName);
970 }
971
972 // check if field exists
973 if ($this->entity->hasField($fieldName))
974 {
975 return $this->sysGetValue($fieldName);
976 }
977 }
978
979 // regular setter
980 if ($first3 == 'set')
981 {
982 $fieldName = self::sysMethodToFieldCase(substr($name, 3));
983 $value = $arguments[0];
984
985 if ($fieldName == '')
986 {
987 $fieldName = StringHelper::strtoupper($arguments[0]);
988 $value = $arguments[1];
989
990 // check for runtime field
991 if (array_key_exists($fieldName, $this->_runtimeValues))
992 {
993 throw new SystemException(sprintf(
994 'Setting value for runtime field `%s` in `%s` is not allowed, it is read-only field',
995 $fieldName, get_called_class()
996 ));
997 }
998
999 // check if custom method exists
1000 $personalMethodName = $name.static::sysFieldToMethodCase($fieldName);
1001
1002 if (method_exists($this, $personalMethodName))
1003 {
1004 return $this->$personalMethodName(...array_slice($arguments, 1));
1005 }
1006
1007 // hard field check
1008 $this->entity->getField($fieldName);
1009 }
1010
1011 // check if field exists
1012 if ($this->entity->hasField($fieldName))
1013 {
1014 $field = $this->entity->getField($fieldName);
1015
1016 if ($field instanceof IReadable && !($value instanceof SqlExpression))
1017 {
1018 $value = $field->cast($value);
1019 }
1020
1021 return $this->sysSetValue($fieldName, $value);
1022 }
1023 }
1024
1025 if ($first3 == 'has')
1026 {
1027 $fieldName = self::sysMethodToFieldCase(substr($name, 3));
1028
1029 if ($fieldName == '')
1030 {
1031 $fieldName = StringHelper::strtoupper($arguments[0]);
1032
1033 // check if custom method exists
1034 $personalMethodName = $name.static::sysFieldToMethodCase($fieldName);
1035
1036 if (method_exists($this, $personalMethodName))
1037 {
1038 return $this->$personalMethodName(...array_slice($arguments, 1));
1039 }
1040
1041 // runtime fields
1042 if (array_key_exists($fieldName, $this->_runtimeValues))
1043 {
1044 return true;
1045 }
1046
1047 // hard field check
1048 $this->entity->getField($fieldName);
1049 }
1050
1051 if ($this->entity->hasField($fieldName))
1052 {
1053 return $this->sysHasValue($fieldName);
1054 }
1055 }
1056
1057 $first4 = substr($name, 0, 4);
1058
1059 // filler
1060 if ($first4 == 'fill')
1061 {
1062 $fieldName = self::sysMethodToFieldCase(substr($name, 4));
1063
1064 // no custom/personal method for fill
1065
1066 // check if field exists
1067 if ($this->entity->hasField($fieldName))
1068 {
1069 return $this->fill([$fieldName]);
1070 }
1071 }
1072
1073 $first5 = substr($name, 0, 5);
1074
1075 // relation adder
1076 if ($first5 == 'addTo')
1077 {
1078 $fieldName = self::sysMethodToFieldCase(substr($name, 5));
1079 $value = $arguments[0];
1080
1081 if ($fieldName == '')
1082 {
1083 $fieldName = StringHelper::strtoupper($arguments[0]);
1084 $value = $arguments[1];
1085
1086 // check if custom method exists
1087 $personalMethodName = $name.static::sysFieldToMethodCase($fieldName);
1088
1089 if (method_exists($this, $personalMethodName))
1090 {
1091 return $this->$personalMethodName(...array_slice($arguments, 1));
1092 }
1093
1094 // hard field check
1095 $this->entity->getField($fieldName);
1096 }
1097
1098 if ($this->entity->hasField($fieldName))
1099 {
1100 $this->sysAddToCollection($fieldName, $value);
1101 return;
1102 }
1103 }
1104
1105 // unsetter
1106 if ($first5 == 'unset')
1107 {
1108 $fieldName = self::sysMethodToFieldCase(substr($name, 5));
1109
1110 if ($fieldName == '')
1111 {
1112 $fieldName = StringHelper::strtoupper($arguments[0]);
1113
1114 // check if custom method exists
1115 $personalMethodName = $name.static::sysFieldToMethodCase($fieldName);
1116
1117 if (method_exists($this, $personalMethodName))
1118 {
1119 return $this->$personalMethodName(...array_slice($arguments, 1));
1120 }
1121
1122 // hard field check
1123 $this->entity->getField($fieldName);
1124 }
1125
1126 if ($this->entity->hasField($fieldName))
1127 {
1128 return $this->sysUnset($fieldName);
1129 }
1130 }
1131
1132 // resetter
1133 if ($first5 == 'reset')
1134 {
1135 $fieldName = self::sysMethodToFieldCase(substr($name, 5));
1136
1137 if ($fieldName == '')
1138 {
1139 $fieldName = StringHelper::strtoupper($arguments[0]);
1140
1141 // check if custom method exists
1142 $personalMethodName = $name.static::sysFieldToMethodCase($fieldName);
1143
1144 if (method_exists($this, $personalMethodName))
1145 {
1146 return $this->$personalMethodName(...array_slice($arguments, 1));
1147 }
1148
1149 // hard field check
1150 $this->entity->getField($fieldName);
1151 }
1152
1153 if ($this->entity->hasField($fieldName))
1154 {
1155 $field = $this->entity->getField($fieldName);
1156
1157 if ($field instanceof OneToMany || $field instanceof ManyToMany)
1158 {
1159 return $this->sysResetRelation($fieldName);
1160 }
1161 else
1162 {
1163 return $this->sysReset($fieldName);
1164 }
1165 }
1166 }
1167
1168 $first9 = substr($name, 0, 9);
1169
1170 // relation mass remover
1171 if ($first9 == 'removeAll')
1172 {
1173 $fieldName = self::sysMethodToFieldCase(substr($name, 9));
1174
1175 if ($fieldName == '')
1176 {
1177 $fieldName = StringHelper::strtoupper($arguments[0]);
1178
1179 // check if custom method exists
1180 $personalMethodName = $name.static::sysFieldToMethodCase($fieldName);
1181
1182 if (method_exists($this, $personalMethodName))
1183 {
1184 return $this->$personalMethodName(...array_slice($arguments, 1));
1185 }
1186
1187 // hard field check
1188 $this->entity->getField($fieldName);
1189 }
1190
1191 if ($this->entity->hasField($fieldName))
1192 {
1193 $this->sysRemoveAllFromCollection($fieldName);
1194 return;
1195 }
1196 }
1197
1198 $first10 = substr($name, 0, 10);
1199
1200 // relation remover
1201 if ($first10 == 'removeFrom')
1202 {
1203 $fieldName = self::sysMethodToFieldCase(substr($name, 10));
1204 $value = $arguments[0];
1205
1206 if ($fieldName == '')
1207 {
1208 $fieldName = StringHelper::strtoupper($arguments[0]);
1209 $value = $arguments[1];
1210
1211 // check if custom method exists
1212 $personalMethodName = $name.static::sysFieldToMethodCase($fieldName);
1213
1214 if (method_exists($this, $personalMethodName))
1215 {
1216 return $this->$personalMethodName(...array_slice($arguments, 1));
1217 }
1218
1219 // hard field check
1220 $this->entity->getField($fieldName);
1221 }
1222
1223 if ($this->entity->hasField($fieldName))
1224 {
1225 $this->sysRemoveFromCollection($fieldName, $value);
1226 return;
1227 }
1228 }
1229
1230 $first12 = substr($name, 0, 12);
1231
1232 // actual value getter
1233 if ($first12 == 'remindActual')
1234 {
1235 $fieldName = self::sysMethodToFieldCase(substr($name, 12));
1236
1237 if ($fieldName == '')
1238 {
1239 $fieldName = StringHelper::strtoupper($arguments[0]);
1240
1241 // check if custom method exists
1242 $personalMethodName = $name.static::sysFieldToMethodCase($fieldName);
1243
1244 if (method_exists($this, $personalMethodName))
1245 {
1246 return $this->$personalMethodName(...array_slice($arguments, 1));
1247 }
1248
1249 // hard field check
1250 $this->entity->getField($fieldName);
1251 }
1252
1253 // check if field exists
1254 if ($this->entity->hasField($fieldName))
1255 {
1256 return $this->_actualValues[$fieldName] ?? null;
1257 }
1258 }
1259
1260 $first7 = substr($name, 0, 7);
1261
1262 // strict getter
1263 if ($first7 == 'require')
1264 {
1265 $fieldName = self::sysMethodToFieldCase(substr($name, 7));
1266
1267 if ($fieldName == '')
1268 {
1269 $fieldName = StringHelper::strtoupper($arguments[0]);
1270
1271 // check if custom method exists
1272 $personalMethodName = $name.static::sysFieldToMethodCase($fieldName);
1273
1274 if (method_exists($this, $personalMethodName))
1275 {
1276 return $this->$personalMethodName(...array_slice($arguments, 1));
1277 }
1278
1279 // hard field check
1280 $this->entity->getField($fieldName);
1281 }
1282
1283 // check if field exists
1284 if ($this->entity->hasField($fieldName))
1285 {
1286 return $this->sysGetValue($fieldName, true);
1287 }
1288 }
1289
1290 $first2 = substr($name, 0, 2);
1291 $last6 = substr($name, -6);
1292
1293 // actual value checker
1294 if ($first2 == 'is' && $last6 =='Filled')
1295 {
1296 $fieldName = self::sysMethodToFieldCase(substr($name, 2, -6));
1297
1298 if ($fieldName == '')
1299 {
1300 $fieldName = StringHelper::strtoupper($arguments[0]);
1301
1302 // check if custom method exists
1303 $personalMethodName = $first2.static::sysFieldToMethodCase($fieldName).$last6;
1304
1305 if (method_exists($this, $personalMethodName))
1306 {
1307 return $this->$personalMethodName(...array_slice($arguments, 1));
1308 }
1309
1310 // hard field check
1311 $this->entity->getField($fieldName);
1312 }
1313
1314 if ($this->entity->hasField($fieldName))
1315 {
1316 $field = $this->entity->getField($fieldName);
1317
1318 if ($field instanceof OneToMany || $field instanceof ManyToMany)
1319 {
1320 return array_key_exists($fieldName, $this->_actualValues) && $this->_actualValues[$fieldName]->sysIsFilled();
1321 }
1322 else
1323 {
1324 return $this->sysIsFilled($fieldName);
1325 }
1326 }
1327 }
1328
1329 $last7 = substr($name, -7);
1330
1331 // runtime value checker
1332 if ($first2 == 'is' && $last7 == 'Changed')
1333 {
1334 $fieldName = self::sysMethodToFieldCase(substr($name, 2, -7));
1335
1336 if ($fieldName == '')
1337 {
1338 $fieldName = StringHelper::strtoupper($arguments[0]);
1339
1340 // check if custom method exists
1341 $personalMethodName = $first2.static::sysFieldToMethodCase($fieldName).$last7;
1342
1343 if (method_exists($this, $personalMethodName))
1344 {
1345 return $this->$personalMethodName(...array_slice($arguments, 1));
1346 }
1347
1348 // hard field check
1349 $this->entity->getField($fieldName);
1350 }
1351
1352 if ($this->entity->hasField($fieldName))
1353 {
1354 $field = $this->entity->getField($fieldName);
1355
1356 if ($field instanceof OneToMany || $field instanceof ManyToMany)
1357 {
1358 return array_key_exists($fieldName, $this->_actualValues) && $this->_actualValues[$fieldName]->sysIsChanged();
1359 }
1360 else
1361 {
1362 return $this->sysIsChanged($fieldName);
1363 }
1364 }
1365 }
1366
1367 throw new SystemException(sprintf(
1368 'Unknown method `%s` for object `%s`', $name, get_called_class()
1369 ));
1370 }
1371
1377 public function sysGetEntity()
1378 {
1379 if ($this->_entity === null)
1380 {
1382 $dataClass = static::$dataClass;
1383 $this->_entity = $dataClass::getEntity();
1384 }
1385
1386 return $this->_entity;
1387 }
1388
1396 public function sysGetPrimary()
1397 {
1398 $primaryValues = [];
1399
1400 foreach ($this->entity->getPrimaryArray() as $primaryName)
1401 {
1402 $primaryValues[$primaryName] = $this->sysGetValue($primaryName);
1403 }
1404
1405 return $primaryValues;
1406 }
1407
1408 public function sysGetPrimaryAsString()
1409 {
1410 return static::sysSerializePrimary($this->sysGetPrimary(), $this->_entity);
1411 }
1412
1421 public function sysGetRuntime($name)
1422 {
1423 return $this->_runtimeValues[$name] ?? null;
1424 }
1425
1435 public function sysSetRuntime($name, $value)
1436 {
1437 $this->_runtimeValues[$name] = $value;
1438
1439 return $this;
1440 }
1441
1449 public function sysSetActual($fieldName, $value)
1450 {
1451 $fieldName = StringHelper::strtoupper($fieldName);
1452 $this->_actualValues[$fieldName] = $value;
1453
1454 // special condition for object values - it should be gotten and changed as current value
1455 // and actual value will be used for comparison
1456 if ($this->entity->getField($fieldName) instanceof ObjectField)
1457 {
1458 $this->_currentValues[$fieldName] = clone $value;
1459 }
1460 }
1461
1469 public function sysChangeState($state)
1470 {
1471 if ($this->_state !== $state)
1472 {
1473 /* not sure if we need check or changes here
1474 if ($state == State::RAW)
1475 {
1476 // actual should be empty
1477 }
1478 elseif ($state == State::ACTUAL)
1479 {
1480 // runtime values should be empty
1481 }
1482 elseif ($state == State::CHANGED)
1483 {
1484 // runtime values should not be empty
1485 }*/
1486
1487 $this->_state = $state;
1488 }
1489
1490 }
1491
1499 public function sysGetState()
1500 {
1501 return $this->_state;
1502 }
1503
1514 public function sysGetValue($fieldName, $require = false)
1515 {
1516 $fieldName = StringHelper::strtoupper($fieldName);
1517
1518 if (array_key_exists($fieldName, $this->_currentValues))
1519 {
1520 return $this->_currentValues[$fieldName];
1521 }
1522 else
1523 {
1524 if ($require && !array_key_exists($fieldName, $this->_actualValues))
1525 {
1526 throw new SystemException(sprintf(
1527 '%s value is required for further operations', $fieldName
1528 ));
1529 }
1530
1531 return $this->_actualValues[$fieldName] ?? null;
1532 }
1533 }
1534
1546 public function sysSetValue($fieldName, $value)
1547 {
1548 $fieldName = StringHelper::strtoupper($fieldName);
1549 $field = $this->entity->getField($fieldName);
1550
1551 // system validations
1552 if ($field instanceof ScalarField)
1553 {
1554 // restrict updating primary
1555 if ($this->_state !== State::RAW && in_array($field->getName(), $this->entity->getPrimaryArray()))
1556 {
1557 throw new SystemException(sprintf(
1558 'Setting value for Primary `%s` in `%s` is not allowed, it is read-only field',
1559 $field->getName(), get_called_class()
1560 ));
1561 }
1562 }
1563
1564 // no setter for expressions
1565 if ($field instanceof ExpressionField && !($field instanceof UserTypeField))
1566 {
1567 throw new SystemException(sprintf(
1568 'Setting value for ExpressionField `%s` in `%s` is not allowed, it is read-only field',
1569 $fieldName, get_called_class()
1570 ));
1571 }
1572
1573 if ($field instanceof Reference)
1574 {
1575 if (!empty($value))
1576 {
1577 // validate object class and skip null
1578 $remoteObjectClass = $field->getRefEntity()->getObjectClass();
1579
1580 if (!($value instanceof $remoteObjectClass))
1581 {
1582 throw new ArgumentException(sprintf(
1583 'Expected instance of `%s`, got `%s` instead',
1584 $remoteObjectClass,
1585 is_object($value) ? get_class($value) : gettype($value)
1586 ));
1587 }
1588 }
1589 }
1590
1591 // change only if value is different from actual
1592 // exclude UF fields for this check as long as UF file fields look into request to change value
1593 // (\Bitrix\Main\UserField\Types\FileType::onBeforeSave)
1594 // let UF manager handle all the values without optimization
1595 if (array_key_exists($fieldName, $this->_actualValues) && !($field instanceof UserTypeField))
1596 {
1597 if ($field instanceof IReadable)
1598 {
1599 if ($field->cast($value) === $this->_actualValues[$fieldName]
1600 // double check if value objects are different, but db values are the same
1601 || $field->convertValueToDb($field->modifyValueBeforeSave($value, []))
1602 === $field->convertValueToDb($field->modifyValueBeforeSave($this->_actualValues[$fieldName], []))
1603 )
1604 {
1605 // forget previous runtime change
1606 unset($this->_currentValues[$fieldName]);
1607 return $this;
1608 }
1609 }
1610 elseif ($field instanceof Reference)
1611 {
1613 if ($value->primary === $this->_actualValues[$fieldName]->primary)
1614 {
1615 // forget previous runtime change
1616 unset($this->_currentValues[$fieldName]);
1617 return $this;
1618 }
1619 }
1620 }
1621
1622 // set value
1623 if ($field instanceof ScalarField || $field instanceof UserTypeField)
1624 {
1625 $this->_currentValues[$fieldName] = $value;
1626 }
1627 elseif ($field instanceof Reference)
1628 {
1630 $this->_currentValues[$fieldName] = $value;
1631
1632 // set elemental fields if there are any
1633 $elementals = $field->getElementals();
1634
1635 if (!empty($elementals))
1636 {
1637 $elementalsChanged = false;
1638
1639 foreach ($elementals as $localFieldName => $remoteFieldName)
1640 {
1641 if ($this->entity->getField($localFieldName)->isPrimary())
1642 {
1643 // skip local primary in non-raw state
1644 if ($this->state !== State::RAW)
1645 {
1646 continue;
1647 }
1648
1649 // skip autocomplete
1650 if ($this->state === State::RAW && $this->entity->getField($localFieldName)->isAutocomplete())
1651 {
1652 continue;
1653 }
1654 }
1655
1656 $remoteField = $field->getRefEntity()->getField($remoteFieldName);
1657
1658 if (!empty($value) && !$value->sysHasValue($remoteField->getName())
1659 && $value->state === State::RAW && $remoteField->isPrimary() && $remoteField->isAutocomplete())
1660 {
1661 // get primary value after save
1662 $localObject = $this;
1663 $remoteObject = $value;
1664
1665 $remoteObject->sysAddOnPrimarySetListener(function () use (
1666 $localObject, $localFieldName, $remoteObject, $remoteFieldName
1667 ) {
1668 $localObject->sysSetValue($localFieldName, $remoteObject->get($remoteFieldName));
1669 });
1670 }
1671 else
1672 {
1673 $elementalValue = empty($value) ? null : $value->sysGetValue($remoteFieldName);
1674 $this->sysSetValue($localFieldName, $elementalValue);
1675 }
1676
1677 $elementalsChanged = true;
1678 }
1679
1680 if (!$elementalsChanged)
1681 {
1682 // object was not changed actually
1683 return $this;
1684 }
1685 }
1686 }
1687 else
1688 {
1689 throw new SystemException(sprintf(
1690 'Unknown field type `%s` in system setter of `%s`', get_class($field), get_called_class()
1691 ));
1692 }
1693
1694 if ($this->_state == State::ACTUAL)
1695 {
1696 $this->sysChangeState(State::CHANGED);
1697 }
1698
1699 // on primary gain event
1700 if ($field instanceof ScalarField && $field->isPrimary() && $this->sysHasPrimary())
1701 {
1702 $this->sysOnPrimarySet();
1703 }
1704
1705 return $this;
1706 }
1707
1715 public function sysHasValue($fieldName)
1716 {
1717 $fieldName = StringHelper::strtoupper($fieldName);
1718
1719 return $this->sysIsFilled($fieldName) || $this->sysIsChanged($fieldName);
1720 }
1721
1729 public function sysIsFilled($fieldName)
1730 {
1731 $fieldName = StringHelper::strtoupper($fieldName);
1732
1733 return array_key_exists($fieldName, $this->_actualValues);
1734 }
1735
1743 public function sysIsChanged($fieldName)
1744 {
1745 $fieldName = StringHelper::strtoupper($fieldName);
1746 $field = $this->entity->getField($fieldName);
1747
1748 if ($field instanceof ObjectField)
1749 {
1750 $currentValue = $this->_currentValues[$fieldName] ?? null;
1751 $actualValue = $this->_actualValues[$fieldName] ?? null;
1752
1753 return $field->encode($currentValue) !== $field->encode($actualValue);
1754 }
1755
1756 return array_key_exists($fieldName, $this->_currentValues);
1757 }
1758
1764 public function sysHasPrimary()
1765 {
1766 foreach ($this->primary as $primaryValue)
1767 {
1768 if ($primaryValue === null)
1769 {
1770 return false;
1771 }
1772 }
1773
1774 return true;
1775 }
1776
1780 public function sysOnPrimarySet()
1781 {
1782 // call subscribers
1783 if ($this->sysHasPrimary())
1784 {
1785 foreach ($this->_onPrimarySetListeners as $listener)
1786 {
1787 call_user_func($listener, $this);
1788 }
1789 }
1790 }
1791
1797 public function sysAddOnPrimarySetListener($callback)
1798 {
1799 // add to listeners
1800 $this->_onPrimarySetListeners[] = $callback;
1801 }
1802
1810 public function sysUnset($fieldName)
1811 {
1812 $fieldName = StringHelper::strtoupper($fieldName);
1813
1814 unset($this->_currentValues[$fieldName]);
1815 unset($this->_actualValues[$fieldName]);
1816
1817 return $this;
1818 }
1819
1827 public function sysReset($fieldName)
1828 {
1829 $fieldName = StringHelper::strtoupper($fieldName);
1830
1831 unset($this->_currentValues[$fieldName]);
1832
1833 return $this;
1834 }
1835
1843 public function sysResetRelation($fieldName)
1844 {
1845 $fieldName = StringHelper::strtoupper($fieldName);
1846
1847 if (isset($this->_actualValues[$fieldName]))
1848 {
1850 $collection = $this->_actualValues[$fieldName];
1851 $collection->sysResetChanges(true);
1852 }
1853
1854 return $this;
1855 }
1856
1864 public function sysRequirePrimary()
1865 {
1866 $primaryValues = [];
1867
1868 foreach ($this->entity->getPrimaryArray() as $primaryName)
1869 {
1870 try
1871 {
1872 $primaryValues[$primaryName] = $this->sysGetValue($primaryName, true);
1873 }
1874 catch (SystemException $e)
1875 {
1876 throw new SystemException(sprintf(
1877 'Primary `%s` value is required for further operations', $primaryName
1878 ));
1879 }
1880 }
1881
1882 return $primaryValues;
1883 }
1884
1894 public function sysGetIdleFields($fields = [])
1895 {
1896 $list = [];
1897
1898 if (empty($fields))
1899 {
1900 // all fields by default
1901 $fields = array_keys($this->entity->getFields());
1902 }
1903
1904 foreach ($fields as $fieldName)
1905 {
1906 $fieldName = StringHelper::strtoupper($fieldName);
1907
1908 if (!array_key_exists($fieldName, $this->_actualValues))
1909 {
1910 // regular field
1911 $list[] = $fieldName;
1912 }
1913 elseif ($this->_actualValues[$fieldName] instanceof Collection && !$this->_actualValues[$fieldName]->sysIsFilled())
1914 {
1915 // non-filled collection
1916 $list[] = $fieldName;
1917 }
1918 }
1919
1920 return $list;
1921 }
1922
1932 public function sysGetIdleFieldsByMask($mask = FieldTypeMask::ALL)
1933 {
1934 $list = [];
1935
1936 foreach ($this->entity->getFields() as $field)
1937 {
1938 $fieldMask = $field->getTypeMask();
1939
1940 if (
1941 !array_key_exists(StringHelper::strtoupper($field->getName()), $this->_actualValues)
1942 && ($mask & $fieldMask)
1943 )
1944 {
1945 $list[] = $field->getName();
1946 }
1947 }
1948
1949 return $list;
1950 }
1951
1960 public function sysSaveRelations(Result $result)
1961 {
1962 $saveCascade = true;
1963
1964 foreach ($this->_actualValues as $fieldName => $value)
1965 {
1966 $field = $this->entity->getField($fieldName);
1967
1968 if ($field instanceof Reference && !array_key_exists($fieldName, $this->_currentValues))
1969 {
1970 // if there is a new relation, then the old one is not into cascade anymore
1971 if ($saveCascade && !empty($value))
1972 {
1974 $value->save();
1975 }
1976 }
1977 elseif ($field instanceof OneToMany)
1978 {
1979 $collection = $value;
1980
1982 $objectsToSave = [];
1983
1985 $objectsToDelete = [];
1986
1987 if ($collection->sysIsChanged())
1988 {
1989 // save changed elements of collection
1990 foreach ($collection->sysGetChanges() as $change)
1991 {
1992 [$remoteObject, $changeType] = $change;
1993
1994 if ($changeType == Collection::OBJECT_ADDED)
1995 {
1996 $objectsToSave[] = $remoteObject;
1997 }
1998 elseif ($changeType == Collection::OBJECT_REMOVED)
1999 {
2000 if ($field->getCascadeDeletePolicy() == CascadePolicy::FOLLOW)
2001 {
2002 $objectsToDelete[] = $remoteObject;
2003 }
2004 else
2005 {
2006 // set null by default
2007 $objectsToSave[] = $remoteObject;
2008 }
2009 }
2010 }
2011 }
2012
2013 if ($saveCascade)
2014 {
2015 // everything should be saved, except deleted
2016 foreach ($collection->getAll() as $remoteObject)
2017 {
2018 if (!in_array($remoteObject, $objectsToDelete) && !in_array($remoteObject, $objectsToSave))
2019 {
2020 $objectsToSave[] = $remoteObject;
2021 }
2022 }
2023 }
2024
2025 // save remote objects
2026 foreach ($objectsToSave as $remoteObject)
2027 {
2028 $remoteResult = $remoteObject->save();
2029
2030 if (!$remoteResult->isSuccess())
2031 {
2032 $result->addErrors($remoteResult->getErrors());
2033 }
2034 }
2035
2036 // delete remote objects
2037 foreach ($objectsToDelete as $remoteObject)
2038 {
2039 $remoteResult = $remoteObject->delete();
2040
2041 if (!$remoteResult->isSuccess())
2042 {
2043 $result->addErrors($remoteResult->getErrors());
2044 }
2045 }
2046
2047 // forget collection changes
2048 if ($collection->sysIsChanged())
2049 {
2050 $collection->sysResetChanges();
2051 }
2052 }
2053 elseif ($field instanceof ManyToMany)
2054 {
2055 $collection = $value;
2056
2057 if ($value->sysIsChanged())
2058 {
2059 foreach ($collection->sysGetChanges() as $change)
2060 {
2061 [$remoteObject, $changeType] = $change;
2062
2063 // initialize mediator object
2064 $mediatorObjectClass = $field->getMediatorEntity()->getObjectClass();
2065 $localReferenceName = $field->getLocalReferenceName();
2066 $remoteReferenceName = $field->getRemoteReferenceName();
2067
2069 $mediatorObject = new $mediatorObjectClass;
2070 $mediatorObject->sysSetValue($localReferenceName, $this);
2071 $mediatorObject->sysSetValue($remoteReferenceName, $remoteObject);
2072
2073 // add or remove mediator depending on changeType
2074 if ($changeType == Collection::OBJECT_ADDED)
2075 {
2076 $mediatorObject->save();
2077 }
2078 elseif ($changeType == Collection::OBJECT_REMOVED)
2079 {
2080 // destroy directly through data class
2081 $mediatorDataClass = $field->getMediatorEntity()->getDataClass();
2082 $mediatorDataClass::delete($mediatorObject->primary);
2083 }
2084 }
2085
2086 // forget collection changes
2087 $collection->sysResetChanges();
2088 }
2089
2090 // should everything be saved?
2091 if ($saveCascade)
2092 {
2093 foreach ($collection->getAll() as $remoteObject)
2094 {
2095 $remoteResult = $remoteObject->save();
2096
2097 if (!$remoteResult->isSuccess())
2098 {
2099 $result->addErrors($remoteResult->getErrors());
2100 }
2101 }
2102 }
2103 }
2104
2105 // remove deleted objects from collections
2106 if ($value instanceof Collection)
2107 {
2108 $value->sysReviseDeletedObjects();
2109 }
2110 }
2111
2112 if ($saveCascade)
2113 {
2114 $this->sysSaveCurrentReferences();
2115 }
2116 }
2117
2118 public function sysSaveCurrentReferences()
2119 {
2120 foreach ($this->_currentValues as $fieldName => $value)
2121 {
2122 if ($this->entity->getField($fieldName) instanceof Reference && !empty($value))
2123 {
2124 $value->save();
2125 }
2126 }
2127 }
2128
2129 public function sysPostSave()
2130 {
2131 // clear current values
2132 foreach ($this->_currentValues as $k => $v)
2133 {
2134 $field = $this->entity->getField($k);
2135
2136 // handle references
2137 if ($v instanceof EntityObject)
2138 {
2139 // hold raw references
2140 if ($v->state === State::RAW)
2141 {
2142 continue;
2143 }
2144
2145 // move actual or changed
2146 if ($v->state === State::ACTUAL || $v->state === State::CHANGED)
2147 {
2148 $this->sysSetActual($k, $v);
2149 }
2150 }
2151 elseif ($field instanceof ScalarField || $field instanceof UserTypeField)
2152 {
2153 $v = $field->cast($v);
2154
2155 if ($v instanceof SqlExpression)
2156 {
2157 continue;
2158 }
2159
2160 $this->sysSetActual($k, $v);
2161 }
2162
2163 // clear values
2164 unset($this->_currentValues[$k]);
2165 }
2166
2167 // change state
2168 $this->sysChangeState(State::ACTUAL);
2169
2170 // return object field to current values
2171 foreach ($this->_actualValues as $fieldName => $actualValue)
2172 {
2173 if ($this->entity->getField($fieldName) instanceof ObjectField)
2174 {
2175 $this->_currentValues[$fieldName] = clone $actualValue;
2176 }
2177 }
2178 }
2179
2189 public function sysAddToCollection($fieldName, $remoteObject)
2190 {
2191 $fieldName = StringHelper::strtoupper($fieldName);
2192
2194 $field = $this->entity->getField($fieldName);
2195 $remoteObjectClass = $field->getRefEntity()->getObjectClass();
2196
2197 // validate object class
2198 if (!($remoteObject instanceof $remoteObjectClass))
2199 {
2200 throw new ArgumentException(sprintf(
2201 'Expected instance of `%s`, got `%s` instead', $remoteObjectClass, get_class($remoteObject)
2202 ));
2203 }
2204
2205 // initialize collection
2206 $collection = $this->sysGetValue($fieldName);
2207
2208 if (empty($collection))
2209 {
2210 $collection = $field->getRefEntity()->createCollection();
2211 $this->_actualValues[$fieldName] = $collection;
2212 }
2213
2214 if ($field instanceof OneToMany)
2215 {
2216 // set self to the object
2217 $remoteFieldName = $field->getRefField()->getName();
2218 $remoteObject->sysSetValue($remoteFieldName, $this);
2219
2220 // if we don't have primary right now, repeat setter later
2221 if ($this->state == State::RAW)
2222 {
2223 $localObject = $this;
2224
2225 $this->sysAddOnPrimarySetListener(function () use ($localObject, $remoteObject, $remoteFieldName) {
2226 $remoteObject->sysSetValue($remoteFieldName, $localObject);
2227 });
2228 }
2229 }
2230
2232 $collection->add($remoteObject);
2233
2234 // mark object as changed
2235 if ($this->_state == State::ACTUAL)
2236 {
2237 $this->sysChangeState(State::CHANGED);
2238 }
2239 }
2240
2250 public function sysRemoveFromCollection($fieldName, $remoteObject)
2251 {
2252 $fieldName = StringHelper::strtoupper($fieldName);
2253
2255 $field = $this->entity->getField($fieldName);
2256 $remoteObjectClass = $field->getRefEntity()->getObjectClass();
2257
2258 // validate object class
2259 if (!($remoteObject instanceof $remoteObjectClass))
2260 {
2261 throw new ArgumentException(sprintf(
2262 'Expected instance of `%s`, got `%s` instead', $remoteObjectClass, get_class($remoteObject)
2263 ));
2264 }
2265
2267 $collection = $this->sysGetValue($fieldName);
2268
2269 if (empty($collection))
2270 {
2271 $collection = $field->getRefEntity()->createCollection();
2272 $this->_actualValues[$fieldName] = $collection;
2273 }
2274
2275 // remove from collection
2276 $collection->remove($remoteObject);
2277
2278 if ($field instanceof OneToMany)
2279 {
2280 // remove self from the object
2281 if ($field->getCascadeDeletePolicy() == CascadePolicy::FOLLOW)
2282 {
2283 // nothing to do
2284 }
2285 else
2286 {
2287 // set null by default
2288 $remoteFieldName = $field->getRefField()->getName();
2289 $remoteObject->sysSetValue($remoteFieldName, null);
2290 }
2291
2292 }
2293
2294 // mark object as changed
2295 if ($this->_state == State::ACTUAL)
2296 {
2297 $this->sysChangeState(State::CHANGED);
2298 }
2299 }
2300
2309 public function sysRemoveAllFromCollection($fieldName)
2310 {
2311 $fieldName = StringHelper::strtoupper($fieldName);
2312 $collection = $this->sysFillRelationCollection($fieldName);
2313
2314 // remove one by one
2315 foreach ($collection as $remoteObject)
2316 {
2317 $this->sysRemoveFromCollection($fieldName, $remoteObject);
2318 }
2319 }
2320
2328 public function sysFillRelationCollection($field)
2329 {
2330 if ($field instanceof Relation)
2331 {
2332 $fieldName = $field->getName();
2333 }
2334 else
2335 {
2336 $fieldName = $field;
2337 $field = $this->entity->getField($fieldName);
2338 }
2339
2341 $collection = $this->sysGetValue($fieldName);
2342
2343 if (empty($collection))
2344 {
2345 $collection = $field->getRefEntity()->createCollection();
2346 $this->_actualValues[$fieldName] = $collection;
2347 }
2348
2349 if (!$collection->sysIsFilled())
2350 {
2351 // we need only primary here
2352 $remotePrimaryDefinitions = [];
2353
2354 foreach ($field->getRefEntity()->getPrimaryArray() as $primaryName)
2355 {
2356 $remotePrimaryDefinitions[] = $fieldName.'.'.$primaryName;
2357 }
2358
2359 $this->fill($remotePrimaryDefinitions);
2360
2361 // we can set fullness flag here
2362 $collection->sysSetFilled();
2363 }
2364
2365 return $collection;
2366 }
2367
2375 public static function sysMethodToFieldCase($methodName)
2376 {
2377 if (!isset(static::$_camelToSnakeCache[$methodName]))
2378 {
2379 static::$_camelToSnakeCache[$methodName] = StringHelper::strtoupper(
2380 StringHelper::camel2snake($methodName)
2381 );
2382 }
2383
2384 return static::$_camelToSnakeCache[$methodName];
2385 }
2386
2394 public static function sysFieldToMethodCase($fieldName)
2395 {
2396 if (!isset(static::$_snakeToCamelCache[$fieldName]))
2397 {
2398 static::$_snakeToCamelCache[$fieldName] = StringHelper::snake2camel($fieldName);
2399 }
2400
2401 return static::$_snakeToCamelCache[$fieldName];
2402 }
2403
2411 public static function sysSerializePrimary($primary, $entity)
2412 {
2413 if (count($entity->getPrimaryArray()) == 1)
2414 {
2415 return (string) current($primary);
2416 }
2417
2418 return (string) Json::encode(array_values($primary));
2419 }
2420
2430 public function offsetExists($offset): bool
2431 {
2432 return $this->sysHasValue($offset) && $this->sysGetValue($offset) !== null;
2433 }
2434
2444 #[\ReturnTypeWillChange]
2445 public function offsetGet($offset)
2446 {
2447 if ($this->offsetExists($offset))
2448 {
2449 // regular field
2450 return $this->get($offset);
2451 }
2452 elseif (array_key_exists($offset, $this->_runtimeValues))
2453 {
2454 // runtime field
2455 return $this->sysGetRuntime($offset);
2456 }
2457
2458 return $this->offsetExists($offset) ? $this->get($offset) : null;
2459 }
2460
2470 public function offsetSet($offset, $value): void
2471 {
2472 if (is_null($offset))
2473 {
2474 throw new ArgumentException('Field name should be set');
2475 }
2476 else
2477 {
2478 $this->set($offset, $value);
2479 }
2480 }
2481
2487 public function offsetUnset($offset): void
2488 {
2489 $this->unset($offset);
2490 }
2491}
if($_SERVER $defaultValue['REQUEST_METHOD']==="GET" &&!empty($RestoreDefaults) && $bizprocPerms==="W" &&check_bitrix_sessid())
Определения options.php:32
__construct($setDefaultValues=true)
Определения entityobject.php:120
cloneValues(array $values)
Определения entityobject.php:185
filterValuesByMask(array $values, int $fieldsMask, bool $invertedFilter=false)
Определения entityobject.php:194
collectValues($valuesType=Values::ALL, $fieldsMask=FieldTypeMask::ALL, $recursive=false)
Определения entityobject.php:220
const CHANGED
Определения state.php:20
const DELETED
Определения state.php:21
const RAW
Определения state.php:18
const ACTUAL
Определения state.php:19
const CURRENT
Определения values.php:19
const ACTUAL
Определения values.php:18
$data['IS_AVAILABLE']
Определения .description.php:13
</td ></tr ></table ></td ></tr >< tr >< td class="bx-popup-label bx-width30"><?=GetMessage("PAGE_NEW_TAGS")?> array( $site)
Определения file_new.php:804
$result
Определения get_property_values.php:14
$entity
$name
Определения menu_edit.php:35
$value
Определения Param.php:39
__call(string $name, array $args=[])
Определения GroupActionTrait.php:13
fill(array $source)
Определения ActiveRecordImplementation.php:515
offsetUnset($offset)
Определения arrayaccesswithreferences.php:90
offsetExists($offset)
Определения arrayaccesswithreferences.php:63
& offsetGet($offset)
Определения arrayaccesswithreferences.php:71
offsetSet($offset, $value)
Определения arrayaccesswithreferences.php:76
Определения collection.php:2
if( $daysToExpire >=0 &&$daysToExpire< 60 elseif)( $daysToExpire< 0)
Определения prolog_main_admin.php:393
</p ></td >< td valign=top style='border-top:none;border-left:none;border-bottom:solid windowtext 1.0pt;border-right:solid windowtext 1.0pt;padding:0cm 2.0pt 0cm 2.0pt;height:9.0pt'>< p class=Normal align=center style='margin:0cm;margin-bottom:.0001pt;text-align:center;line-height:normal'>< a name=ТекстовоеПоле54 ></a ><?=($taxRate > count( $arTaxList) > 0) ? $taxRate."%"
Определения waybill.php:936
$k
Определения template_pdf.php:567
$fields
Определения yandex_run.php:501