spasmie

Untitled

Aug 19th, 2025
49
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 32.10 KB | None | 0 0
  1. <?php
  2.  
  3. declare(strict_types=1);
  4.  
  5. namespace App\Service;
  6.  
  7. use App\Entity\BaseEntity;
  8. use App\Entity\CustomFieldValue;
  9. use App\Entity\Interface\BaseEntityInterface;
  10. use App\Entity\Interface\BatchByTemplateInterface;
  11. use App\Entity\Interface\CustomFieldEntityInterface;
  12. use App\Entity\Model\JoinFilter;
  13. use App\Entity\User;
  14. use App\Enum\Entity\BatchType;
  15. use App\Enum\Entity\BoolType;
  16. use App\Enum\Entity\VatTypeEnum;
  17. use App\Enum\Response\ResponseFormatType;
  18. use App\Event\Entity\EntityBatchTemplateUpdateEvent;
  19. use App\Event\Entity\PostBulkEvent;
  20. use App\Exception\Entity\EntityValidationException;
  21. use App\Exception\Exception;
  22. use App\Exception\ObjectNotFoundException;
  23. use App\Exception\ObjectNotImplementException;
  24. use App\Helper\SerializerExtractorHelper;
  25. use App\Model\BaseModel;
  26. use App\Model\IdArrayModel;
  27. use App\Model\Interface\BaseModelInterface;
  28. use App\Model\Interface\CustomFieldModelInterface;
  29. use App\Model\Interface\PaginatedModelInterface;
  30. use App\Model\Interface\PatchableModelInterface;
  31. use App\Model\Pagination\PaginatedBatchModel;
  32. use App\Model\Pagination\PaginatedModel;
  33. use App\Model\Pagination\PaginationModel;
  34. use App\Service\Api\BatchApiService;
  35. use App\Service\Api\Interface\ApiServiceInterface;
  36. use App\Service\Api\Interface\DocumentExcelServiceInterface;
  37. use App\Service\Api\Interface\ExportExcelServiceInterface;
  38. use App\Service\Locator\EntityServiceLocator;
  39. use App\Service\Utility\EntityHelperService;
  40. use App\Service\Utility\ExcelExportService;
  41. use App\Service\Utility\MappingModelService;
  42. use App\Trait\Service\GetServiceTrait;
  43. use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
  44. use Symfony\Component\DependencyInjection\ServiceLocator;
  45. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  46. use Symfony\Component\Serializer\SerializerInterface;
  47. use Symfony\Component\Uid\UuidV7;
  48. use Symfony\Component\Validator\Validator\ValidatorInterface;
  49. use Symfony\Contracts\Service\Attribute\Required;
  50.  
  51. class ApiService implements ApiServiceInterface
  52. {
  53.     use GetServiceTrait;
  54.  
  55.     public const int DEFAULT_LIMIT = 30;
  56.  
  57.     protected EventDispatcherInterface $dispatcher;
  58.     protected ExcelExportService $excelExportService;
  59.     protected ValidatorInterface $validator;
  60.     /**
  61.      * @var ServiceLocator<EntityService>
  62.      */
  63.     protected ServiceLocator $locator;
  64.     protected ValidationService $validationService;
  65.     protected BatchApiService $batchApiService;
  66.     protected MappingModelService $mappingModelService;
  67.     protected EntityHelperService $entityHelperService;
  68.     protected ParameterBagInterface $params;
  69.     protected SerializerInterface $serializer;
  70.  
  71.     #[Required]
  72.    public function setDependencies(
  73.         BatchApiService $batchApiService,
  74.         EventDispatcherInterface $dispatcher,
  75.         EntityHelperService $entityHelperService,
  76.         ExcelExportService $excelExportService,
  77.         MappingModelService $mappingModelService,
  78.         ParameterBagInterface $params,
  79.         EntityServiceLocator $entityServiceLocator,
  80.         ValidationService $validationService,
  81.         ValidatorInterface $validator,
  82.         SerializerInterface $serializer,
  83.     ): void {
  84.         $this->dispatcher = $dispatcher;
  85.         $this->validator = $validator;
  86.         $this->locator = $entityServiceLocator->getLocator();
  87.         $this->batchApiService = $batchApiService;
  88.         $this->validationService = $validationService;
  89.         $this->mappingModelService = $mappingModelService;
  90.         $this->entityHelperService = $entityHelperService;
  91.         $this->params = $params;
  92.         $this->serializer = $serializer;
  93.         $this->excelExportService = $excelExportService;
  94.     }
  95.  
  96.     #[\Override]
  97.    public function map(
  98.         BaseEntityInterface $entity,
  99.         array $context,
  100.         int $tree = 0,
  101.         array $propNameLimit = [],
  102.         ?int $mappingModelHelperTree = null,
  103.         bool $showArchived = false,
  104.     ): BaseModelInterface {
  105.         $model = $this->mapProperties(
  106.             $entity,
  107.             $context,
  108.             $tree,
  109.             $propNameLimit,
  110.             $mappingModelHelperTree,
  111.             $showArchived,
  112.         );
  113.  
  114.         $this->mapCustomFields([$entity], [$model], $context);
  115.  
  116.         return $this->postMap($model, $entity, $context);
  117.     }
  118.  
  119.     #[\Override]
  120.    public function preMap(
  121.         BaseEntityInterface $entity,
  122.         array $context,
  123.     ): array {
  124.         return [];
  125.     }
  126.  
  127.     #[\Override]
  128.    public function postMap(
  129.         BaseModelInterface $model,
  130.         BaseEntityInterface $entity,
  131.         array $context,
  132.     ): BaseModelInterface {
  133.         return $model;
  134.     }
  135.  
  136.     /**
  137.      * @param BaseEntityInterface[] $entities
  138.      *
  139.      * @return BaseEntityInterface[]
  140.      */
  141.     #[\Override]
  142.    public function preMapAll(
  143.         array $entities,
  144.         array $context,
  145.     ): array {
  146.         return $entities;
  147.     }
  148.  
  149.     /**
  150.      * @param BaseEntityInterface[] $entities
  151.      *
  152.      * @return BaseModelInterface[]
  153.      */
  154.     #[\Override]
  155.    public function mapAll(
  156.         array $entities,
  157.         array $context = [],
  158.     ): array {
  159.         if (method_exists($this, 'preMapAll')) {
  160.             $entities = $this->preMapAll($entities, $context);
  161.         }
  162.  
  163.         $list = [];
  164.  
  165.         foreach ($entities as $k => $entity) {
  166.             $list[$k] = $this->mapProperties($entity, $context);
  167.         }
  168.  
  169.         $this->mapCustomFields($entities, $list, $context);
  170.  
  171.         foreach ($entities as $k => $entity) {
  172.             $this->postMap($list[$k], $entity, $context);
  173.         }
  174.  
  175.         return $list;
  176.     }
  177.  
  178.     #[\Override]
  179.    public function getAll(
  180.         ?User $currentUser = null,
  181.         ?int $limit = null,
  182.         int $offset = 0,
  183.         array $filter = [],
  184.         string $search = '',
  185.         array $context = [],
  186.         array $orderBy = [],
  187.         bool $criteriaAlreadyPrepared = false,
  188.         bool $isAccurateSearch = false,
  189.     ): PaginatedModel {
  190.         $service = $this->getService();
  191.         $account = $currentUser?->getCurrentAccount();
  192.  
  193.         if (!$criteriaAlreadyPrepared) {
  194.             $criteria = $service->prepareCriteria(
  195.                 criteria: $filter,
  196.                 account: $account,
  197.                 search: $search,
  198.                 exclude: ['account'],
  199.                 isAccurateSearch: $isAccurateSearch,
  200.             );
  201.             $criteriaAlreadyPrepared = true;
  202.         } else {
  203.             $criteria = $filter;
  204.         }
  205.  
  206.         $count = $service->getCount($criteria, $criteriaAlreadyPrepared);
  207.  
  208.         $model = new PaginatedModel();
  209.  
  210.         $limit = $this->prepareLimit($limit);
  211.         $offset = $this->prepareOffset($offset);
  212.  
  213.         $results = $count ? $service->getAll(
  214.             criteria: $criteria,
  215.             limit: $limit,
  216.             offset: $offset,
  217.             orderBy: $orderBy,
  218.             criteriaAlreadyPrepared: $criteriaAlreadyPrepared,
  219.         ) : [];
  220.  
  221.         $model->setItems(
  222.             $this->mapAll(
  223.                 $results,
  224.                 $context,
  225.             ),
  226.         )->setTotals(
  227.             $service->getTotals($account, $criteria),
  228.         )->setPagination(
  229.             $this->mapPagination($limit, $offset, $count),
  230.         );
  231.  
  232.         if (in_array('pagination:hash', $context, true)) {
  233.             $lastDate = null;
  234.             foreach ($results as $result) {
  235.                 if (method_exists($result, 'getModifiedAt')) {
  236.                     if (null === $lastDate) {
  237.                         $lastDate = $result->getModifiedAt();
  238.                     }
  239.                     if ($lastDate < $result->getModifiedAt()) {
  240.                         $lastDate = $result->getModifiedAt();
  241.                     }
  242.                 }
  243.             }
  244.             if (null === $lastDate) {
  245.                 $lastDate = new \DateTimeImmutable();
  246.             }
  247.             $model->setHash(hash('xxh3', $lastDate->format(\DateTimeInterface::ATOM)));
  248.         }
  249.  
  250.         return $model;
  251.     }
  252.  
  253.     #[\Override]
  254.    public function getAllStreamed(
  255.         ?User $currentUser = null,
  256.         ?int $limit = null,
  257.         int $offset = 0,
  258.         array $filter = [],
  259.         string $search = '',
  260.         array $orderBy = [],
  261.         bool $criteriaAlreadyPrepared = false,
  262.         bool $isAccurateSearch = false,
  263.         ?ResponseFormatType $format = null,
  264.     ): void {
  265.         $service = $this->getService();
  266.         $account = $currentUser?->getCurrentAccount();
  267.  
  268.         if (!$criteriaAlreadyPrepared) {
  269.             $criteria = $service->prepareCriteria(
  270.                 criteria: $filter,
  271.                 account: $account,
  272.                 search: $search,
  273.                 exclude: ['account'],
  274.                 isAccurateSearch: $isAccurateSearch,
  275.             );
  276.             $criteriaAlreadyPrepared = true;
  277.         } else {
  278.             $criteria = $filter;
  279.         }
  280.  
  281.         $callbackFunction = fn () => $service->getAllIterable(
  282.             criteria: $criteria,
  283.             limit: $limit,
  284.             offset: $offset,
  285.             orderBy: $orderBy,
  286.             criteriaAlreadyPrepared: $criteriaAlreadyPrepared,
  287.         );
  288.  
  289.         if (ResponseFormatType::Excel === $format) {
  290.             if ($this instanceof ExportExcelServiceInterface) {
  291.                 $this->getExcel($callbackFunction, $service, $criteria);
  292.             } else {
  293.                 throw new ObjectNotImplementException($this::class, ExportExcelServiceInterface::class);
  294.             }
  295.         }
  296.     }
  297.  
  298.     public function getOneStreamed(
  299.         ?User $currentUser = null,
  300.         ?string $id = null,
  301.         array $criteria = [],
  302.         bool $criteriaAlreadyPrepared = false,
  303.         ?ResponseFormatType $format = null,
  304.     ): void {
  305.         $service = $this->getService();
  306.         $entity = $this->entityHelperService->getName($service);
  307.  
  308.         if ($this->entityHelperService->isPropertyExist($entity, 'account')) {
  309.             $account = $currentUser?->getCurrentAccount();
  310.         } else {
  311.             $account = null;
  312.         }
  313.  
  314.         if (!$criteriaAlreadyPrepared) {
  315.             $criteria = $service->prepareCriteria(
  316.                 criteria: $criteria,
  317.                 id: $id,
  318.                 account: $account,
  319.                 exclude: ['account'],
  320.             );
  321.             $criteriaAlreadyPrepared = true;
  322.         }
  323.  
  324.         $entity = $service->get(
  325.             id: $id,
  326.             account: $account,
  327.             criteria: $criteria,
  328.             criteriaAlreadyPrepared: $criteriaAlreadyPrepared,
  329.         );
  330.  
  331.         if (ResponseFormatType::Excel === $format) {
  332.             if ($service instanceof DocumentExcelServiceInterface) {
  333.                 $service->getDocument($entity);
  334.             } else {
  335.                 throw new ObjectNotImplementException($service::class, DocumentExcelServiceInterface::class);
  336.             }
  337.         }
  338.     }
  339.  
  340.     #[\Override]
  341.    public function get(
  342.         ?User $currentUser = null,
  343.         ?string $id = null,
  344.         array $criteria = [],
  345.         array $context = [],
  346.         array $orderBy = [],
  347.         bool $criteriaAlreadyPrepared = false,
  348.         array $limits = [],
  349.     ): BaseModelInterface {
  350.         $service = $this->getService();
  351.         $entity = $this->entityHelperService->getName($service);
  352.  
  353.         if ($this->entityHelperService->isPropertyExist($entity, 'account')) {
  354.             $account = $currentUser?->getCurrentAccount();
  355.         } else {
  356.             $account = null;
  357.         }
  358.  
  359.         if (!$criteriaAlreadyPrepared) {
  360.             $criteria = $service->prepareCriteria(
  361.                 criteria: $criteria,
  362.                 id: $id,
  363.                 account: $account,
  364.                 exclude: ['account'],
  365.             );
  366.             $criteriaAlreadyPrepared = true;
  367.         }
  368.  
  369.         return $this->map(
  370.             $service->get(
  371.                 id: $id,
  372.                 account: $account,
  373.                 criteria: $criteria,
  374.                 orderBy: $orderBy,
  375.                 criteriaAlreadyPrepared: $criteriaAlreadyPrepared,
  376.             ),
  377.             $context,
  378.         );
  379.     }
  380.  
  381.     #[\Override]
  382.    public function create(
  383.         BaseModelInterface $model,
  384.         User $currentUser,
  385.         array $context,
  386.     ): BaseModelInterface|BaseEntityInterface {
  387.  
  388.         throw new Exception('Create method not found in '.$this::class);
  389.     }
  390.  
  391.     #[\Override]
  392.    public function update(
  393.         BaseModelInterface $model,
  394.         User $currentUser,
  395.         array $context,
  396.     ): BaseModelInterface|BaseEntityInterface {
  397.  
  398.         throw new Exception('Update method not found in '.$this::class);
  399.     }
  400.  
  401.     public function patch(
  402.         BaseModelInterface $model,
  403.         User $currentUser,
  404.         array $context,
  405.     ): BaseEntityInterface|BaseModelInterface {
  406.         $model = $this->resetModel(
  407.             $model,
  408.             $currentUser,
  409.         );
  410.  
  411.         return $this->update(
  412.             model: $model,
  413.             currentUser: $currentUser,
  414.             context: $context,
  415.         );
  416.     }
  417.  
  418.     #[\Override]
  419.    public function delete(
  420.         int|string|null $id,
  421.         ?User $currentUser = null,
  422.         array $criteria = [],
  423.     ): void {
  424.         $service = $this->getService();
  425.  
  426.         $account = $currentUser?->getCurrentAccount();
  427.         $service->delete(
  428.             entity: $service->get(
  429.                 id: $id,
  430.                 account: $account,
  431.                 criteria: $criteria,
  432.             ),
  433.             account: $account,
  434.         );
  435.     }
  436.  
  437.     public function batchCreate(
  438.         PaginatedModelInterface $inputModel,
  439.         User $currentUser,
  440.         array $context = [],
  441.         array $inputArgs = [],
  442.     ): BaseModelInterface {
  443.         return $this->batchApiService->create(
  444.             model: $inputModel,
  445.             currentUser: $currentUser,
  446.             context: $context,
  447.             inputArgs: $inputArgs,
  448.             entityName: $this->entityHelperService->getName($this),
  449.             type: BatchType::Create,
  450.         );
  451.     }
  452.  
  453.     public function batchUpdate(
  454.         PaginatedModelInterface $inputModel,
  455.         User $currentUser,
  456.         array $inputArgs = [],
  457.         array $context = [],
  458.     ): BaseModelInterface {
  459.         return $this->batchApiService->create(
  460.             model: $inputModel,
  461.             currentUser: $currentUser,
  462.             context: $context,
  463.             inputArgs: $inputArgs,
  464.             entityName: $this->entityHelperService->getName($this),
  465.             type: BatchType::Update,
  466.         );
  467.     }
  468.  
  469.     public function batchDelete(
  470.         IdArrayModel $inputModel,
  471.         User $currentUser,
  472.         array $filter = [],
  473.         array $criteria = [],
  474.     ): ?BaseModelInterface {
  475.         $isSync = false;
  476.         $service = $this->getService();
  477.         $account = $currentUser->getCurrentAccount();
  478.         $ids = [];
  479.  
  480.         if ($filter) {
  481.             $criteria = $service->prepareCriteria(
  482.                 criteria: array_merge($filter, $criteria),
  483.                 account: $account,
  484.                 exclude: ['account'],
  485.             );
  486.             $ids = $this->getService()->getIds($criteria, true, $account);
  487.         } elseif ($inputModel->getIds()) {
  488.             foreach ($inputModel->getIds() as $id) {
  489.                 if (null !== $id) {
  490.                     $ids[] = $id;
  491.                 }
  492.             }
  493.  
  494.             $max = (int) $this->params->get('app.max_sync_delete_limit');
  495.  
  496.             if ($max && count($ids) < $max) {
  497.                 $isSync = true;
  498.             }
  499.         }
  500.  
  501.         if ($isSync) {
  502.             foreach ($ids as $id) {
  503.                 try {
  504.                     $service->delete($service->get(id: $id, account: $account), $account);
  505.                 } catch (ObjectNotFoundException) {
  506.                 }
  507.             }
  508.  
  509.             return null;
  510.         }
  511.  
  512.         return $this->batchApiService->create(
  513.             new PaginatedModel(),
  514.             $currentUser,
  515.             [],
  516.             $ids,
  517.             $this->entityHelperService->getName($this),
  518.             BatchType::Delete,
  519.         );
  520.     }
  521.  
  522.     public function bulk(
  523.         PaginatedModelInterface $inputModel,
  524.         User $currentUser,
  525.         array $criteria = [],
  526.         array $contextList = [],
  527.         array $contextCreate = [],
  528.         array $contextUpdate = [],
  529.         array $inputArgs = [],
  530.         string $entity = '',
  531.     ): BaseModelInterface {
  532.         $items = $inputModel->getItems();
  533.  
  534.         foreach ($items as $item) {
  535.             $itemId = $item->getId();
  536.             $this->validationService->validate($item, $itemId ? $contextUpdate : $contextCreate);
  537.         }
  538.  
  539.         $ids = [];
  540.         foreach ($items as $item) {
  541.             if (null !== $item->getId()) {
  542.                 $ids[] = $item->getId();
  543.             }
  544.         }
  545.  
  546.         $criteria['account'] = $currentUser->getCurrentAccount();
  547.         $service = $this->getService();
  548.         $entities = $service->getAll($criteria);
  549.         $allEntities = [];
  550.  
  551.         /** @var BaseEntity $entityItem */
  552.         foreach ($entities as $entityItem) {
  553.             if (in_array($entityItem->getIdStr(), $ids, true)) {
  554.                 $allEntities[$entityItem->getIdStr()] = $entityItem;
  555.             } else {
  556.                 $this->delete($entityItem->getId()?->toRfc4122());
  557.             }
  558.         }
  559.  
  560.         foreach ($items as $item) {
  561.             $itemId = $item->getId();
  562.  
  563.             if ($itemId) {
  564.                 if (array_key_exists($itemId, $allEntities)) {
  565.                     $this->update(
  566.                         model: $item,
  567.                         currentUser: $currentUser,
  568.                         context: $contextUpdate,
  569.                     );
  570.                 }
  571.             } else {
  572.                 $this->create(
  573.                     model: $item,
  574.                     currentUser: $currentUser,
  575.                     context: $contextCreate,
  576.                 );
  577.             }
  578.         }
  579.  
  580.         $this->dispatcher->dispatch(new PostBulkEvent($items));
  581.  
  582.         return $this->getAll(
  583.             currentUser: $currentUser,
  584.             limit: 300,
  585.             filter: $criteria,
  586.             context: $contextList,
  587.         );
  588.     }
  589.  
  590.     protected function mapProperties(
  591.         BaseEntityInterface $entity,
  592.         array $context,
  593.         int $tree = 0,
  594.         array $propNameLimit = [],
  595.         ?int $mappingModelHelperTree = null,
  596.         bool $showArchived = false,
  597.     ): BaseModelInterface {
  598.         $preMapResult = $this->preMap($entity, $context);
  599.  
  600.         $mappingModelService = $this->mappingModelService->setTreeLimit($mappingModelHelperTree);
  601.  
  602.         return $mappingModelService->mapProperties(
  603.             entity: $entity,
  604.             mapGroups: $context,
  605.             treeLimit: $tree,
  606.             propNameLimit: $propNameLimit,
  607.             customModelClass: $preMapResult['modelClassName'] ?? null,
  608.             showArchived: $showArchived,
  609.         );
  610.     }
  611.  
  612.     protected function mapCustomFields(
  613.         array $entities,
  614.         array $models,
  615.         array $context,
  616.     ): void {
  617.         if (!(
  618.             isset($this->customFieldValueService, $this->customFieldValueApiService)
  619.             && count($entities)
  620.             && count($models)
  621.             && current($entities) instanceof CustomFieldEntityInterface
  622.             && current($models) instanceof CustomFieldModelInterface
  623.         )) {
  624.             return;
  625.         }
  626.  
  627.         $allCustomFields = [];
  628.  
  629.         $ids = array_filter(array_map(
  630.             static fn (BaseEntityInterface $entity) => $entity->getId(),
  631.             $entities,
  632.         ));
  633.  
  634.         if (!count($ids)) {
  635.             return;
  636.         }
  637.  
  638.         $this->customFieldValueService->with('customField');
  639.  
  640.         $criteria = [
  641.             'entityId' => $ids,
  642.             'deleted' => false,
  643.             'customField.deleted' => new JoinFilter('customField', ['deleted' => 'false']),
  644.             'filter' => true,
  645.         ];
  646.         $items = $this->customFieldValueService->getAll(criteria: $criteria, criteriaAlreadyPrepared: true);
  647.  
  648.         /* @var CustomFieldValue $item */
  649.         foreach ($items as $item) {
  650.             if (!$item->getEntityId()) {
  651.                 continue;
  652.             }
  653.             $allCustomFields[$item->getEntityId()->toRfc4122()][] = $item;
  654.         }
  655.  
  656.         foreach ($models as $model) {
  657.             $modelId = $model->getId();
  658.             $customFields = $allCustomFields[$modelId] ?? [];
  659.             $model->setCustomFields($this->customFieldValueApiService->mapAll($customFields, $context));
  660.         }
  661.     }
  662.  
  663.     protected function getUuid(
  664.         ?string $id,
  665.     ): ?UuidV7 {
  666.         return $id ? UuidV7::fromRfc4122($id) : null;
  667.     }
  668.  
  669.     protected function entityArray(
  670.         object $model,
  671.         object $entity,
  672.         array $context = [],
  673.     ): ?array {
  674.         $entityArray = [];
  675.         $entityReflection = new \ReflectionClass($entity);
  676.         $entityProperties = $entityReflection->getProperties();
  677.         $serializerExtractor = SerializerExtractorHelper::getSerializerExtractor();
  678.         $properties = $serializerExtractor->getProperties($model::class, ['serializer_groups' => $context]);
  679.  
  680.         foreach ($properties as $property) {
  681.             $getMethodName = $this->entityHelperService->getGetter($property);
  682.             if (method_exists($model, $getMethodName)) {
  683.                 $entityPropertyTypeFull = '';
  684.                 $entityPropertyType = 'NULL';
  685.                 foreach ($entityProperties as $entityProperty) {
  686.                     if ($property === $entityProperty->getName()) {
  687.                         $entityPropertyTypeFull = str_replace('?', '', (string) $entityProperty->getType());
  688.                         $entityPropertyType = preg_replace('/^(\w+\\\)*/', '', $entityPropertyTypeFull);
  689.                         break;
  690.                     }
  691.                 }
  692.  
  693.                 $value = $model->{$getMethodName}();
  694.  
  695.                 if (null !== $value) {
  696.                     if (in_array(gettype($value), [
  697.                         'NULL',
  698.                         'integer',
  699.                         'double',
  700.                         'string',
  701.                         'boolean',
  702.                         'bool',
  703.                         'array',
  704.                     ], true)) {
  705.                         $entityArray[$property] = $value;
  706.                     } elseif ('VatTypeEnum' === $entityPropertyType) {
  707.                         $entityArray[$property] = null === $value->getId() ? VatTypeEnum::Vat_none :
  708.                             VatTypeEnum::tryFrom((int) $value->getId());
  709.                     } elseif ('DateTimeImmutable' === $entityPropertyType) {
  710.                         $entityArray[$property] = $value;
  711.                     } elseif ('UuidV7' === $entityPropertyType) {
  712.                         $entityArray[$property] = $value;
  713.                     } elseif (enum_exists($entityPropertyTypeFull)) {
  714.                         $reflectionEnum = new \ReflectionEnum($entityPropertyTypeFull);
  715.                         if ($reflectionEnum->hasCase((string) $value)) {
  716.                             $entityArray[$property] = $reflectionEnum->getCase((string) $value)->getValue();
  717.                         }
  718.                     } elseif ('BoolTypeInterface' === $entityPropertyType) {
  719.                         $entityArray[$property] = BoolType::true->name === $value->name;
  720.                     } elseif (is_object($value) && $model === $entity) {
  721.                         $entityArray[$property] = $this->entityArray($value, $value, $context);
  722.                     }
  723.                 }
  724.             }
  725.         }
  726.  
  727.         return $entityArray;
  728.     }
  729.  
  730.     public function modelAsArray(
  731.         ?object $model,
  732.         array $context = [],
  733.         bool $isFlat = false,
  734.         int $tree = 0,
  735.     ): ?array {
  736.         if (!$model || ($isFlat && $tree > 0)) {
  737.             return null;
  738.         }
  739.         ++$tree;
  740.         $modelArray = [];
  741.         $serializerExtractor = SerializerExtractorHelper::getSerializerExtractor();
  742.         $properties = $serializerExtractor->getProperties($model::class, ['serializer_groups' => $context]);
  743.  
  744.         foreach ($properties as $property) {
  745.             $getMethodName = $this->entityHelperService->getGetter($property);
  746.             if (method_exists($model, $getMethodName)) {
  747.                 $value = $model->{$getMethodName}();
  748.                 if (is_array($value)) {
  749.                     foreach ($value as $key => $object) {
  750.                         if (is_object($object)) {
  751.                             $result = $this->modelAsArray($object, $context, $isFlat, $tree);
  752.                             if ($result) {
  753.                                 $modelArray[$property][$key] = $result;
  754.                             }
  755.                         }
  756.                     }
  757.                 } elseif ($value instanceof \DateTimeImmutable) {
  758.                     $modelArray[$property] = $value->format('Y-m-d H:i:s');
  759.                 } elseif ($value instanceof \BackedEnum) {
  760.                     $modelArray[$property] = $value->value;
  761.                 } elseif (is_object($value)) {
  762.                     $result = $this->modelAsArray($value, $context, $isFlat, $tree);
  763.                     if ($result) {
  764.                         $modelArray[$property] = $result;
  765.                     }
  766.                 } elseif (in_array(gettype($value), [
  767.                     'NULL',
  768.                     'integer',
  769.                     'double',
  770.                     'string',
  771.                     'boolean',
  772.                     'bool',
  773.                 ], true)) {
  774.                     $modelArray[$property] = $value;
  775.                 }
  776.             }
  777.         }
  778.  
  779.         return $modelArray;
  780.     }
  781.  
  782.     protected function entityToArray(
  783.         BaseModel $model,
  784.         array $context = [],
  785.     ): array {
  786.         $entityClass = str_replace('\Model\\', '\Entity\\', $model::class);
  787.         $entityClass = ucfirst(str_replace('Model', '', $entityClass));
  788.         try {
  789.             $entity = new $entityClass();
  790.         } catch (\Exception $e) {
  791.             throw new \LogicException(sprintf('В рамках преобразования в массив не удалось создать копию объекта "%s" (%s).', $entityClass, $e->getMessage()), $e->getCode(), $e);
  792.         }
  793.  
  794.         return $this->entityArray(
  795.             model: $model,
  796.             entity: $entity,
  797.             context: $context,
  798.         );
  799.     }
  800.  
  801.     protected function prepareLimit(
  802.         ?int $limit,
  803.     ): ?int {
  804.         if (null === $limit || $limit < 0) {
  805.             $limit = null;
  806.         } elseif (0 === $limit) {
  807.             $limit = self::DEFAULT_LIMIT;
  808.         }
  809.  
  810.         /*if ($limit > static::$maxLimit) {
  811.             $limit = static::$maxLimit;
  812.         }*/
  813.  
  814.         return $limit;
  815.     }
  816.  
  817.     protected function prepareOffset(
  818.         ?int $offset,
  819.     ): int {
  820.         if (null === $offset || $offset < 0) {
  821.             $offset = 0;
  822.         }
  823.  
  824.         return $offset;
  825.     }
  826.  
  827.     protected function mapPagination(
  828.         ?int $limit,
  829.         ?int $offset,
  830.         int $total,
  831.     ): PaginationModel {
  832.         return (new PaginationModel())
  833.             ->setTotal($total)
  834.             ->setLimit($limit)
  835.             ->setOffset($offset);
  836.     }
  837.  
  838.     public function batchByTemplate(
  839.         BaseModel $model,
  840.         array $filter,
  841.         User $currentUser,
  842.         array $context,
  843.     ): ?PaginatedBatchModel {
  844.         if (!$model instanceof BatchByTemplateInterface) {
  845.             throw new \RuntimeException(sprintf('Class %s not implements %s', $model::class, BatchByTemplateInterface::class));
  846.         }
  847.  
  848.         $models = [];
  849.         $items = $model->getItemsId() ?: (isset($filter['itemsId']) ? explode(',', $filter['itemsId']) : []);
  850.         unset($filter['itemsId']);
  851.         $ids = array_unique(array_filter($items));
  852.  
  853.         if (!$ids && !$filter) {
  854.             $exception = new EntityValidationException();
  855.             $exception->addCustomError('Empty items id');
  856.  
  857.             throw $exception;
  858.         }
  859.  
  860.         $template = $model->getTemplate();
  861.  
  862.         unset($template['id']);
  863.  
  864.         if (!$template) {
  865.             $exception = new EntityValidationException();
  866.             $exception->addCustomError('Not found fields on update');
  867.  
  868.             throw $exception;
  869.         }
  870.  
  871.         if ($ids) {
  872.             /** @var BaseModel $modelTemplate */
  873.             $modelTemplate = $this->serializer->deserialize(
  874.                 data: json_encode($template, \JSON_UNESCAPED_UNICODE),
  875.                 type: $model::class,
  876.                 format: 'json',
  877.                 context: $context,
  878.             );
  879.  
  880.             if ($modelTemplate instanceof PatchableModelInterface) {
  881.                 $modelTemplate->setRequestFields(array_keys($template));
  882.             }
  883.  
  884.             $max = (int) $this->params->get('app.max_sync_template_limit');
  885.             $isSync = ($max && count($ids) <= $max);
  886.  
  887.             foreach ($ids as $id) {
  888.                 $modelEntity = (clone $modelTemplate)->setId($id);
  889.  
  890.                 if ($isSync) {
  891.                     $this->patch(
  892.                         model: $modelEntity,
  893.                         currentUser: $currentUser,
  894.                         context: $context,
  895.                     );
  896.  
  897.                     continue;
  898.                 }
  899.                 $models[] = $modelEntity;
  900.             }
  901.  
  902.             if ($models) {
  903.                 $this->batchUpdate(
  904.                     inputModel: (new PaginatedModel())->setItems($models),
  905.                     currentUser: $currentUser,
  906.                     context: $context,
  907.                 );
  908.                 unset($models);
  909.             }
  910.         } elseif ($filter) {
  911.             $service = $this->getService();
  912.             $entityClass = $this->entityHelperService->getEntity($service);
  913.             $countFilter = array_merge($filter, ['account' => $currentUser->getCurrentAccount()]);
  914.             $count = $service->getCount($countFilter);
  915.             $partition = $this->params->get('app.template.batch.partition');
  916.  
  917.             if (0 === $count) {
  918.                 return null;
  919.             }
  920.             for ($i = 0; $i <= $count; $i += $partition) {
  921.                 $this->dispatcher->dispatch(
  922.                     new EntityBatchTemplateUpdateEvent(
  923.                         template: $template,
  924.                         serviceClass: $this->entityHelperService->getService($this),
  925.                         entityName: $this->entityHelperService->getName($entityClass),
  926.                         context: $context,
  927.                         userId: $currentUser->getIdStr(),
  928.                         employeeId: $currentUser->getCurrentEmployee()->getIdStr(),
  929.                         accountId: $currentUser->getCurrentAccount()->getIdStr(),
  930.                         filter: $filter,
  931.                         limit: $partition,
  932.                         offset: $i,
  933.                     ),
  934.                 );
  935.             }
  936.         }
  937.  
  938.         return null;
  939.     }
  940.  
  941.     protected function resetModel(
  942.         BaseModelInterface $model,
  943.         ?User $currentUser,
  944.     ): BaseModelInterface {
  945.         if (!$model->getId()) {
  946.             return $model;
  947.         }
  948.  
  949.         $modelName = $this->entityHelperService->getModel($this);
  950.  
  951.         $entityModel = $this->get(
  952.             currentUser: $currentUser,
  953.             id: $model->getId(),
  954.             context: $modelName::CONTEXT_VIEW,
  955.         );
  956.  
  957.         return $this->fillEntityModel(
  958.             model: $model,
  959.             entityModel: $entityModel,
  960.         );
  961.     }
  962.  
  963.     protected function fillEntityModel(
  964.         BaseModelInterface $model,
  965.         BaseModelInterface $entityModel,
  966.     ): BaseModelInterface {
  967.         if (!$model instanceof PatchableModelInterface) {
  968.             return $model;
  969.         }
  970.  
  971.         foreach ($model->getRequestFields() as $field) {
  972.             $getter = $this->entityHelperService->getGetter($field);
  973.             $setter = $this->entityHelperService->getSetter($field);
  974.  
  975.             if (method_exists($entityModel, $setter) && method_exists($model, $getter)) {
  976.                 $value = $model->{$getter}();
  977.                 $entityModel->{$setter}($value);
  978.             }
  979.         }
  980.  
  981.         return $entityModel;
  982.     }
  983. }
  984.  
Advertisement
Add Comment
Please, Sign In to add comment