Bitrix-D7 23.9
 
Загрузка...
Поиск...
Не найдено
entity.php
1<?php
3
8
9Loc::loadMessages(__FILE__);
10
11abstract class Entity
12{
13 const PREFIX_OLD = 'OLD_';
14
15 public const EVENT_ON_BUILD_CACHED_FIELD_LIST = 'OnBuildCachedFieldList';
16
17 public const FIELDS_MAIN = 0x0001;
18 public const FIELDS_UF = 0x0002;
19 public const FIELDS_ALL = self::FIELDS_MAIN|self::FIELDS_UF;
20
21 private static $entity = [];
22
24 private $tablet = null;
26 private $tabletFields = [];
28 private $tabletUserFields = [];
30 private $result;
32 private $cache = [];
34 private $cacheModifyed = [];
35
36 private $fields = [];
37 private $fieldsCount = 0;
38 private $aliases = [];
39 private $fieldMask = [];
40 private $fetchCutMask;
41
42 public function __construct()
43 {
44 $this->initEntityTablet();
45 $this->initEntityCache();
46
47 $this->result = null;
48 $this->fetchCutMask = [];
49 }
50
56 public static function getEntity(): Entity
57 {
58 $className = get_called_class();
59 if (empty(self::$entity[$className]))
60 {
61 $entity = new static;
62 self::$entity[$className] = $entity;
63 }
64
65 return self::$entity[$className];
66 }
67
78 public static function getList(array $parameters): Entity
79 {
80 $entity = static::getEntity();
81 $parameters = $entity->prepareTabletQueryParameters($parameters);
82 $entity->result = $entity->getTablet()->getList($parameters);
83
84 return $entity;
85 }
86
97 public static function getRow(array $parameters): ?array
98 {
99 $parameters['limit'] = 1;
100 $result = static::getList($parameters);
101 $row = $result->fetch();
102
103 return (is_array($row) ? $row : null);
104 }
105
112 public function fetch(Main\Text\Converter $converter = null)
113 {
114 if ($this->result === null)
115 return false;
116 $row = $this->result->fetch($converter);
117 if (!$row)
118 {
119 $this->result = null;
120 $this->fetchCutMask = [];
121 return false;
122 }
123 if (empty($this->fields))
124 return $row;
125 if (!isset($row['ID']))
126 return $row;
127
128 $this->setEntityCacheItem((int)$row['ID'], $row, true);
129 if (!empty($this->fetchCutMask))
130 $row = array_diff_key($row, $this->fetchCutMask);
131 return $row;
132 }
133
139 public static function clearCache(): void
140 {
141 static::getEntity()->clearEntityCache();
142 }
143
150 public static function add(array $data): ORM\Data\AddResult
151 {
152 $result = new ORM\Data\AddResult();
153
154 $entity = static::getEntity();
155
156 static::normalize($data);
157
158 if (Event::existEventHandlers($entity, ORM\Data\DataManager::EVENT_ON_BEFORE_ADD))
159 {
160 $event = new Event(
161 $entity,
162 ORM\Data\DataManager::EVENT_ON_BEFORE_ADD,
163 $data
164 );
165 $event->send();
166
167 $event->mergeData($data);
168 if ($event->getErrors($result))
169 return $result;
170 }
171
172 static::prepareForAdd($result, null, $data);
173 if (!$result->isSuccess())
174 return $result;
175 unset($result);
176
177 if (Event::existEventHandlers($entity, ORM\Data\DataManager::EVENT_ON_ADD))
178 {
179 $event = new Event(
180 $entity,
181 ORM\Data\DataManager::EVENT_ON_ADD,
182 $data
183 );
184 $event->send();
185 unset($event);
186 }
187
188 $result = $entity->getTablet()->add($data['fields']);
189 $success = $result->isSuccess();
190 if ($success)
191 {
192 $data['fields'] = $result->getData();
193 if ($entity->fieldsCount > 0)
194 $entity->setEntityCacheItem((int)$result->getId(), $result->getData(), false);
195 }
196
197 if (Event::existEventHandlers($entity, ORM\Data\DataManager::EVENT_ON_AFTER_ADD))
198 {
199 $event = new Event(
200 $entity,
201 ORM\Data\DataManager::EVENT_ON_AFTER_ADD,
202 [
203 'id' => $result->getId(),
204 'fields' => $data['fields'],
205 'external_fields' => $data['external_fields'],
206 'actions' => $data['actions'],
207 'success' => $success
208 ]
209 );
210 $event->send();
211 unset($event);
212 }
213
214 if ($success && !empty($data['actions']))
215 static::runAddExternalActions($result->getId(), $data);
216
217 unset($success, $entity);
218
219 return $result;
220 }
221
229 public static function update($id, array $data): ORM\Data\UpdateResult
230 {
231 $result = new ORM\Data\UpdateResult();
232
233 $entity = static::getEntity();
234
235 static::normalize($data);
236
237 if (Event::existEventHandlers($entity, ORM\Data\DataManager::EVENT_ON_BEFORE_UPDATE))
238 {
239 $event = new Event(
240 $entity,
241 ORM\Data\DataManager::EVENT_ON_BEFORE_UPDATE,
242 [
243 'id' => $id,
244 'fields' => $data['fields'],
245 'external_fields' => $data['external_fields'],
246 'actions' => $data['actions']
247 ]
248 );
249 $event->send();
250
251 $event->mergeData($data);
252 if ($event->getErrors($result))
253 return $result;
254 }
255
256 static::prepareForUpdate($result, $id, $data);
257 if (!$result->isSuccess())
258 return $result;
259 unset($result);
260
261 if (Event::existEventHandlers($entity, ORM\Data\DataManager::EVENT_ON_UPDATE))
262 {
263 $event = new Event(
264 $entity,
265 ORM\Data\DataManager::EVENT_ON_UPDATE,
266 [
267 'id' => $id,
268 'fields' => $data['fields'],
269 'external_fields' => $data['external_fields'],
270 'actions' => $data['actions']
271 ]
272 );
273 $event->send();
274 unset($event);
275 }
276
277 $result = $entity->getTablet()->update($id, $data['fields']);
278 $success = $result->isSuccess();
279 if ($success)
280 {
281 $data['fields'] = $result->getData();
282 if ($entity->fieldsCount > 0)
283 $entity->modifyEntityCacheItem($id, $data['fields']);
284 }
285
286 if (Event::existEventHandlers($entity, ORM\Data\DataManager::EVENT_ON_AFTER_UPDATE))
287 {
288 $event = new Event(
289 $entity,
290 ORM\Data\DataManager::EVENT_ON_AFTER_UPDATE,
291 [
292 'id' => $id,
293 'fields' => $data['fields'],
294 'external_fields' => $data['external_fields'],
295 'actions' => $data['actions'],
296 'success' => $success
297 ]
298 );
299 $event->send();
300 unset($event);
301 }
302
303 if ($success && !empty($data['actions']))
304 static::runUpdateExternalActions($id, $data);
305
306 unset($success, $entity);
307
308 return $result;
309 }
310
317 public static function delete($id): ORM\Data\DeleteResult
318 {
319 $result = new ORM\Data\DeleteResult();
320
321 $entity = static::getEntity();
322
323 if (Event::existEventHandlers($entity, ORM\Data\DataManager::EVENT_ON_BEFORE_DELETE))
324 {
325 $event = new Event(
326 $entity,
327 ORM\Data\DataManager::EVENT_ON_BEFORE_DELETE,
328 ['id' => $id]
329 );
330 $event->send();
331
332 if ($event->getErrors($result))
333 return $result;
334 }
335
336 if (Event::existEventHandlers($entity, ORM\Data\DataManager::EVENT_ON_DELETE))
337 {
338 $event = new Event(
339 $entity,
340 ORM\Data\DataManager::EVENT_ON_DELETE,
341 ['id' => $id]
342 );
343 $event->send();
344 unset($event);
345 }
346
347 if ($entity->fieldsCount > 0 && !isset($entity->cache[$id]))
348 $entity->loadEntityCacheItem($id);
349
350 $result = $entity->getTablet()->delete($id);
351 $success = $result->isSuccess();
352 if ($success)
353 $entity->expireEntityCacheItem((int)$id);
354
355 if (Event::existEventHandlers($entity, ORM\Data\DataManager::EVENT_ON_AFTER_DELETE))
356 {
357 $event = new Event(
358 $entity,
359 ORM\Data\DataManager::EVENT_ON_AFTER_DELETE,
360 ['id' => $id, 'success' => $success]
361 );
362 $event->send();
363 unset($event);
364 }
365
366 if ($success)
367 static::runDeleteExternalActions($id);
368
369 unset($success, $entity);
370
371 return $result;
372 }
373
381 public static function setCacheItem($id, array $row): void
382 {
383 $id = (int)$id;
384 if ($id <= 0 || empty($row))
385 return;
386 static::getEntity()->setEntityCacheItem($id, $row, false);
387 }
388
396 public static function getCacheItem($id, bool $load = false): ?array
397 {
398 $id = (int)$id;
399 if ($id <= 0)
400 return null;
401 return static::getEntity()->getEntityCacheItem($id, $load);
402 }
403
411 public static function clearCacheItem($id): void
412 {
413 $id = (int)$id;
414 if ($id <= 0)
415 return;
416 static::getEntity()->clearEntityCacheItem($id);
417 }
418
424 public static function getTabletClassName(): string
425 {
426 return '';
427 }
428
435 public static function getTabletFieldNames(int $fields = self::FIELDS_MAIN): array
436 {
437 $result = [];
438 $entity = static::getEntity();
439 if ($fields & self::FIELDS_MAIN)
440 {
441 $result = array_keys($entity->tabletFields);
442 }
443 if ($fields & self::FIELDS_UF)
444 {
445 $list = array_keys($entity->tabletUserFields);
446 if (!empty($list))
447 {
448 $result = (empty($result)
449 ? $list
450 : array_merge($result, $list)
451 );
452 }
453 unset($list);
454 }
455
456 unset($entity);
457 return $result;
458 }
459
465 public static function getCachedFieldList(): array
466 {
467 $entity = static::getEntity();
468 return $entity->fields;
469 }
470
477 protected function getTablet(): ORM\Data\DataManager
478 {
479 if (!($this->tablet instanceof ORM\Data\DataManager))
480 throw new Main\ObjectNotFoundException(sprintf(
481 'Tablet not found in entity `%s`',
482 get_class($this)
483 ));
484 return $this->tablet;
485 }
486
495 protected static function prepareForAdd(ORM\Data\AddResult $result, $id, array &$data): void
496 {
497 $data = static::getEntity()->checkTabletWhiteList($data);
498 if (empty($data))
499 {
500 $result->addError(new ORM\EntityError(sprintf(
501 'Empty data for add in entity `%s`',
502 get_called_class()
503 )));
504 }
505 }
506
515 protected static function prepareForUpdate(ORM\Data\UpdateResult $result, $id, array &$data): void
516 {
517 $data = static::getEntity()->checkTabletWhiteList($data);
518 if (empty($data))
519 {
520 $result->addError(new ORM\EntityError(sprintf(
521 'Empty data for update in entity `%s`',
522 get_called_class()
523 )));
524 }
525 }
526
534 protected static function deleteNoDemands($id): ORM\Data\DeleteResult
535 {
536 $entity = static::getEntity();
537
538 if ($entity->fieldsCount > 0 && !isset($entity->cache[$id]))
539 $entity->loadEntityCacheItem($id);
540
541 $result = $entity->getTablet()->delete($id);
542 if ($result->isSuccess())
543 {
544 if ($entity->fieldsCount > 0)
545 $entity->expireEntityCacheItem((int)$id);
546 static::runDeleteExternalActions($id);
547 }
548
549 unset($entity);
550
551 return $result;
552 }
553
560 protected static function normalize(array &$data): void
561 {
562 $result = [
563 'fields' => [],
564 'external_fields' => [],
565 'actions' => []
566 ];
567
568 if (isset($data['fields']) && is_array($data['fields']))
569 {
570 $result['fields'] = $data['fields'];
571 if (isset($data['external_fields']) && is_array($data['external_fields']))
572 $result['external_fields'] = $data['external_fields'];
573 if (isset($data['actions']) && is_array($data['actions']))
574 $result['actions'] = $data['actions'];
575 }
576 else
577 {
578 $result['fields'] = $data;
579 }
580
581 $data = $result;
582 unset($result);
583 }
584
592 protected static function runAddExternalActions($id, array $data): void {}
593
601 protected static function runUpdateExternalActions($id, array $data): void {}
602
609 protected static function runDeleteExternalActions($id): void {}
610
616 protected static function getDefaultCachedFieldList(): array
617 {
618 return [];
619 }
620
627 private function initEntityTablet(): void
628 {
629 $tabletClassName = static::getTabletClassName();
630 $this->tablet = new $tabletClassName;
631 $this->tabletFields = [];
632 $this->tabletUserFields = [];
633
634 $entity = $this->tablet->getEntity();
635 $checkUseFields = $entity->getUfId() !== null;
636 $list = $entity->getFields();
637 foreach ($list as $field)
638 {
639 if ($field instanceof ORM\Fields\ScalarField)
640 {
641 $this->tabletFields[$field->getName()] = true;
642 }
643 elseif ($checkUseFields && $field instanceof ORM\Fields\UserTypeField)
644 {
645 $this->tabletUserFields[$field->getName()] = true;
646 }
647 }
648 unset($field, $list);
649 unset($checkUseFields);
650 unset($entity, $tabletClassName);
651 }
652
659 private function initEntityCache(): void
660 {
661 $this->clearEntityCache();
662
663 $this->aliases = [];
664 $this->fieldMask = [];
665 $fieldList = static::getDefaultCachedFieldList();
666 if (Event::existEventHandlers($this, self::EVENT_ON_BUILD_CACHED_FIELD_LIST))
667 {
668 $event = new Event(
669 $this,
670 self::EVENT_ON_BUILD_CACHED_FIELD_LIST
671 );
672 $event->send();
673
674 foreach($event->getResults() as $eventResult)
675 {
676 if ($eventResult->getType() == Main\EventResult::SUCCESS)
677 {
678 $addFields = $eventResult->getParameters();
679 if (!empty($addFields) && is_array($addFields))
680 {
681 foreach ($addFields as $alias => $field)
682 {
683 if (!isset($this->tabletFields[$field]))
684 {
685 continue;
686 }
687 $index = array_search($field, $fieldList);
688 if (is_int($alias))
689 {
690 if ($index === false || !is_int($index))
691 {
692 $fieldList[] = $field;
693 }
694 }
695 else
696 {
697 if ($index !== $alias)
698 {
699 $fieldList[$alias] = $field;
700 }
701 }
702 }
703 }
704 }
705 }
706 unset($eventResult, $event);
707 }
708
709 $this->fields = $fieldList;
710 unset($fieldList);
711 if (!empty($this->fields))
712 {
713 foreach ($this->fields as $alias => $field)
714 {
715 if (is_int($alias))
716 {
717 $this->fieldMask[$field] = true;
718 }
719 else
720 {
721 $this->fieldMask[$alias] = true;
722 $this->aliases[$alias] = $field;
723 }
724 }
725 unset($alias, $field);
726 }
727 $this->fieldsCount = count($this->fields);
728 }
729
736 private function clearEntityCache(): void
737 {
738 $this->cache = [];
739 $this->cacheModifyed = [];
740 }
741
749 private function prepareTabletQueryParameters(array $parameters): array
750 {
751 $this->fetchCutMask = [];
752
753 if (empty($this->fields))
754 return $parameters;
755 if (!isset($parameters['select']))
756 return $parameters;
757 if (in_array('*', $parameters['select']))
758 return $parameters;
759 if (isset($parameters['group']))
760 return $parameters;
761
762 $select = $parameters['select'];
763 foreach ($this->fields as $field)
764 {
765 $existField = false;
766 $index = array_search($field, $select);
767 if ($index !== false && is_int($index))
768 $existField = true;
769 if ($existField)
770 continue;
771
772 $parameters['select'][] = $field;
773 $this->fetchCutMask[$field] = true;
774 }
775 unset($index, $existField, $field);
776
777 return $parameters;
778 }
779
787 private function replaceFieldToAlias(array &$row): void
788 {
789 if (empty($this->aliases))
790 return;
791
792 foreach ($this->aliases as $alias => $field)
793 {
794 $row[$alias] = $row[$field];
795 unset($row[$field]);
796 }
797 unset($alias, $field);
798 }
799
807 private function checkTabletWhiteList(array $fields): array
808 {
809 $baseFields = array_intersect_key($fields, $this->tabletFields);
810 if (!empty($this->tabletUserFields))
811 {
812 $userFields = array_intersect_key($fields, $this->tabletUserFields);
813 if (!empty($userFields))
814 {
815 $baseFields = $baseFields + $userFields;
816 }
817 unset($userFields);
818 }
819 return $baseFields;
820 }
821
822 static public function getCallbackRestEvent(): array
823 {
824 return [Main\Rest\Event::class, 'processItemEvent'];
825 }
826
827 /* entity cache item tools */
828
836 private function loadEntityCacheItem($id): void
837 {
838 if (isset($this->cache[$id]))
839 return;
840 if (empty($this->fields))
841 return;
842
843 $iterator = $this->getTablet()->getList([
844 'select' => array_values($this->fields),
845 'filter' => ['=ID' => $id]
846 ]);
847 $row = $iterator->fetch();
848 unset($iterator);
849 if (!empty($row))
850 $this->setEntityCacheItem($id, $row, true);
851 unset($row);
852 }
853
862 private function getEntityCacheItem($id, bool $load = false): array
863 {
864 $result = [];
865 if (!isset($this->cache[$id]) && $load && !empty($this->fields))
866 $this->loadEntityCacheItem($id);
867 if (isset($this->cache[$id]))
868 $result = $this->cache[$id];
869
870 return $result;
871 }
872
882 private function setEntityCacheItem($id, array $row, bool $replaceAliases = false): void
883 {
884 if (empty($this->fieldMask))
885 return;
886 if (isset($this->cache[$id]))
887 return;
888
889 if ($replaceAliases)
890 $this->replaceFieldToAlias($row);
891 $data = array_intersect_key($row, $this->fieldMask);
892 if (!empty($data) && count($data) == $this->fieldsCount)
893 $this->cache[$id] = $data;
894 unset($data);
895 }
896
905 private function modifyEntityCacheItem($id, array $row): void
906 {
907 if (empty($this->fieldMask))
908 return;
909
910 $data = array_intersect_key($row, $this->fieldMask);
911 if (!empty($data))
912 {
913 if (!isset($this->cache[$id]))
914 $this->loadEntityCacheItem($id);
915 if (isset($this->cache[$id]))
916 {
917 $this->expireEntityCacheItem($id, true);
918 $this->cache[$id] = array_merge($this->cache[$id], $data);
919 }
920 }
921 unset($data);
922 }
923
932 private function expireEntityCacheItem($id, bool $copy = false): void
933 {
934 if (empty($this->fields))
935 return;
936
937 if (!isset($this->cache[$id]))
938 return;
939 if (isset($this->cacheModifyed[$id]))
940 return;
941
942 $oldData = [];
943 foreach (array_keys($this->fieldMask) as $field)
944 $oldData[self::PREFIX_OLD.$field] = $this->cache[$id][$field];
945 unset($field);
946 if ($copy)
947 $this->cache[$id] = array_merge($oldData, $this->cache[$id]);
948 else
949 $this->cache[$id] = $oldData;
950 unset($oldData);
951
952 $this->cacheModifyed[$id] = true;
953 }
954
962 private function clearEntityCacheItem($id): void
963 {
964 if (isset($this->cache[$id]))
965 unset($this->cache[$id]);
966 if (isset($this->cacheModifyed[$id]))
967 unset($this->cacheModifyed[$id]);
968 }
969
970 public static function clearSettings(): void {}
971
972 /* entity cache item tools end */
973
974 protected static function prepareFloatValue($value): ?float
975 {
976 if ($value === null)
977 {
978 return null;
979 }
980
981 $result = null;
982 if (is_string($value))
983 {
984 if ($value !== '' && is_numeric($value))
985 {
986 $value = (float)$value;
987 if (is_finite($value))
988 {
989 $result = $value;
990 }
991 }
992 }
993 else
994 {
995 if (is_int($value))
996 {
997 $value = (float)$value;
998 }
999 if (
1000 is_float($value) && is_finite($value)
1001 )
1002 {
1003 $result = $value;
1004 }
1005 }
1006
1007 return $result;
1008 }
1009
1010 protected static function prepareIntValue($value): ?int
1011 {
1012 if ($value === null)
1013 {
1014 return null;
1015 }
1016
1017 $result = null;
1018 if (is_string($value))
1019 {
1020 if ($value !== '' && is_numeric($value))
1021 {
1022 $result = (int)$value;
1023 }
1024 }
1025 elseif (is_int($value))
1026 {
1027 $result = $value;
1028 }
1029
1030 return $result;
1031 }
1032
1033 protected static function prepareStringValue($value): ?string
1034 {
1035 if (is_string($value))
1036 {
1037 return trim($value) ?: null;
1038 }
1039
1040 return null;
1041 }
1042}
static prepareFloatValue($value)
Definition entity.php:974
static prepareForUpdate(ORM\Data\UpdateResult $result, $id, array &$data)
Definition entity.php:515
static getDefaultCachedFieldList()
Definition entity.php:616
static update($id, array $data)
Definition entity.php:229
static prepareForAdd(ORM\Data\AddResult $result, $id, array &$data)
Definition entity.php:495
static prepareIntValue($value)
Definition entity.php:1010
static getRow(array $parameters)
Definition entity.php:97
static normalize(array &$data)
Definition entity.php:560
static setCacheItem($id, array $row)
Definition entity.php:381
static runDeleteExternalActions($id)
Definition entity.php:609
static getList(array $parameters)
Definition entity.php:78
static deleteNoDemands($id)
Definition entity.php:534
static add(array $data)
Definition entity.php:150
fetch(Main\Text\Converter $converter=null)
Definition entity.php:112
static prepareStringValue($value)
Definition entity.php:1033
static runUpdateExternalActions($id, array $data)
Definition entity.php:601
static runAddExternalActions($id, array $data)
Definition entity.php:592
static getTabletFieldNames(int $fields=self::FIELDS_MAIN)
Definition entity.php:435
static getCacheItem($id, bool $load=false)
Definition entity.php:396
const EVENT_ON_BUILD_CACHED_FIELD_LIST
Definition entity.php:15
static loadMessages($file)
Definition loc.php:64