src/EventSubscriber/Import/EntityMapper/VOD/Title/EntityPreInitSubscriber.php line 126

Open in your IDE?
  1. <?php
  2. namespace App\EventSubscriber\Import\EntityMapper\VOD\Title;
  3. use App\Repository\SonataClassificationCategoryRepository;
  4. use App\Application\EntityImportBundle\Event\EmbeddableMapperEvent;
  5. use App\Application\EntityImportBundle\Event\EntityPreInitEvent;
  6. use App\Application\EntityImportBundle\Exception\RequiredPropertyException;
  7. use App\Application\EntityImportBundle\ValueObject\ConfigItemPropertiesValue;
  8. use App\Entity\SonataClassificationCategory;
  9. use App\Entity\ProductGenre;
  10. use App\Entity\VOD\Title;
  11. use App\Entity\VOD\TitleEstInitialPriceCategory;
  12. use App\Entity\VOD\TitleMediaFormat;
  13. use App\Entity\VOD\TitleTvodInitialPriceCategory;
  14. use App\Enum\Genre;
  15. use App\Enum\VOD\EstPriceCategory;
  16. use App\Enum\VOD\MediaFormat;
  17. use App\Enum\VOD\TitleCategoryEnum;
  18. use App\Enum\VOD\TitleLanguageEnum;
  19. use App\Enum\VOD\TvodPriceCategory;
  20. use App\EventSubscriber\Import\EntityMapper\BasePreInitSubscriber;
  21. use App\Repository\ProductRepository;
  22. use App\Repository\VOD\TitleRepository;
  23. use App\Util\Helper\StringUtils;
  24. use Doctrine\Common\Collections\ArrayCollection;
  25. use Doctrine\Common\Collections\Collection;
  26. use Doctrine\ORM\EntityManagerInterface;
  27. use Psr\Log\LoggerInterface;
  28. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  29. /**
  30.  * Class EntityPreInitSubscriber.
  31.  */
  32. class EntityPreInitSubscriber extends BasePreInitSubscriber
  33. {
  34.     /**
  35.      * The event dispatcher.
  36.      *
  37.      * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
  38.      */
  39.     private $eventDispatcher;
  40.     /**
  41.      * The log manager.
  42.      *
  43.      * @var \Psr\Log\LoggerInterface
  44.      */
  45.     private $logger;
  46.     /**
  47.      * The instance of the title repository.
  48.      *
  49.      * @var \App\Repository\VOD\TitleRepository
  50.      */
  51.     private $titleRepository;
  52.     /**
  53.      * The instance of the product repository.
  54.      *
  55.      * @var \App\Repository\ProductRepository
  56.      */
  57.     private $productRepository;
  58.     /**
  59.      * The instance of the classification category repository.
  60.      *
  61.      * @var \App\Entity\SonataClassificationCategoryRepository
  62.      */
  63.     private $categoryRepository;
  64.     /**
  65.      * EntityPreInitSubscriber constructor.
  66.      *
  67.      * @param \Doctrine\ORM\EntityManagerInterface $entityManager
  68.      *   The entity manager
  69.      * @param \Psr\Log\LoggerInterface $logger
  70.      *   The log manager
  71.      * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $eventDispatcher
  72.      *   The event dispatcher
  73.      * @param \App\Repository\VOD\TitleRepository $titleRepository
  74.      *   The title repository
  75.      * @param \App\Repository\ProductRepository $productRepository
  76.      *   The product repository
  77.      * @param \App\Entity\SonataClassificationCategoryRepository $categoryRepository
  78.      *   The classification category repository
  79.      */
  80.     public function __construct(
  81.         EntityManagerInterface $entityManager,
  82.         LoggerInterface $logger,
  83.         EventDispatcherInterface $eventDispatcher,
  84.         TitleRepository $titleRepository,
  85.         ProductRepository $productRepository,
  86.         SonataClassificationCategoryRepository $categoryRepository
  87.     ) {
  88.         parent::__construct($entityManager);
  89.         $this->eventDispatcher $eventDispatcher;
  90.         $this->logger $logger;
  91.         $this->titleRepository $titleRepository;
  92.         $this->productRepository $productRepository;
  93.         $this->categoryRepository $categoryRepository;
  94.     }
  95.     public static function getSubscribedEvents()
  96.     {
  97.         return [
  98.             EntityPreInitEvent::class => 'onEntityPreInit',
  99.         ];
  100.     }
  101.     /**
  102.      * Public callback for the pre init event.
  103.      *
  104.      * Responsible for providing the mapping between parsed csv entry row and Title entity.
  105.      * The reason why we need pre-init event, rather than the default one, is because in
  106.      * this case we need a little bit more advanced logic when deciding how we will handle
  107.      * the entity instance - whether to load an existing one or create one and this cannot
  108.      * be achieved by simply providing or omitting an entity id.
  109.      *
  110.      * @param \App\Application\EntityImportBundle\Event\EntityPreInitEvent $event
  111.      *   The instance of the dispatched event
  112.      */
  113.     public function onEntityPreInit(EntityPreInitEvent $event): void
  114.     {
  115.         if (Title::class !== $event->getTargetClassName()) {
  116.             return;
  117.         }
  118.         $configuredProperties $event->getConfigurationProperties();
  119.         // Firstly, we need to find out whether or not there is an existing entity
  120.         // that we can update, otherwise we will just create a new instance of the
  121.         // Title entity. To do that, we will grab a reference to some of the
  122.         // mandatory properties and use their values to look for a record.
  123.         [$navCodeProperty$extendedCodeProperty$languageProperty] = [
  124.             $this->filterConfiguredProperty('navCode'$configuredPropertiestrue),
  125.             $this->filterConfiguredProperty('extendedNavCode'$configuredPropertiestrue),
  126.             $this->filterConfiguredProperty('language'$configuredPropertiestrue),
  127.         ];
  128.         if (null === $navCodeProperty || null === $extendedCodeProperty || null === $languageProperty) {
  129.             $event->setSkipRowProcessing(true);
  130.             return;
  131.         }
  132.         $data $event->getData();
  133.         $navCode $this->getValue($navCodeProperty$data);
  134.         $extendedNavCode $this->getValue($extendedCodeProperty$data);
  135.         $language $this->getValue($languageProperty$data);
  136.         // The properties are mandatory, but we will check anyway whether we can get the data
  137.         // because we need all of them in order to try and find an existing title entity.
  138.         if (!$navCode || !$extendedNavCode || !$language || !TitleLanguageEnum::accepts($language)) {
  139.             $event->setSkipRowProcessing(true);
  140.             return;
  141.         }
  142.         if (!$entity $this->titleRepository->findForEntityImport($navCode$extendedNavCode$language)) {
  143.             $entity = new Title();
  144.             $entity->setExtendedNavCode($extendedNavCode);
  145.             $entity->setLanguage($language);
  146.             // We did not find an existing Title entity, therefore we created a new one instead.
  147.             // But in order to proceed with the import process, first we need to find a valid
  148.             // product based on the navision title code, in order to setup the product reference.
  149.             // A Title record is only valid when we have a product to attach it to.
  150.             if (!$product $this->productRepository->findOneByNavCode($navCode)) {
  151.                 $event->setSkipRowProcessing(true);
  152.                 return;
  153.             }
  154.             $entity->setProduct($product);
  155.         }
  156.         // Go through all, but the embeddable properties.
  157.         foreach ($configuredProperties as $property) {
  158.             if ($property->isEmbeddable()) {
  159.                 continue;
  160.             }
  161.             $callback StringUtils::camelize('handle_' $property->getName());
  162.             try {
  163.                 if (method_exists($this$callback)) {
  164.                     $this->{$callback}($entity$property$data);
  165.                 } else {
  166.                     $this->defaultPropertyHandler($entity$property$data);
  167.                 }
  168.             } catch (\LogicException RequiredPropertyException \UnexpectedValueException $ex) {
  169.                 $this->logger->error($ex->getMessage());
  170.                 $event->setSkipRowProcessing(true);
  171.             }
  172.         }
  173.         // Go through all embeddable properties.
  174.         $this->eventDispatcher->dispatch(new EmbeddableMapperEvent(
  175.             $entity,
  176.             $event->getEmbeddedProperties(),
  177.             $data
  178.         ));
  179.         $event->setEntity($entity);
  180.     }
  181.     /**
  182.      * Responsible for applying default entity mapping for given property.
  183.      *
  184.      * @param \App\Entity\VOD\Title $entity
  185.      *   The managed entity
  186.      * @param \App\Application\EntityImportBundle\ValueObject\ConfigItemPropertiesValue $property
  187.      *   The property to map
  188.      * @param array $data
  189.      *   The parsed data of the currently managed row
  190.      */
  191.     protected function defaultPropertyHandler(Title $entityConfigItemPropertiesValue $property, array $data): void
  192.     {
  193.         $this->setPropertyValue($entity$property$this->getValue($property$data$entitytrue));
  194.     }
  195.     /**
  196.      * Responsible for applying mapping for enumerable category type.
  197.      *
  198.      * @param \App\Entity\VOD\Title $entity
  199.      *   The managed entity
  200.      * @param \App\Application\EntityImportBundle\ValueObject\ConfigItemPropertiesValue $property
  201.      *   The property to map
  202.      * @param array $data
  203.      *   The parsed data of the currently managed row
  204.      */
  205.     protected function handleCategoryType(Title $entityConfigItemPropertiesValue $property, array $data): void
  206.     {
  207.         $value $this->getValue($property$data$entitytrue);
  208.         if (!TitleCategoryEnum::accepts($value)) {
  209.             throw new \UnexpectedValueException("Invalid value provided for property {$property->getName()}.");
  210.         }
  211.         $this->setPropertyValue($entity$property$value);
  212.     }
  213.     /**
  214.      * Responsible for providing mapping for product genres collection.
  215.      *
  216.      * @param \App\Entity\VOD\Title $entity
  217.      *   The managed entity
  218.      * @param \App\Application\EntityImportBundle\ValueObject\ConfigItemPropertiesValue $property
  219.      *   The property to map
  220.      * @param array $data
  221.      *   The parsed data of the currently managed row
  222.      */
  223.     protected function handleGenres(Title $entityConfigItemPropertiesValue $property, array $data): void
  224.     {
  225.         if (!$value $this->getPropertyValue($property$data$entity)) {
  226.             return;
  227.         }
  228.         if (!$product $entity->getProduct()) {
  229.             return;
  230.         }
  231.         if (!$parts array_map('trim'explode(','$value))) {
  232.             return;
  233.         }
  234.         $genres = new ArrayCollection();
  235.         foreach ($parts as $item) {
  236.             if (Genre::accepts($item)) {
  237.                 $genre = new ProductGenre();
  238.                 $genre->setGenre($item);
  239.                 $genre->setProduct($product);
  240.                 $genres->add($genre);
  241.             }
  242.         }
  243.         if (!$genres->isEmpty()) {
  244.             $product->getGenres()->clear();
  245.             $product->setGenres($genres);
  246.         } elseif ($genres->isEmpty() && $this->getAllowedNullableCharacter() === $value) {
  247.             $product->getGenres()->clear();
  248.         }
  249.     }
  250.     /**
  251.      * Responsible for providing mapping for entity aspect ratio.
  252.      *
  253.      * @param \App\Entity\VOD\Title $entity
  254.      *   The managed entity
  255.      * @param \App\Application\EntityImportBundle\ValueObject\ConfigItemPropertiesValue $property
  256.      *   The property to map
  257.      * @param array $data
  258.      *   The parsed data of the currently managed row
  259.      */
  260.     protected function handleAspectRatio(Title $entityConfigItemPropertiesValue $property, array $data): void
  261.     {
  262.         if (!$value $this->getValue($property$data$entity)) {
  263.             return;
  264.         }
  265.         $aspectRatio $this->categoryRepository->findByNameAndContext(trim($value));
  266.         if ($aspectRatio instanceof Category) {
  267.             $this->setPropertyValue($entity$property$aspectRatio);
  268.         } elseif ($this->getAllowedNullableCharacter() === $value) {
  269.             $this->setPropertyValue($entity$property$value);
  270.         }
  271.     }
  272.     /**
  273.      * Responsible for providing mapping for the media IDs collection.
  274.      *
  275.      * @param \App\Entity\VOD\Title $entity
  276.      *   The managed entity
  277.      * @param \App\Application\EntityImportBundle\ValueObject\ConfigItemPropertiesValue $property
  278.      *   The property to map
  279.      * @param array $data
  280.      *   The parsed data of the currently managed row
  281.      */
  282.     protected function handleMediaIdFormat(Title $entityConfigItemPropertiesValue $property, array $data): void
  283.     {
  284.         if (!$value $this->getValue($property$data$entitytrue)) {
  285.             return;
  286.         }
  287.         if (!MediaFormat::accepts($value)) {
  288.             throw new \UnexpectedValueException("Invalid value provided for property {$property->getName()}.");
  289.         }
  290.         $currentMediaFormats = [];
  291.         foreach ($entity->getMediaFormats() as $mediaFormat) {
  292.             $currentMediaFormats[] = $mediaFormat->getFormat();
  293.         }
  294.         // We already have an existing media format, do not process further.
  295.         // Media formats have also auto generated IDs, so this process should
  296.         // be handle carefully.
  297.         if (in_array($value$currentMediaFormats)) {
  298.             return;
  299.         }
  300.         // No media format found, create new entity and proceed.
  301.         $mediaFormat = new TitleMediaFormat();
  302.         $mediaFormat->setFormat($value);
  303.         $mediaFormat->setTitle($entity);
  304.         $entity->addMediaFormat($mediaFormat);
  305.         $this->getEntityManager()->persist($mediaFormat);
  306.     }
  307.     /**
  308.      * Responsible for providing mapping for the initial EST price category.
  309.      *
  310.      * @param \App\Entity\VOD\Title $entity
  311.      *   The managed entity
  312.      * @param \App\Application\EntityImportBundle\ValueObject\ConfigItemPropertiesValue $property
  313.      *   The property to map
  314.      * @param array $data
  315.      *   The parsed data of the currently managed row
  316.      */
  317.     protected function handleEstPriceCategory(Title $entityConfigItemPropertiesValue $property, array $data): void
  318.     {
  319.         if (!$value $this->getValue($property$data$entitytrue)) {
  320.             return;
  321.         }
  322.         $collection $entity->getEstInitialPriceCategories();
  323.         $collection $collection->filter(fn (TitleEstInitialPriceCategory $category) => empty($category->getCountry()));
  324.         if ($this->getAllowedNullableCharacter() === $value || !EstPriceCategory::accepts($value)) {
  325.             throw new \UnexpectedValueException("Invalid value provided for property {$property->getName()}.");
  326.         }
  327.         // We do not have any categories added yet for this VET, so create the initial one.
  328.         if ($collection->isEmpty()) {
  329.             $priceCategory = new TitleEstInitialPriceCategory();
  330.             $priceCategory->setTitle($entity);
  331.             $priceCategory->setCategory($value);
  332.             $entity->addEstInitialPriceCategory($priceCategory);
  333.             $this->getEntityManager()->persist($priceCategory);
  334.             return;
  335.         }
  336.         // We have a valid price category entity /managed at some point/ so we either
  337.         // update it or remove it from the collection, depending on the value.
  338.         $initialCategory $collection->first();
  339.         // If we are up to this point, that means that we already have managed price category instance
  340.         // and we simply want to update it's value.
  341.         $initialCategory->setCategory($value);
  342.         $this->getEntityManager()->persist($initialCategory);
  343.     }
  344.     /**
  345.      * Responsible for providing mapping for the initial TVOD price category.
  346.      *
  347.      * @param \App\Entity\VOD\Title $entity
  348.      *   The managed entity
  349.      * @param \App\Application\EntityImportBundle\ValueObject\ConfigItemPropertiesValue $property
  350.      *   The property to map
  351.      * @param array $data
  352.      *   The parsed data of the currently managed row
  353.      */
  354.     protected function handleTvodPriceCategory(Title $entityConfigItemPropertiesValue $property, array $data): void
  355.     {
  356.         if (!$value $this->getValue($property$data$entitytrue)) {
  357.             return;
  358.         }
  359.         $collection $entity->getTvodInitialPriceCategories();
  360.         $collection $collection->filter(fn (TitleTvodInitialPriceCategory $category) => empty($category->getCountry()));
  361.         if ($this->getAllowedNullableCharacter() === $value || !TvodPriceCategory::accepts($value)) {
  362.             throw new \UnexpectedValueException("Invalid value provided for property {$property->getName()}.");
  363.         }
  364.         // We do not have any categories added yet for this VET, so create the initial one.
  365.         if ($collection->isEmpty()) {
  366.             $priceCategory = new TitleTvodInitialPriceCategory();
  367.             $priceCategory->setTitle($entity);
  368.             $priceCategory->setCategory($value);
  369.             $entity->addTvodInitialPriceCategory($priceCategory);
  370.             $this->getEntityManager()->persist($priceCategory);
  371.             return;
  372.         }
  373.         // We have a valid price category entity /managed at some point/ so we either
  374.         // update it or remove it from the collection, depending on the value.
  375.         $initialCategory $collection->first();
  376.         // If we are up to this point, that means that we already have managed price category instance
  377.         // and we simply want to update it's value.
  378.         $initialCategory->setCategory($value);
  379.         $this->getEntityManager()->persist($initialCategory);
  380.     }
  381. }