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

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