vendor/w-vision/data-definitions/src/DataDefinitionsBundle/Importer/Importer.php line 179

Open in your IDE?
  1. <?php
  2. /**
  3.  * Data Definitions.
  4.  *
  5.  * LICENSE
  6.  *
  7.  * This source file is subject to the GNU General Public License version 3 (GPLv3)
  8.  * For the full copyright and license information, please view the LICENSE.md and gpl-3.0.txt
  9.  * files that are distributed with this source code.
  10.  *
  11.  * @copyright  Copyright (c) 2016-2019 w-vision AG (https://www.w-vision.ch)
  12.  * @license    https://github.com/w-vision/DataDefinitions/blob/master/gpl-3.0.txt GNU General Public License version 3 (GPLv3)
  13.  */
  14. declare(strict_types=1);
  15. namespace Wvision\Bundle\DataDefinitionsBundle\Importer;
  16. use CoreShop\Component\Registry\ServiceRegistryInterface;
  17. use Countable;
  18. use InvalidArgumentException;
  19. use Pimcore;
  20. use Pimcore\File;
  21. use Pimcore\Mail;
  22. use Pimcore\Model\DataObject\ClassDefinition;
  23. use Pimcore\Model\DataObject\Concrete;
  24. use Pimcore\Model\DataObject\Service;
  25. use Pimcore\Model\Document;
  26. use Pimcore\Model\Factory;
  27. use Pimcore\Model\Version;
  28. use Psr\Log\LoggerAwareInterface;
  29. use Psr\Log\LoggerInterface;
  30. use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
  31. use Throwable;
  32. use Wvision\Bundle\DataDefinitionsBundle\Event\EventDispatcherInterface;
  33. use Wvision\Bundle\DataDefinitionsBundle\Exception\DoNotSetException;
  34. use Wvision\Bundle\DataDefinitionsBundle\Exception\UnexpectedValueException;
  35. use Wvision\Bundle\DataDefinitionsBundle\Filter\FilterInterface;
  36. use Wvision\Bundle\DataDefinitionsBundle\Loader\LoaderInterface;
  37. use Wvision\Bundle\DataDefinitionsBundle\Model\DataSetAwareInterface;
  38. use Wvision\Bundle\DataDefinitionsBundle\Model\ImportDefinitionInterface;
  39. use Wvision\Bundle\DataDefinitionsBundle\Model\ImportMapping;
  40. use Wvision\Bundle\DataDefinitionsBundle\Model\ParamsAwareInterface;
  41. use Wvision\Bundle\DataDefinitionsBundle\Provider\ImportDataSet;
  42. use Wvision\Bundle\DataDefinitionsBundle\Provider\ImportDataSetInterface;
  43. use Wvision\Bundle\DataDefinitionsBundle\Provider\ImportProviderInterface;
  44. use Wvision\Bundle\DataDefinitionsBundle\Persister\PersisterInterface;
  45. use Wvision\Bundle\DataDefinitionsBundle\Runner\RunnerInterface;
  46. use Wvision\Bundle\DataDefinitionsBundle\Runner\SaveRunnerInterface;
  47. use Wvision\Bundle\DataDefinitionsBundle\Runner\SetterRunnerInterface;
  48. use Wvision\Bundle\DataDefinitionsBundle\Setter\SetterInterface;
  49. final class Importer implements ImporterInterface
  50. {
  51.     private ServiceRegistryInterface $providerRegistry;
  52.     private ServiceRegistryInterface $filterRegistry;
  53.     private ServiceRegistryInterface $runnerRegistry;
  54.     private ServiceRegistryInterface $interpreterRegistry;
  55.     private ServiceRegistryInterface $setterRegistry;
  56.     private ServiceRegistryInterface $cleanerRegistry;
  57.     private ServiceRegistryInterface $loaderRegistry;
  58.     private ServiceRegistryInterface $persisterRegistry;
  59.     private EventDispatcherInterface $eventDispatcher;
  60.     private LoggerInterface $logger;
  61.     private Factory $modelFactory;
  62.     private ExpressionLanguage $expressionLanguage;
  63.     private bool $shouldStop false;
  64.     public function __construct(
  65.         ServiceRegistryInterface $providerRegistry,
  66.         ServiceRegistryInterface $filterRegistry,
  67.         ServiceRegistryInterface $runnerRegistry,
  68.         ServiceRegistryInterface $interpreterRegistry,
  69.         ServiceRegistryInterface $setterRegistry,
  70.         ServiceRegistryInterface $cleanerRegistry,
  71.         ServiceRegistryInterface $loaderRegistry,
  72.         ServiceRegistryInterface $persisterRegistry,
  73.         EventDispatcherInterface $eventDispatcher,
  74.         LoggerInterface $logger,
  75.         Factory $modelFactory,
  76.         ExpressionLanguage $expressionLanguage
  77.     ) {
  78.         $this->providerRegistry $providerRegistry;
  79.         $this->filterRegistry $filterRegistry;
  80.         $this->runnerRegistry $runnerRegistry;
  81.         $this->interpreterRegistry $interpreterRegistry;
  82.         $this->setterRegistry $setterRegistry;
  83.         $this->cleanerRegistry $cleanerRegistry;
  84.         $this->loaderRegistry $loaderRegistry;
  85.         $this->persisterRegistry $persisterRegistry;
  86.         $this->eventDispatcher $eventDispatcher;
  87.         $this->logger $logger;
  88.         $this->modelFactory $modelFactory;
  89.         $this->expressionLanguage $expressionLanguage;
  90.     }
  91.     public function doImport(ImportDefinitionInterface $definition$params): array
  92.     {
  93.         $filter null;
  94.         if ($definition->getCreateVersion()) {
  95.             Version::enable();
  96.         } else {
  97.             Version::disable();
  98.         }
  99.         $filterType $definition->getFilter();
  100.         if ($filterType) {
  101.             /**
  102.              * @var FilterInterface $filter
  103.              */
  104.             $filter $this->filterRegistry->get($filterType);
  105.         }
  106.         /** @var ImportDataSetInterface|array $data */
  107.         $data $this->getData($definition$params);
  108.         if ((\is_countable($data) || $data instanceof Countable) && \count($data) > 0) {
  109.             $this->eventDispatcher->dispatch($definition'data_definitions.import.total'\count($data), $params);
  110.         }
  111.         [$objectIds$exceptions] = $this->runImport($definition$params$filter$data);
  112.         $cleanerType $definition->getCleaner();
  113.         if ($cleanerType) {
  114.             $cleaner $this->cleanerRegistry->get($cleanerType);
  115.             $this->logger->info(sprintf('Running Cleaner "%s"'$cleanerType));
  116.             $this->eventDispatcher->dispatch($definition'data_definitions.import.status',
  117.                 sprintf('Running Cleaner "%s"'$cleanerType));
  118.             if ($cleaner instanceof DataSetAwareInterface) {
  119.                 $cleaner->setDataSet($data);
  120.             }
  121.             if ($cleaner instanceof ParamsAwareInterface) {
  122.                 $cleaner->setParams($params);
  123.             }
  124.             if ($cleaner instanceof LoggerAwareInterface) {
  125.                 $cleaner->setLogger($this->logger);
  126.             }
  127.             $cleaner->cleanup($definition$objectIds);
  128.             $this->logger->info(sprintf('Finished Cleaner "%s"'$cleanerType));
  129.             $this->eventDispatcher->dispatch($definition'data_definitions.import.status',
  130.                 sprintf('Finished Cleaner "%s"'$cleanerType));
  131.         }
  132.         if (count($exceptions) > 0) {
  133.             $this->processFailedImport($definition$params$objectIds$exceptions);
  134.         } else {
  135.             $this->processSuccessfullImport($definition$params$objectIds$exceptions);
  136.         }
  137.         $this->eventDispatcher->dispatch($definition'data_definitions.import.finished'''$params);
  138.         return $objectIds;
  139.     }
  140.     public function processSuccessfullImport(ImportDefinitionInterface $definition$params$objectIds$exceptions)
  141.     {
  142.         $this->sendDocument($definitionDocument::getById($definition->getSuccessNotificationDocument()), $objectIds$exceptions);
  143.         $this->eventDispatcher->dispatch($definition'data_definitions.import.success'$params);
  144.     }
  145.     public function processFailedImport(ImportDefinitionInterface $definition$params$objectIds$exceptions)
  146.     {
  147.         $this->sendDocument(
  148.             $definition,
  149.             Document::getById($definition->getFailureNotificationDocument()),
  150.             $objectIds,
  151.             $exceptions
  152.         );
  153.         $this->eventDispatcher->dispatch($definition'data_definitions.import.failure'$params);
  154.     }
  155.     public function stop() : void
  156.     {
  157.         $this->shouldStop true;
  158.     }
  159.     private function sendDocument(
  160.         ImportDefinitionInterface $definition,
  161.         ?Document $document,
  162.         array $objectIds,
  163.         array $exceptions
  164.     )
  165.     {
  166.         if ($document instanceof Document) {
  167.             $params = [
  168.                 'exceptions' => $exceptions,
  169.                 'objectIds' => $objectIds,
  170.                 'className' => $definition->getClass(),
  171.                 'countObjects' => count($objectIds),
  172.                 'countExceptions' => count($exceptions),
  173.                 'name' => $definition->getName(),
  174.                 'provider' => $definition->getProvider(),
  175.             ];
  176.             if ($document instanceof Document\Email) {
  177.                 $mail = new Mail();
  178.                 $mail->setDocument($document);
  179.                 $mail->setParams($params);
  180.                 $mail->send();
  181.             }
  182.         }
  183.     }
  184.     private function getData(ImportDefinitionInterface $definition, array $params)
  185.     {
  186.         /** @var ImportProviderInterface $provider */
  187.         $provider $this->providerRegistry->get($definition->getProvider());
  188.         return $provider->getData($definition->getConfiguration(), $definition$params);
  189.     }
  190.     private function runImport(
  191.         ImportDefinitionInterface $definition,
  192.         array $params,
  193.         FilterInterface $filter null,
  194.         ImportDataSetInterface $dataSet null
  195.     ): array
  196.     {
  197.         if (null === $dataSet) {
  198.             $dataSet = new ImportDataSet(new \EmptyIterator());
  199.         }
  200.         $count 0;
  201.         $countToClean 1000;
  202.         $objectIds = [];
  203.         $exceptions = [];
  204.         foreach ($dataSet as $row) {
  205.             if ($row === null) {
  206.                 continue;
  207.             }
  208.             try {
  209.                 $object $this->importRow($definition$row$dataSetarray_merge($params, ['row' => $count]), $filter);
  210.                 if ($object instanceof Concrete) {
  211.                     $objectIds[] = $object->getId();
  212.                 }
  213.                 if (($count 1) % $countToClean === 0) {
  214.                     Pimcore::collectGarbage();
  215.                     $this->logger->info('Clean Garbage');
  216.                     $this->eventDispatcher->dispatch($definition'data_definitions.import.status''Collect Garbage',
  217.                         $params);
  218.                 }
  219.                 $count++;
  220.             } catch (Throwable $ex) {
  221.                 $this->logger->error($ex);
  222.                 $exceptions[] = $ex;
  223.                 $this->eventDispatcher->dispatch($definition'data_definitions.import.status',
  224.                     sprintf('Error: %s'$ex->getMessage()), $params);
  225.                 if ($definition->getStopOnException()) {
  226.                     throw $ex;
  227.                 }
  228.             }
  229.             $this->eventDispatcher->dispatch($definition'data_definitions.import.progress'''$params);
  230.             if ($this->shouldStop) {
  231.                 $this->eventDispatcher->dispatch($definition'data_definitions.import.status',
  232.                     'Process has been stopped.');
  233.                 return [$objectIds$exceptions];
  234.             }
  235.         }
  236.         return [$objectIds$exceptions];
  237.     }
  238.     private function importRow(
  239.         ImportDefinitionInterface $definition,
  240.         array $data,
  241.         ImportDataSetInterface $dataSet,
  242.         array $params,
  243.         FilterInterface $filter null
  244.     ): ?Concrete
  245.     {
  246.         $runner null;
  247.         $object $this->getObject($definition$data$params);
  248.         if (null !== $object && !$object->getId()) {
  249.             if ($definition->getSkipNewObjects()) {
  250.                 $this->eventDispatcher->dispatch($definition'data_definitions.import.status''Ignoring new Object',
  251.                     $params);
  252.                 return null;
  253.             }
  254.         } else if ($definition->getSkipExistingObjects()) {
  255.             $this->eventDispatcher->dispatch($definition'data_definitions.import.status''Ignoring existing Object',
  256.                 $params);
  257.             return null;
  258.         }
  259.         if ($filter instanceof FilterInterface) {
  260.             if ($filter instanceof DataSetAwareInterface) {
  261.                 $filter->setDataSet($dataSet);
  262.             }
  263.             if ($filter instanceof LoggerAwareInterface) {
  264.                 $filter->setLogger($this->logger);
  265.             }
  266.             if (!$filter->filter($definition$data$object)) {
  267.                 $this->eventDispatcher->dispatch($definition'data_definitions.import.status''Filtered Object'$params);
  268.                 return null;
  269.             }
  270.         }
  271.         $this->eventDispatcher->dispatch(
  272.             $definition,
  273.             'data_definitions.import.status',
  274.             sprintf('Import Object %s', ($object->getId() ? $object->getFullPath() : 'new')),
  275.             $params
  276.         );
  277.         $this->eventDispatcher->dispatch(
  278.             $definition,
  279.             'data_definitions.import.object.start',
  280.             $object,
  281.             $params
  282.         );
  283.         if ($definition->getRunner()) {
  284.             $runner $this->runnerRegistry->get($definition->getRunner());
  285.         }
  286.         if ($runner instanceof RunnerInterface) {
  287.             if ($runner instanceof DataSetAwareInterface) {
  288.                 $runner->setDataSet($dataSet);
  289.             }
  290.             if ($runner instanceof LoggerAwareInterface) {
  291.                 $runner->setLogger($this->logger);
  292.             }
  293.             $runner->preRun($object$data$definition$params);
  294.         }
  295.         $this->logger->info(sprintf('Imported Object: %s'$object->getRealFullPath()));
  296.         /** @var ImportMapping $mapItem */
  297.         foreach ($definition->getMapping() as $mapItem) {
  298.             $value null;
  299.             if (array_key_exists($mapItem->getFromColumn(), $data) || $mapItem->getFromColumn() === "custom") {
  300.                 $value $data[$mapItem->getFromColumn()] ?? null;
  301.                 $this->setObjectValue($object$mapItem$value$data$dataSet$definition$params$runner);
  302.             }
  303.         }
  304.         $shouldSave true;
  305.         if ($runner instanceof SaveRunnerInterface) {
  306.             if ($runner instanceof DataSetAwareInterface) {
  307.                 $runner->setDataSet($dataSet);
  308.             }
  309.             if ($runner instanceof LoggerAwareInterface) {
  310.                 $runner->setLogger($this->logger);
  311.             }
  312.             $shouldSave $runner->shouldSaveObject($object$data$definition$params);
  313.         }
  314.         if ($shouldSave) {
  315.             $params['versionNote'] = sprintf('%s - %s'$definition->getId(), $definition->getName());
  316.             $object->setUserModification($params['userId'] ?? 0);
  317.             $object->setOmitMandatoryCheck($definition->getOmitMandatoryCheck());
  318.             $this->saveObject($object$definition$params);
  319.             $this->eventDispatcher->dispatch(
  320.                 $definition,
  321.                 'data_definitions.import.status',
  322.                 sprintf('Imported Object %s'$object->getFullPath()),
  323.                 $params
  324.             );
  325.         } else {
  326.             $this->eventDispatcher->dispatch(
  327.                 $definition,
  328.                 'data_definitions.import.status',
  329.                 sprintf('Skipped Object %s'$object->getFullPath()),
  330.                 $params
  331.             );
  332.         }
  333.         $this->eventDispatcher->dispatch(
  334.             $definition,
  335.             'data_definitions.import.status',
  336.             sprintf('Imported Object %s'$object->getFullPath()),
  337.             $params
  338.         );
  339.         $this->eventDispatcher->dispatch(
  340.             $definition,
  341.             'data_definitions.import.object.finished',
  342.             $object,
  343.             $params
  344.         );
  345.         if ($runner instanceof RunnerInterface) {
  346.             if ($runner instanceof DataSetAwareInterface) {
  347.                 $runner->setDataSet($dataSet);
  348.             }
  349.             if ($runner instanceof LoggerAwareInterface) {
  350.                 $runner->setLogger($this->logger);
  351.             }
  352.             $runner->postRun($object$data$definition$params);
  353.         }
  354.         return $object;
  355.     }
  356.     private function setObjectValue(
  357.         Concrete $object,
  358.         ImportMapping $map,
  359.         $value,
  360.         array $data,
  361.         ImportDataSetInterface $dataSet,
  362.         ImportDefinitionInterface $definition,
  363.         array $params,
  364.         RunnerInterface $runner null
  365.     ): void {
  366.         if ($map->getInterpreter()) {
  367.             try {
  368.                 $interpreter $this->interpreterRegistry->get($map->getInterpreter());
  369.                 if ($interpreter instanceof DataSetAwareInterface) {
  370.                     $interpreter->setDataSet($dataSet);
  371.                 }
  372.                 if ($interpreter instanceof LoggerAwareInterface) {
  373.                     $interpreter->setLogger($this->logger);
  374.                 }
  375.                 try {
  376.                     $value $interpreter->interpret(
  377.                         $object,
  378.                         $value,
  379.                         $map,
  380.                         $data,
  381.                         $definition,
  382.                         $params,
  383.                         $map->getInterpreterConfig() ?? []
  384.                     );
  385.                 }
  386.                 catch (UnexpectedValueException $ex) {
  387.                     $this->logger->info(sprintf('Unexpected Value from Interpreter "%s" with message "%s"'$map->getInterpreter(), $ex->getMessage()));
  388.                 }
  389.             } catch (DoNotSetException $ex) {
  390.                 return;
  391.             }
  392.         }
  393.         if ($map->getToColumn() === 'o_type' && $map->getSetter() !== 'object_type') {
  394.             throw new InvalidArgumentException('Type has to be used with ObjectType Setter!');
  395.         }
  396.         $shouldSetField true;
  397.         if ($runner instanceof SetterRunnerInterface) {
  398.             if ($runner instanceof DataSetAwareInterface) {
  399.                 $runner->setDataSet($dataSet);
  400.             }
  401.             if ($runner instanceof LoggerAwareInterface) {
  402.                 $runner->setLogger($this->logger);
  403.             }
  404.             $shouldSetField $runner->shouldSetField($object$map$value$data$definition$params);
  405.         }
  406.         if (!$shouldSetField) {
  407.             return;
  408.         }
  409.         if ($map->getSetter()) {
  410.             $setter $this->setterRegistry->get($map->getSetter());
  411.             if ($setter instanceof SetterInterface) {
  412.                 if ($setter instanceof DataSetAwareInterface) {
  413.                     $setter->setDataSet($dataSet);
  414.                 }
  415.                 if ($setter instanceof LoggerAwareInterface) {
  416.                     $setter->setLogger($this->logger);
  417.                 }
  418.                 $setter->set($object$value$map$data);
  419.             }
  420.         } else {
  421.             $object->setValue($map->getToColumn(), $value);
  422.         }
  423.     }
  424.     private function getObject(ImportDefinitionInterface $definition$data$params): Concrete
  425.     {
  426.         $class $definition->getClass();
  427.         $classObject '\Pimcore\Model\DataObject\\'.ucfirst($class);
  428.         $classDefinition ClassDefinition::getByName($class);
  429.         if (!$classDefinition instanceof ClassDefinition) {
  430.             throw new InvalidArgumentException(sprintf('Class not found %s'$class));
  431.         }
  432.         /**
  433.          * @var $loader LoaderInterface
  434.          */
  435.         if ($definition->getLoader()) {
  436.             $loader $this->loaderRegistry->get($definition->getLoader());
  437.         } else {
  438.             $loader $this->loaderRegistry->get('primary_key');
  439.         }
  440.         $obj $loader->load($class$data$definition$params);
  441.         if (null === $obj) {
  442.             $classImplementation $this->modelFactory->getClassNameFor($classObject) ?? $classObject;
  443.             $obj = new $classImplementation();
  444.         }
  445.         $key Service::getValidKey($this->createKey($definition$data), 'object');
  446.         if ($definition->getRelocateExistingObjects() || !$obj->getId()) {
  447.             $obj->setParent(Service::createFolderByPath($this->createPath($definition$data)));
  448.         }
  449.         if ($definition->getRenameExistingObjects() || !$obj->getId()) {
  450.             if ($key && $definition->getKey()) {
  451.                 $obj->setKey($key);
  452.             } else {
  453.                 $obj->setKey(File::getValidFilename(uniqid(''true)));
  454.             }
  455.         }
  456.         if (!$obj->getKey()) {
  457.             throw new InvalidArgumentException('No key set, please check your import-data');
  458.         }
  459.         $obj->setKey(Service::getUniqueKey($obj));
  460.         return $obj;
  461.     }
  462. private function createPath(ImportDefinitionInterface $definition, array $data): string
  463.     {
  464.         if (!$definition->getObjectPath()) {
  465.             return '';
  466.         }
  467.         if (str_starts_with($definition->getObjectPath(), '@')) {
  468.             return $this->expressionLanguage->evaluate(substr($definition->getObjectPath(), 1), $data);
  469.         }
  470.         return $definition->getObjectPath() ?? '';
  471.     }
  472.     private function createKey(ImportDefinitionInterface $definition, array $data): string
  473.     {
  474.         if (!$definition->getKey()) {
  475.             return '';
  476.         }
  477.         if (str_starts_with($definition->getKey(), '@')) {
  478.             return $this->expressionLanguage->evaluate(substr($definition->getKey(), 1), $data);
  479.         }
  480.         return $definition->getKey();
  481.     }
  482.     private function saveObject(Concrete $objectImportDefinitionInterface $definition, array $params): void
  483.     {
  484.         $persister null;
  485.         if ($definition->getPersister()) {
  486.             $persister $this->persisterRegistry->get($definition->getPersister());
  487.         }
  488.         if (!$persister instanceof PersisterInterface) {
  489.             $persister $this->persisterRegistry->get('persister');
  490.         }
  491.         if ($persister instanceof LoggerAwareInterface) {
  492.             $persister->setLogger($this->logger);
  493.         }
  494.         $persister->persist($object$definition$params);
  495.     }
  496. }