vendor/shopware/core/Content/Product/DataAbstractionLayer/ProductIndexer.php line 285

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\Content\Product\DataAbstractionLayer;
  3. use Doctrine\DBAL\Connection;
  4. use Shopware\Core\Content\Product\Events\ProductIndexerEvent;
  5. use Shopware\Core\Content\Product\ProductDefinition;
  6. use Shopware\Core\Defaults;
  7. use Shopware\Core\Framework\DataAbstractionLayer\Dbal\Common\IterableQuery;
  8. use Shopware\Core\Framework\DataAbstractionLayer\Dbal\Common\IteratorFactory;
  9. use Shopware\Core\Framework\DataAbstractionLayer\Doctrine\RetryableQuery;
  10. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
  11. use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityWrittenContainerEvent;
  12. use Shopware\Core\Framework\DataAbstractionLayer\Indexing\ChildCountUpdater;
  13. use Shopware\Core\Framework\DataAbstractionLayer\Indexing\EntityIndexer;
  14. use Shopware\Core\Framework\DataAbstractionLayer\Indexing\EntityIndexerRegistry;
  15. use Shopware\Core\Framework\DataAbstractionLayer\Indexing\EntityIndexingMessage;
  16. use Shopware\Core\Framework\DataAbstractionLayer\Indexing\InheritanceUpdater;
  17. use Shopware\Core\Framework\DataAbstractionLayer\Indexing\ManyToManyIdFieldUpdater;
  18. use Shopware\Core\Framework\Feature;
  19. use Shopware\Core\Framework\Log\Package;
  20. use Shopware\Core\Framework\Plugin\Exception\DecorationPatternException;
  21. use Shopware\Core\Framework\Uuid\Uuid;
  22. use Shopware\Core\Profiling\Profiler;
  23. use Symfony\Component\Messenger\MessageBusInterface;
  24. use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
  25. #[Package('core')]
  26. class ProductIndexer extends EntityIndexer
  27. {
  28.     public const INHERITANCE_UPDATER 'product.inheritance';
  29.     public const STOCK_UPDATER 'product.stock';
  30.     public const VARIANT_LISTING_UPDATER 'product.variant-listing';
  31.     public const CHILD_COUNT_UPDATER 'product.child-count';
  32.     public const MANY_TO_MANY_ID_FIELD_UPDATER 'product.many-to-many-id-field';
  33.     public const CATEGORY_DENORMALIZER_UPDATER 'product.category-denormalizer';
  34.     public const CHEAPEST_PRICE_UPDATER 'product.cheapest-price';
  35.     public const RATING_AVERAGE_UPDATER 'product.rating-average';
  36.     public const STREAM_UPDATER 'product.stream';
  37.     public const SEARCH_KEYWORD_UPDATER 'product.search-keyword';
  38.     public const STATES_UPDATER 'product.states';
  39.     private IteratorFactory $iteratorFactory;
  40.     private EntityRepositoryInterface $repository;
  41.     private Connection $connection;
  42.     private VariantListingUpdater $variantListingUpdater;
  43.     private ProductCategoryDenormalizer $categoryDenormalizer;
  44.     private CheapestPriceUpdater $cheapestPriceUpdater;
  45.     private SearchKeywordUpdater $searchKeywordUpdater;
  46.     private InheritanceUpdater $inheritanceUpdater;
  47.     private RatingAverageUpdater $ratingAverageUpdater;
  48.     private ChildCountUpdater $childCountUpdater;
  49.     private ManyToManyIdFieldUpdater $manyToManyIdFieldUpdater;
  50.     private StockUpdater $stockUpdater;
  51.     private EventDispatcherInterface $eventDispatcher;
  52.     private ProductStreamUpdater $streamUpdater;
  53.     private StatesUpdater $statesUpdater;
  54.     private MessageBusInterface $messageBus;
  55.     /**
  56.      * @internal
  57.      */
  58.     public function __construct(
  59.         IteratorFactory $iteratorFactory,
  60.         EntityRepositoryInterface $repository,
  61.         Connection $connection,
  62.         VariantListingUpdater $variantListingUpdater,
  63.         ProductCategoryDenormalizer $categoryDenormalizer,
  64.         InheritanceUpdater $inheritanceUpdater,
  65.         RatingAverageUpdater $ratingAverageUpdater,
  66.         SearchKeywordUpdater $searchKeywordUpdater,
  67.         ChildCountUpdater $childCountUpdater,
  68.         ManyToManyIdFieldUpdater $manyToManyIdFieldUpdater,
  69.         StockUpdater $stockUpdater,
  70.         EventDispatcherInterface $eventDispatcher,
  71.         CheapestPriceUpdater $cheapestPriceUpdater,
  72.         ProductStreamUpdater $streamUpdater,
  73.         StatesUpdater $statesUpdater,
  74.         MessageBusInterface $messageBus
  75.     ) {
  76.         $this->iteratorFactory $iteratorFactory;
  77.         $this->repository $repository;
  78.         $this->connection $connection;
  79.         $this->variantListingUpdater $variantListingUpdater;
  80.         $this->categoryDenormalizer $categoryDenormalizer;
  81.         $this->searchKeywordUpdater $searchKeywordUpdater;
  82.         $this->inheritanceUpdater $inheritanceUpdater;
  83.         $this->ratingAverageUpdater $ratingAverageUpdater;
  84.         $this->childCountUpdater $childCountUpdater;
  85.         $this->manyToManyIdFieldUpdater $manyToManyIdFieldUpdater;
  86.         $this->stockUpdater $stockUpdater;
  87.         $this->eventDispatcher $eventDispatcher;
  88.         $this->cheapestPriceUpdater $cheapestPriceUpdater;
  89.         $this->streamUpdater $streamUpdater;
  90.         $this->statesUpdater $statesUpdater;
  91.         $this->messageBus $messageBus;
  92.     }
  93.     public function getName(): string
  94.     {
  95.         return 'product.indexer';
  96.     }
  97.     /**
  98.      * @param array<string, string>|null $offset
  99.      *
  100.      * @deprecated tag:v6.5.0 The parameter $offset will be natively typed
  101.      */
  102.     public function iterate(/*?array */$offset): ?EntityIndexingMessage
  103.     {
  104.         if ($offset !== null && !\is_array($offset)) {
  105.             Feature::triggerDeprecationOrThrow(
  106.                 'v6.5.0.0',
  107.                 'Parameter `$offset` of method "iterate()" in class "ProductIndexer" will be natively typed to `?array` in v6.5.0.0.'
  108.             );
  109.         }
  110.         $iterator $this->getIterator($offset);
  111.         $ids $iterator->fetch();
  112.         if (empty($ids)) {
  113.             return null;
  114.         }
  115.         return new ProductIndexingMessage(array_values($ids), $iterator->getOffset());
  116.     }
  117.     public function update(EntityWrittenContainerEvent $event): ?EntityIndexingMessage
  118.     {
  119.         $updates $event->getPrimaryKeys(ProductDefinition::ENTITY_NAME);
  120.         if (empty($updates)) {
  121.             return null;
  122.         }
  123.         Profiler::trace('product:indexer:inheritance', function () use ($updates$event): void {
  124.             $this->inheritanceUpdater->update(ProductDefinition::ENTITY_NAME$updates$event->getContext());
  125.         });
  126.         $stocks $event->getPrimaryKeysWithPropertyChange(ProductDefinition::ENTITY_NAME, ['stock''isCloseout''minPurchase']);
  127.         Profiler::trace('product:indexer:stock', function () use ($stocks$event): void {
  128.             $this->stockUpdater->update($stocks$event->getContext());
  129.         });
  130.         $message = new ProductIndexingMessage(array_values($updates), null$event->getContext());
  131.         $message->addSkip(self::INHERITANCE_UPDATERself::STOCK_UPDATER);
  132.         // @deprecated tag:v6.5.0 - remove this function call and simply use the `$updates` property instead
  133.         // @deprecated tag:v6.5.0 - with next major, we will only dispatch an update event of the updated variant and not for the parent too. This would cause an indexing process of all variants
  134.         $updates $event->getPrimaryKeysWithPayload(ProductDefinition::ENTITY_NAME);
  135.         $delayed \array_unique(\array_filter(\array_merge(
  136.             $this->getParentIds($updates),
  137.             $this->getChildrenIds($updates)
  138.         )));
  139.         foreach (\array_chunk($delayed50) as $chunk) {
  140.             $child = new ProductIndexingMessage($chunknull$event->getContext());
  141.             $child->setIndexer($this->getName());
  142.             EntityIndexerRegistry::addSkips($child$event->getContext());
  143.             $this->messageBus->dispatch($child);
  144.         }
  145.         return $message;
  146.     }
  147.     public function getTotal(): int
  148.     {
  149.         return $this->getIterator(null)->fetchCount();
  150.     }
  151.     public function getDecorated(): EntityIndexer
  152.     {
  153.         throw new DecorationPatternException(self::class);
  154.     }
  155.     public function handle(EntityIndexingMessage $message): void
  156.     {
  157.         $ids array_unique(array_filter($message->getData()));
  158.         if (empty($ids)) {
  159.             return;
  160.         }
  161.         $parentIds $this->filterVariants($ids);
  162.         $context $message->getContext();
  163.         if ($message->allow(self::INHERITANCE_UPDATER)) {
  164.             Profiler::trace('product:indexer:inheritance', function () use ($ids$context): void {
  165.                 $this->inheritanceUpdater->update(ProductDefinition::ENTITY_NAME$ids$context);
  166.             });
  167.         }
  168.         if ($message->allow(self::STOCK_UPDATER)) {
  169.             Profiler::trace('product:indexer:stock', function () use ($ids$context): void {
  170.                 $this->stockUpdater->update($ids$context);
  171.             });
  172.         }
  173.         if ($message->allow(self::VARIANT_LISTING_UPDATER)) {
  174.             Profiler::trace('product:indexer:variant-listing', function () use ($parentIds$context): void {
  175.                 $this->variantListingUpdater->update($parentIds$context);
  176.             });
  177.         }
  178.         if ($message->allow(self::CHILD_COUNT_UPDATER)) {
  179.             Profiler::trace('product:indexer:child-count', function () use ($parentIds$context): void {
  180.                 $this->childCountUpdater->update(ProductDefinition::ENTITY_NAME$parentIds$context);
  181.             });
  182.         }
  183.         if ($message->allow(self::STREAM_UPDATER)) {
  184.             Profiler::trace('product:indexer:streams', function () use ($ids$context): void {
  185.                 $this->streamUpdater->updateProducts($ids$context);
  186.             });
  187.         }
  188.         if ($message->allow(self::MANY_TO_MANY_ID_FIELD_UPDATER)) {
  189.             Profiler::trace('product:indexer:many-to-many', function () use ($ids$context): void {
  190.                 $this->manyToManyIdFieldUpdater->update(ProductDefinition::ENTITY_NAME$ids$context);
  191.             });
  192.         }
  193.         if ($message->allow(self::CATEGORY_DENORMALIZER_UPDATER)) {
  194.             Profiler::trace('product:indexer:category', function () use ($ids$context): void {
  195.                 $this->categoryDenormalizer->update($ids$context);
  196.             });
  197.         }
  198.         if ($message->allow(self::CHEAPEST_PRICE_UPDATER)) {
  199.             Profiler::trace('product:indexer:cheapest-price', function () use ($parentIds$context): void {
  200.                 $this->cheapestPriceUpdater->update($parentIds$context);
  201.             });
  202.         }
  203.         if ($message->allow(self::RATING_AVERAGE_UPDATER)) {
  204.             Profiler::trace('product:indexer:rating', function () use ($parentIds$context): void {
  205.                 $this->ratingAverageUpdater->update($parentIds$context);
  206.             });
  207.         }
  208.         if ($message->allow(self::SEARCH_KEYWORD_UPDATER)) {
  209.             Profiler::trace('product:indexer:search-keywords', function () use ($ids$context): void {
  210.                 $this->searchKeywordUpdater->update($ids$context);
  211.             });
  212.         }
  213.         if ($message->allow(self::STATES_UPDATER)) {
  214.             Profiler::trace('product:indexer:states', function () use ($ids$context): void {
  215.                 $this->statesUpdater->update($ids$context);
  216.             });
  217.         }
  218.         RetryableQuery::retryable($this->connection, function () use ($ids): void {
  219.             $this->connection->executeStatement(
  220.                 'UPDATE product SET updated_at = :now WHERE id IN (:ids)',
  221.                 ['ids' => Uuid::fromHexToBytesList($ids), 'now' => (new \DateTime())->format(Defaults::STORAGE_DATE_TIME_FORMAT)],
  222.                 ['ids' => Connection::PARAM_STR_ARRAY]
  223.             );
  224.         });
  225.         // @deprecated tag:v6.5.0 - parentIds and childrenIds will be removed - event methods will be removed too
  226.         $parentIds $this->getParentIds($ids);
  227.         $childrenIds $this->getChildrenIds($ids);
  228.         Profiler::trace('product:indexer:event', function () use ($ids$childrenIds$parentIds$context$message): void {
  229.             $this->eventDispatcher->dispatch(new ProductIndexerEvent($ids$childrenIds$parentIds$context$message->getSkip()));
  230.         });
  231.     }
  232.     public function getOptions(): array
  233.     {
  234.         return [
  235.             self::INHERITANCE_UPDATER,
  236.             self::STOCK_UPDATER,
  237.             self::VARIANT_LISTING_UPDATER,
  238.             self::CHILD_COUNT_UPDATER,
  239.             self::MANY_TO_MANY_ID_FIELD_UPDATER,
  240.             self::CATEGORY_DENORMALIZER_UPDATER,
  241.             self::CHEAPEST_PRICE_UPDATER,
  242.             self::RATING_AVERAGE_UPDATER,
  243.             self::STREAM_UPDATER,
  244.             self::SEARCH_KEYWORD_UPDATER,
  245.         ];
  246.     }
  247.     /**
  248.      * @param string[] $ids
  249.      *
  250.      * @return string[]
  251.      */
  252.     private function getChildrenIds(array $ids): array
  253.     {
  254.         $childrenIds $this->connection->fetchAllAssociative(
  255.             'SELECT DISTINCT LOWER(HEX(id)) as id FROM product WHERE parent_id IN (:ids)',
  256.             ['ids' => Uuid::fromHexToBytesList($ids)],
  257.             ['ids' => Connection::PARAM_STR_ARRAY]
  258.         );
  259.         return array_unique(array_filter(array_column($childrenIds'id')));
  260.     }
  261.     /**
  262.      * @param string[] $ids
  263.      *
  264.      * @return array|mixed[]
  265.      */
  266.     private function getParentIds(array $ids): array
  267.     {
  268.         $parentIds $this->connection->fetchFirstColumn(
  269.             'SELECT DISTINCT LOWER(HEX(product.parent_id)) as id FROM product WHERE id IN (:ids)',
  270.             ['ids' => Uuid::fromHexToBytesList($ids)],
  271.             ['ids' => Connection::PARAM_STR_ARRAY]
  272.         );
  273.         return array_unique(array_filter($parentIds));
  274.     }
  275.     /**
  276.      * @param string[] $ids
  277.      *
  278.      * @return array|mixed[]
  279.      */
  280.     private function filterVariants(array $ids): array
  281.     {
  282.         return $this->connection->fetchFirstColumn(
  283.             'SELECT DISTINCT LOWER(HEX(`id`))
  284.              FROM product
  285.              WHERE `id` IN (:ids)
  286.              AND `parent_id` IS NULL',
  287.             ['ids' => Uuid::fromHexToBytesList($ids)],
  288.             ['ids' => Connection::PARAM_STR_ARRAY]
  289.         );
  290.     }
  291.     /**
  292.      * @param array<string, string>|null $offset
  293.      */
  294.     private function getIterator(?array $offset): IterableQuery
  295.     {
  296.         return $this->iteratorFactory->createIterator($this->repository->getDefinition(), $offset);
  297.     }
  298. }