vendor/shopware/platform/src/Core/Framework/Adapter/Cache/CacheInvalidationSubscriber.php line 293

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\Framework\Adapter\Cache;
  3. use Doctrine\DBAL\Connection;
  4. use Shopware\Core\Checkout\Cart\CachedRuleLoader;
  5. use Shopware\Core\Checkout\Customer\Aggregate\CustomerGroup\CustomerGroupDefinition;
  6. use Shopware\Core\Checkout\Payment\PaymentMethodDefinition;
  7. use Shopware\Core\Checkout\Payment\SalesChannel\CachedPaymentMethodRoute;
  8. use Shopware\Core\Checkout\Shipping\SalesChannel\CachedShippingMethodRoute;
  9. use Shopware\Core\Checkout\Shipping\ShippingMethodDefinition;
  10. use Shopware\Core\Content\Category\CategoryDefinition;
  11. use Shopware\Core\Content\Category\Event\CategoryIndexerEvent;
  12. use Shopware\Core\Content\Category\SalesChannel\CachedCategoryRoute;
  13. use Shopware\Core\Content\Category\SalesChannel\CachedNavigationRoute;
  14. use Shopware\Core\Content\Cms\CmsPageDefinition;
  15. use Shopware\Core\Content\LandingPage\Event\LandingPageIndexerEvent;
  16. use Shopware\Core\Content\LandingPage\SalesChannel\CachedLandingPageRoute;
  17. use Shopware\Core\Content\Product\Aggregate\ProductCategory\ProductCategoryDefinition;
  18. use Shopware\Core\Content\Product\Aggregate\ProductCrossSelling\ProductCrossSellingDefinition;
  19. use Shopware\Core\Content\Product\Aggregate\ProductManufacturer\ProductManufacturerDefinition;
  20. use Shopware\Core\Content\Product\Aggregate\ProductProperty\ProductPropertyDefinition;
  21. use Shopware\Core\Content\Product\Events\ProductChangedEventInterface;
  22. use Shopware\Core\Content\Product\Events\ProductIndexerEvent;
  23. use Shopware\Core\Content\Product\Events\ProductNoLongerAvailableEvent;
  24. use Shopware\Core\Content\Product\ProductDefinition;
  25. use Shopware\Core\Content\Product\SalesChannel\CrossSelling\CachedProductCrossSellingRoute;
  26. use Shopware\Core\Content\Product\SalesChannel\Detail\CachedProductDetailRoute;
  27. use Shopware\Core\Content\Product\SalesChannel\Listing\CachedProductListingRoute;
  28. use Shopware\Core\Content\Product\SalesChannel\Review\CachedProductReviewRoute;
  29. use Shopware\Core\Content\ProductStream\ProductStreamDefinition;
  30. use Shopware\Core\Content\Property\Aggregate\PropertyGroupOption\PropertyGroupOptionDefinition;
  31. use Shopware\Core\Content\Property\Aggregate\PropertyGroupOptionTranslation\PropertyGroupOptionTranslationDefinition;
  32. use Shopware\Core\Content\Property\Aggregate\PropertyGroupTranslation\PropertyGroupTranslationDefinition;
  33. use Shopware\Core\Content\Property\PropertyGroupDefinition;
  34. use Shopware\Core\Content\Rule\Event\RuleIndexerEvent;
  35. use Shopware\Core\Content\Seo\CachedSeoResolver;
  36. use Shopware\Core\Content\Seo\Event\SeoUrlUpdateEvent;
  37. use Shopware\Core\Content\Sitemap\Event\SitemapGeneratedEvent;
  38. use Shopware\Core\Content\Sitemap\SalesChannel\CachedSitemapRoute;
  39. use Shopware\Core\Defaults;
  40. use Shopware\Core\Framework\Adapter\Translation\Translator;
  41. use Shopware\Core\Framework\DataAbstractionLayer\Cache\EntityCacheKeyGenerator;
  42. use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityWrittenContainerEvent;
  43. use Shopware\Core\Framework\Plugin\Event\PluginPostActivateEvent;
  44. use Shopware\Core\Framework\Plugin\Event\PluginPostDeactivateEvent;
  45. use Shopware\Core\Framework\Plugin\Event\PluginPostInstallEvent;
  46. use Shopware\Core\Framework\Plugin\Event\PluginPostUninstallEvent;
  47. use Shopware\Core\Framework\Plugin\Event\PluginPostUpdateEvent;
  48. use Shopware\Core\Framework\Uuid\Uuid;
  49. use Shopware\Core\System\Country\CountryDefinition;
  50. use Shopware\Core\System\Country\SalesChannel\CachedCountryRoute;
  51. use Shopware\Core\System\Currency\CurrencyDefinition;
  52. use Shopware\Core\System\Currency\SalesChannel\CachedCurrencyRoute;
  53. use Shopware\Core\System\Language\LanguageDefinition;
  54. use Shopware\Core\System\Language\SalesChannel\CachedLanguageRoute;
  55. use Shopware\Core\System\SalesChannel\Aggregate\SalesChannelCountry\SalesChannelCountryDefinition;
  56. use Shopware\Core\System\SalesChannel\Aggregate\SalesChannelCurrency\SalesChannelCurrencyDefinition;
  57. use Shopware\Core\System\SalesChannel\Aggregate\SalesChannelLanguage\SalesChannelLanguageDefinition;
  58. use Shopware\Core\System\SalesChannel\Aggregate\SalesChannelPaymentMethod\SalesChannelPaymentMethodDefinition;
  59. use Shopware\Core\System\SalesChannel\Aggregate\SalesChannelShippingMethod\SalesChannelShippingMethodDefinition;
  60. use Shopware\Core\System\SalesChannel\Context\CachedSalesChannelContextFactory;
  61. use Shopware\Core\System\SalesChannel\SalesChannelDefinition;
  62. use Shopware\Core\System\Salutation\SalesChannel\CachedSalutationRoute;
  63. use Shopware\Core\System\Salutation\SalutationDefinition;
  64. use Shopware\Core\System\Snippet\SnippetDefinition;
  65. use Shopware\Core\System\StateMachine\Loader\InitialStateIdLoader;
  66. use Shopware\Core\System\StateMachine\StateMachineDefinition;
  67. use Shopware\Core\System\SystemConfig\CachedSystemConfigLoader;
  68. use Shopware\Core\System\SystemConfig\Event\SystemConfigChangedEvent;
  69. use Shopware\Core\System\SystemConfig\SystemConfigService;
  70. use Shopware\Core\System\Tax\TaxDefinition;
  71. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  72. /**
  73.  * @internal - The functions inside this class are no public-api and can be changed without previous deprecation
  74.  */
  75. class CacheInvalidationSubscriber implements EventSubscriberInterface
  76. {
  77.     private Connection $connection;
  78.     private CacheInvalidator $cacheInvalidator;
  79.     public function __construct(
  80.         CacheInvalidator $cacheInvalidator,
  81.         Connection $connection
  82.     ) {
  83.         $this->cacheInvalidator $cacheInvalidator;
  84.         $this->connection $connection;
  85.     }
  86.     /**
  87.      * @return array<string, string|array{0: string, 1: int}|list<array{0: string, 1?: int}>>
  88.      */
  89.     public static function getSubscribedEvents()
  90.     {
  91.         return [
  92.             CategoryIndexerEvent::class => [
  93.                 ['invalidateCategoryRouteByCategoryIds'2000],
  94.                 ['invalidateListingRouteByCategoryIds'2001],
  95.             ],
  96.             LandingPageIndexerEvent::class => [
  97.                 ['invalidateIndexedLandingPages'2000],
  98.             ],
  99.             ProductIndexerEvent::class => [
  100.                 ['invalidateSearch'2000],
  101.                 ['invalidateListings'2001],
  102.                 ['invalidateProductIds'2002],
  103.                 ['invalidateDetailRoute'2004],
  104.                 ['invalidateStreamsAfterIndexing'2005],
  105.                 ['invalidateReviewRoute'2006],
  106.             ],
  107.             ProductNoLongerAvailableEvent::class => [
  108.                 ['invalidateSearch'2000],
  109.                 ['invalidateListings'2001],
  110.                 ['invalidateProductIds'2002],
  111.                 ['invalidateDetailRoute'2004],
  112.                 ['invalidateStreamsAfterIndexing'2005],
  113.                 ['invalidateReviewRoute'2006],
  114.             ],
  115.             EntityWrittenContainerEvent::class => [
  116.                 ['invalidateCmsPageIds'2001],
  117.                 ['invalidateCurrencyRoute'2002],
  118.                 ['invalidateLanguageRoute'2003],
  119.                 ['invalidateNavigationRoute'2004],
  120.                 ['invalidatePaymentMethodRoute'2005],
  121.                 ['invalidateProductAssignment'2006],
  122.                 ['invalidateManufacturerFilters'2007],
  123.                 ['invalidatePropertyFilters'2008],
  124.                 ['invalidateCrossSellingRoute'2009],
  125.                 ['invalidateContext'2010],
  126.                 ['invalidateShippingMethodRoute'2011],
  127.                 ['invalidateSnippets'2012],
  128.                 ['invalidateStreamsBeforeIndexing'2013],
  129.                 ['invalidateStreamIds'2014],
  130.                 ['invalidateCountryRoute'2015],
  131.                 ['invalidateSalutationRoute'2016],
  132.                 ['invalidateInitialStateIdLoader'2017],
  133.             ],
  134.             SeoUrlUpdateEvent::class => [
  135.                 ['invalidateSeoUrls'2000],
  136.             ],
  137.             RuleIndexerEvent::class => [
  138.                 ['invalidateRules'2000],
  139.             ],
  140.             PluginPostInstallEvent::class => [
  141.                 ['invalidateRules'2000],
  142.                 ['invalidateConfig'2001],
  143.             ],
  144.             PluginPostActivateEvent::class => [
  145.                 ['invalidateRules'2000],
  146.                 ['invalidateConfig'2001],
  147.             ],
  148.             PluginPostUpdateEvent::class => [
  149.                 ['invalidateRules'2000],
  150.                 ['invalidateConfig'2001],
  151.             ],
  152.             PluginPostDeactivateEvent::class => [
  153.                 ['invalidateRules'2000],
  154.                 ['invalidateConfig'2001],
  155.             ],
  156.             PluginPostUninstallEvent::class => [
  157.                 ['invalidateRules'2000],
  158.                 ['invalidateConfig'2001],
  159.             ],
  160.             SystemConfigChangedEvent::class => [
  161.                 ['invalidateConfigKey'2000],
  162.             ],
  163.             SitemapGeneratedEvent::class => [
  164.                 ['invalidateSitemap'2000],
  165.             ],
  166.         ];
  167.     }
  168.     public function invalidateInitialStateIdLoader(EntityWrittenContainerEvent $event): void
  169.     {
  170.         if (!$event->getPrimaryKeys(StateMachineDefinition::ENTITY_NAME)) {
  171.             return;
  172.         }
  173.         $this->cacheInvalidator->invalidate([InitialStateIdLoader::CACHE_KEY]);
  174.     }
  175.     public function invalidateSitemap(SitemapGeneratedEvent $event): void
  176.     {
  177.         $this->cacheInvalidator->invalidate([
  178.             CachedSitemapRoute::buildName($event->getSalesChannelContext()->getSalesChannelId()),
  179.         ]);
  180.     }
  181.     public function invalidateConfig(): void
  182.     {
  183.         // invalidates the complete cached config
  184.         $this->cacheInvalidator->invalidate([
  185.             CachedSystemConfigLoader::CACHE_TAG,
  186.         ]);
  187.     }
  188.     public function invalidateConfigKey(SystemConfigChangedEvent $event): void
  189.     {
  190.         // invalidates the complete cached config and routes which access a specific key
  191.         $this->cacheInvalidator->invalidate([
  192.             SystemConfigService::buildName($event->getKey()),
  193.             CachedSystemConfigLoader::CACHE_TAG,
  194.         ]);
  195.     }
  196.     public function invalidateSnippets(EntityWrittenContainerEvent $event): void
  197.     {
  198.         // invalidates all http cache items where the snippets used
  199.         $snippets $event->getEventByEntityName(SnippetDefinition::ENTITY_NAME);
  200.         if (!$snippets) {
  201.             return;
  202.         }
  203.         $tags = [];
  204.         foreach ($snippets->getPayloads() as $payload) {
  205.             if (isset($payload['translationKey'])) {
  206.                 $tags[] = Translator::buildName($payload['translationKey']);
  207.             }
  208.         }
  209.         $this->cacheInvalidator->invalidate($tags);
  210.     }
  211.     public function invalidateShippingMethodRoute(EntityWrittenContainerEvent $event): void
  212.     {
  213.         // checks if a shipping method changed or the assignment between shipping method and sales channel
  214.         $logs array_merge(
  215.             $this->getChangedShippingMethods($event),
  216.             $this->getChangedShippingAssignments($event)
  217.         );
  218.         $this->cacheInvalidator->invalidate($logs);
  219.     }
  220.     public function invalidateSeoUrls(SeoUrlUpdateEvent $event): void
  221.     {
  222.         // invalidates the cache for the seo url resolver based on the path infos which used for the new seo urls
  223.         $urls $event->getSeoUrls();
  224.         $pathInfo array_column($urls'pathInfo');
  225.         $this->cacheInvalidator->invalidate(array_map([CachedSeoResolver::class, 'buildName'], $pathInfo));
  226.     }
  227.     public function invalidateRules(): void
  228.     {
  229.         // invalidates the rule loader each time a rule changed or a plugin install state changed
  230.         $this->cacheInvalidator->invalidate([CachedRuleLoader::CACHE_KEY]);
  231.     }
  232.     public function invalidateCmsPageIds(EntityWrittenContainerEvent $event): void
  233.     {
  234.         // invalidates all routes and http cache pages where a cms page was loaded, the id is assigned as tag
  235.         $this->cacheInvalidator->invalidate(
  236.             array_map([EntityCacheKeyGenerator::class, 'buildCmsTag'], $event->getPrimaryKeys(CmsPageDefinition::ENTITY_NAME))
  237.         );
  238.     }
  239.     public function invalidateProductIds(ProductChangedEventInterface $event): void
  240.     {
  241.         // invalidates all routes which loads products in nested unknown objects, like cms listing elements or cross selling elements
  242.         $this->cacheInvalidator->invalidate(
  243.             array_map([EntityCacheKeyGenerator::class, 'buildProductTag'], $event->getIds())
  244.         );
  245.     }
  246.     public function invalidateStreamIds(EntityWrittenContainerEvent $event): void
  247.     {
  248.         // invalidates all routes which are loaded based on a stream (e.G. category listing and cross selling)
  249.         $this->cacheInvalidator->invalidate(
  250.             array_map([EntityCacheKeyGenerator::class, 'buildStreamTag'], $event->getPrimaryKeys(ProductStreamDefinition::ENTITY_NAME))
  251.         );
  252.     }
  253.     public function invalidateCategoryRouteByCategoryIds(CategoryIndexerEvent $event): void
  254.     {
  255.         // invalidates the category route cache when a category changed
  256.         $this->cacheInvalidator->invalidate(
  257.             array_map([CachedCategoryRoute::class, 'buildName'], $event->getIds())
  258.         );
  259.     }
  260.     public function invalidateListingRouteByCategoryIds(CategoryIndexerEvent $event): void
  261.     {
  262.         // invalidates the product listing route each time a category changed
  263.         $this->cacheInvalidator->invalidate(
  264.             array_map([CachedProductListingRoute::class, 'buildName'], $event->getIds())
  265.         );
  266.     }
  267.     public function invalidateIndexedLandingPages(LandingPageIndexerEvent $event): void
  268.     {
  269.         // invalidates the landing page route, if the corresponding landing page changed
  270.         $this->cacheInvalidator->invalidate(
  271.             array_map([CachedLandingPageRoute::class, 'buildName'], $event->getIds())
  272.         );
  273.     }
  274.     public function invalidateCurrencyRoute(EntityWrittenContainerEvent $event): void
  275.     {
  276.         // invalidates the currency route when a currency changed or an assignment between the sales channel and currency changed
  277.         $this->cacheInvalidator->invalidate(array_merge(
  278.             $this->getChangedCurrencyAssignments($event),
  279.             $this->getChangedCurrencies($event)
  280.         ));
  281.     }
  282.     public function invalidateLanguageRoute(EntityWrittenContainerEvent $event): void
  283.     {
  284.         // invalidates the language route when a language changed or an assignment between the sales channel and language changed
  285.         $this->cacheInvalidator->invalidate(array_merge(
  286.             $this->getChangedLanguageAssignments($event),
  287.             $this->getChangedLanguages($event)
  288.         ));
  289.     }
  290.     public function invalidateCountryRoute(EntityWrittenContainerEvent $event): void
  291.     {
  292.         // invalidates the country route when a country changed or an assignment between the sales channel and country changed
  293.         $this->cacheInvalidator->invalidate(array_merge(
  294.             $this->getChangedCountryAssignments($event),
  295.             $this->getChangedCountries($event),
  296.         ));
  297.     }
  298.     public function invalidateSalutationRoute(EntityWrittenContainerEvent $event): void
  299.     {
  300.         // invalidates the salutation route when a salutation changed
  301.         $this->cacheInvalidator->invalidate(array_merge(
  302.             $this->getChangedSalutations($event),
  303.         ));
  304.     }
  305.     public function invalidateNavigationRoute(EntityWrittenContainerEvent $event): void
  306.     {
  307.         // invalidates the navigation route when a category changed or the entry point configuration of an sales channel changed
  308.         $logs array_merge(
  309.             $this->getChangedCategories($event),
  310.             $this->getChangedEntryPoints($event)
  311.         );
  312.         $this->cacheInvalidator->invalidate($logs);
  313.     }
  314.     public function invalidatePaymentMethodRoute(EntityWrittenContainerEvent $event): void
  315.     {
  316.         // invalidates the payment method route when a payment method changed or an assignment between the sales channel and payment method changed
  317.         $logs array_merge(
  318.             $this->getChangedPaymentMethods($event),
  319.             $this->getChangedPaymentAssignments($event)
  320.         );
  321.         $this->cacheInvalidator->invalidate($logs);
  322.     }
  323.     public function invalidateSearch(): void
  324.     {
  325.         // invalidates the search and suggest route each time a product changed
  326.         $this->cacheInvalidator->invalidate([
  327.             'product-suggest-route',
  328.             'product-search-route',
  329.         ]);
  330.     }
  331.     public function invalidateDetailRoute(ProductChangedEventInterface $event): void
  332.     {
  333.         //invalidates the product detail route each time a product changed or if the product is no longer available (because out of stock)
  334.         $this->cacheInvalidator->invalidate(
  335.             array_map([CachedProductDetailRoute::class, 'buildName'], $event->getIds())
  336.         );
  337.     }
  338.     public function invalidateProductAssignment(EntityWrittenContainerEvent $event): void
  339.     {
  340.         //invalidates the product listing route, each time a product - category assignment changed
  341.         $ids $event->getPrimaryKeys(ProductCategoryDefinition::ENTITY_NAME);
  342.         $ids array_column($ids'categoryId');
  343.         $this->cacheInvalidator->invalidate(
  344.             array_map([CachedProductListingRoute::class, 'buildName'], $ids)
  345.         );
  346.     }
  347.     public function invalidateContext(EntityWrittenContainerEvent $event): void
  348.     {
  349.         //invalidates the context cache - each time one of the entities which are considered inside the context factory changed
  350.         $ids $event->getPrimaryKeys(SalesChannelDefinition::ENTITY_NAME);
  351.         $keys array_map([CachedSalesChannelContextFactory::class, 'buildName'], $ids);
  352.         if ($event->getEventByEntityName(CurrencyDefinition::ENTITY_NAME)) {
  353.             $keys[] = CachedSalesChannelContextFactory::ALL_TAG;
  354.         }
  355.         if ($event->getEventByEntityName(PaymentMethodDefinition::ENTITY_NAME)) {
  356.             $keys[] = CachedSalesChannelContextFactory::ALL_TAG;
  357.         }
  358.         if ($event->getEventByEntityName(ShippingMethodDefinition::ENTITY_NAME)) {
  359.             $keys[] = CachedSalesChannelContextFactory::ALL_TAG;
  360.         }
  361.         if ($event->getEventByEntityName(TaxDefinition::ENTITY_NAME)) {
  362.             $keys[] = CachedSalesChannelContextFactory::ALL_TAG;
  363.         }
  364.         if ($event->getEventByEntityName(CountryDefinition::ENTITY_NAME)) {
  365.             $keys[] = CachedSalesChannelContextFactory::ALL_TAG;
  366.         }
  367.         if ($event->getEventByEntityName(CustomerGroupDefinition::ENTITY_NAME)) {
  368.             $keys[] = CachedSalesChannelContextFactory::ALL_TAG;
  369.         }
  370.         if ($event->getEventByEntityName(LanguageDefinition::ENTITY_NAME)) {
  371.             $keys[] = CachedSalesChannelContextFactory::ALL_TAG;
  372.         }
  373.         $keys array_filter(array_unique($keys));
  374.         if (empty($keys)) {
  375.             return;
  376.         }
  377.         $this->cacheInvalidator->invalidate($keys);
  378.     }
  379.     public function invalidateManufacturerFilters(EntityWrittenContainerEvent $event): void
  380.     {
  381.         // invalidates the product listing route, each time a manufacturer changed
  382.         $ids $event->getPrimaryKeys(ProductManufacturerDefinition::ENTITY_NAME);
  383.         if (empty($ids)) {
  384.             return;
  385.         }
  386.         $ids $this->connection->fetchFirstColumn(
  387.             'SELECT DISTINCT LOWER(HEX(category_id)) as category_id
  388.              FROM product_category_tree
  389.                 INNER JOIN product ON product.id = product_category_tree.product_id AND product_category_tree.product_version_id = product.version_id
  390.              WHERE product.product_manufacturer_id IN (:ids)
  391.              AND product.version_id = :version',
  392.             ['ids' => Uuid::fromHexToBytesList($ids), 'version' => Uuid::fromHexToBytes(Defaults::LIVE_VERSION)],
  393.             ['ids' => Connection::PARAM_STR_ARRAY]
  394.         );
  395.         $this->cacheInvalidator->invalidate(
  396.             array_map([CachedProductListingRoute::class, 'buildName'], $ids)
  397.         );
  398.     }
  399.     public function invalidatePropertyFilters(EntityWrittenContainerEvent $event): void
  400.     {
  401.         $this->cacheInvalidator->invalidate(array_merge(
  402.             $this->getChangedPropertyFilterTags($event),
  403.             $this->getDeletedPropertyFilterTags($event)
  404.         ));
  405.     }
  406.     public function invalidateReviewRoute(ProductChangedEventInterface $event): void
  407.     {
  408.         $this->cacheInvalidator->invalidate(
  409.             array_map([CachedProductReviewRoute::class, 'buildName'], $event->getIds())
  410.         );
  411.     }
  412.     public function invalidateListings(ProductChangedEventInterface $event): void
  413.     {
  414.         // invalidates product listings which are based on the product category assignment
  415.         $this->cacheInvalidator->invalidate(
  416.             array_map([CachedProductListingRoute::class, 'buildName'], $this->getProductCategoryIds($event->getIds()))
  417.         );
  418.     }
  419.     public function invalidateStreamsBeforeIndexing(EntityWrittenContainerEvent $event): void
  420.     {
  421.         // invalidates all stream based pages and routes before the product indexer changes product_stream_mapping
  422.         $ids $event->getPrimaryKeys(ProductDefinition::ENTITY_NAME);
  423.         if (empty($ids)) {
  424.             return;
  425.         }
  426.         // invalidates product listings which are based on a product stream
  427.         $ids $this->connection->fetchFirstColumn(
  428.             'SELECT DISTINCT LOWER(HEX(product_stream_id))
  429.              FROM product_stream_mapping
  430.              WHERE product_stream_mapping.product_id IN (:ids)
  431.              AND product_stream_mapping.product_version_id = :version',
  432.             ['ids' => Uuid::fromHexToBytesList($ids), 'version' => Uuid::fromHexToBytes(Defaults::LIVE_VERSION)],
  433.             ['ids' => Connection::PARAM_STR_ARRAY]
  434.         );
  435.         $this->cacheInvalidator->invalidate(
  436.             array_map([EntityCacheKeyGenerator::class, 'buildStreamTag'], $ids)
  437.         );
  438.     }
  439.     public function invalidateStreamsAfterIndexing(ProductChangedEventInterface $event): void
  440.     {
  441.         // invalidates all stream based pages and routes after the product indexer changes product_stream_mapping
  442.         $ids $this->connection->fetchFirstColumn(
  443.             'SELECT DISTINCT LOWER(HEX(product_stream_id))
  444.              FROM product_stream_mapping
  445.              WHERE product_stream_mapping.product_id IN (:ids)
  446.              AND product_stream_mapping.product_version_id = :version',
  447.             ['ids' => Uuid::fromHexToBytesList($event->getIds()), 'version' => Uuid::fromHexToBytes(Defaults::LIVE_VERSION)],
  448.             ['ids' => Connection::PARAM_STR_ARRAY]
  449.         );
  450.         $this->cacheInvalidator->invalidate(
  451.             array_map([EntityCacheKeyGenerator::class, 'buildStreamTag'], $ids)
  452.         );
  453.     }
  454.     public function invalidateCrossSellingRoute(EntityWrittenContainerEvent $event): void
  455.     {
  456.         // invalidates the product detail route for the changed cross selling definitions
  457.         $ids $event->getPrimaryKeys(ProductCrossSellingDefinition::ENTITY_NAME);
  458.         if (empty($ids)) {
  459.             return;
  460.         }
  461.         $ids $this->connection->fetchFirstColumn(
  462.             'SELECT DISTINCT LOWER(HEX(product_id)) FROM product_cross_selling WHERE id IN (:ids)',
  463.             ['ids' => Uuid::fromHexToBytesList($ids)],
  464.             ['ids' => Connection::PARAM_STR_ARRAY]
  465.         );
  466.         $this->cacheInvalidator->invalidate(
  467.             array_map([CachedProductCrossSellingRoute::class, 'buildName'], $ids)
  468.         );
  469.     }
  470.     private function getDeletedPropertyFilterTags(EntityWrittenContainerEvent $event): array
  471.     {
  472.         // invalidates the product listing route, each time a property changed
  473.         $ids $event->getDeletedPrimaryKeys(ProductPropertyDefinition::ENTITY_NAME);
  474.         if (empty($ids)) {
  475.             return [];
  476.         }
  477.         $productIds array_column($ids'productId');
  478.         return array_merge(
  479.             array_map([CachedProductDetailRoute::class, 'buildName'], array_unique($productIds)),
  480.             array_map([CachedProductListingRoute::class, 'buildName'], $this->getProductCategoryIds($productIds))
  481.         );
  482.     }
  483.     private function getChangedPropertyFilterTags(EntityWrittenContainerEvent $event): array
  484.     {
  485.         // invalidates the product listing route and detail rule, each time a property group changed
  486.         $propertyGroupIds array_unique(array_merge(
  487.             $event->getPrimaryKeysWithPayloadIgnoringFields(PropertyGroupDefinition::ENTITY_NAME, ['id''updatedAt']),
  488.             array_column($event->getPrimaryKeysWithPayloadIgnoringFields(PropertyGroupTranslationDefinition::ENTITY_NAME, ['propertyGroupId''languageId''updatedAt']), 'propertyGroupId')
  489.         ));
  490.         // invalidates the product listing route and detail rule, each time a property option changed
  491.         $propertyOptionIds array_unique(array_merge(
  492.             $event->getPrimaryKeysWithPayloadIgnoringFields(PropertyGroupOptionDefinition::ENTITY_NAME, ['id''updatedAt']),
  493.             array_column($event->getPrimaryKeysWithPayloadIgnoringFields(PropertyGroupOptionTranslationDefinition::ENTITY_NAME, ['propertyGroupOptionId''languageId''updatedAt']), 'propertyGroupOptionId')
  494.         ));
  495.         if (empty($propertyGroupIds) && empty($propertyOptionIds)) {
  496.             return [];
  497.         }
  498.         $productIds $this->connection->fetchFirstColumn(
  499.             'SELECT product_property.product_id
  500.              FROM product_property
  501.                 LEFT JOIN property_group_option productProperties ON productProperties.id = product_property.property_group_option_id
  502.              WHERE productProperties.property_group_id IN (:ids) OR productProperties.id IN (:optionIds)
  503.              AND product_property.product_version_id = :version',
  504.             ['ids' => Uuid::fromHexToBytesList($propertyGroupIds), 'optionIds' => Uuid::fromHexToBytesList($propertyOptionIds), 'version' => Uuid::fromHexToBytes(Defaults::LIVE_VERSION)],
  505.             ['ids' => Connection::PARAM_STR_ARRAY'optionIds' => Connection::PARAM_STR_ARRAY]
  506.         );
  507.         $productIds array_unique(array_merge(
  508.             $productIds,
  509.             $this->connection->fetchFirstColumn(
  510.                 'SELECT product_option.product_id
  511.                  FROM product_option
  512.                     LEFT JOIN property_group_option productOptions ON productOptions.id = product_option.property_group_option_id
  513.                  WHERE productOptions.property_group_id IN (:ids) OR productOptions.id IN (:optionIds)
  514.                  AND product_option.product_version_id = :version',
  515.                 ['ids' => Uuid::fromHexToBytesList($propertyGroupIds), 'optionIds' => Uuid::fromHexToBytesList($propertyOptionIds), 'version' => Uuid::fromHexToBytes(Defaults::LIVE_VERSION)],
  516.                 ['ids' => Connection::PARAM_STR_ARRAY'optionIds' => Connection::PARAM_STR_ARRAY]
  517.             )
  518.         ));
  519.         if (empty($productIds)) {
  520.             return [];
  521.         }
  522.         $parentIds $this->connection->fetchFirstColumn(
  523.             'SELECT DISTINCT LOWER(HEX(COALESCE(parent_id, id)))
  524.             FROM product
  525.             WHERE id in (:productIds) AND version_id = :version',
  526.             ['productIds' => $productIds'version' => Uuid::fromHexToBytes(Defaults::LIVE_VERSION)],
  527.             ['productIds' => Connection::PARAM_STR_ARRAY]
  528.         );
  529.         $categoryIds $this->connection->fetchFirstColumn(
  530.             'SELECT DISTINCT LOWER(HEX(category_id))
  531.             FROM product_category_tree
  532.             WHERE product_id in (:productIds) AND product_version_id = :version',
  533.             ['productIds' => $productIds'version' => Uuid::fromHexToBytes(Defaults::LIVE_VERSION)],
  534.             ['productIds' => Connection::PARAM_STR_ARRAY]
  535.         );
  536.         return array_merge(
  537.             array_map([CachedProductDetailRoute::class, 'buildName'], array_filter($parentIds)),
  538.             array_map([CachedProductListingRoute::class, 'buildName'], array_filter($categoryIds)),
  539.         );
  540.     }
  541.     private function getProductCategoryIds(array $ids): array
  542.     {
  543.         return $this->connection->fetchFirstColumn(
  544.             'SELECT DISTINCT LOWER(HEX(category_id)) as category_id
  545.              FROM product_category_tree
  546.              WHERE product_id IN (:ids)
  547.              AND product_version_id = :version
  548.              AND category_version_id = :version',
  549.             ['ids' => Uuid::fromHexToBytesList($ids), 'version' => Uuid::fromHexToBytes(Defaults::LIVE_VERSION)],
  550.             ['ids' => Connection::PARAM_STR_ARRAY]
  551.         );
  552.     }
  553.     private function getChangedShippingMethods(EntityWrittenContainerEvent $event): array
  554.     {
  555.         $ids $event->getPrimaryKeys(ShippingMethodDefinition::ENTITY_NAME);
  556.         if (empty($ids)) {
  557.             return [];
  558.         }
  559.         $ids $this->connection->fetchFirstColumn(
  560.             'SELECT DISTINCT LOWER(HEX(sales_channel_id)) as id FROM sales_channel_shipping_method WHERE shipping_method_id IN (:ids)',
  561.             ['ids' => Uuid::fromHexToBytesList($ids)],
  562.             ['ids' => Connection::PARAM_STR_ARRAY]
  563.         );
  564.         $tags = [];
  565.         if ($event->getDeletedPrimaryKeys(ShippingMethodDefinition::ENTITY_NAME)) {
  566.             $tags[] = CachedShippingMethodRoute::ALL_TAG;
  567.         }
  568.         return array_merge($tagsarray_map([CachedShippingMethodRoute::class, 'buildName'], $ids));
  569.     }
  570.     private function getChangedShippingAssignments(EntityWrittenContainerEvent $event): array
  571.     {
  572.         //Used to detect changes to the shipping assignment of a sales channel
  573.         $ids $event->getPrimaryKeys(SalesChannelShippingMethodDefinition::ENTITY_NAME);
  574.         $ids array_column($ids'salesChannelId');
  575.         return array_map([CachedShippingMethodRoute::class, 'buildName'], $ids);
  576.     }
  577.     private function getChangedPaymentMethods(EntityWrittenContainerEvent $event): array
  578.     {
  579.         $ids $event->getPrimaryKeys(PaymentMethodDefinition::ENTITY_NAME);
  580.         if (empty($ids)) {
  581.             return [];
  582.         }
  583.         $ids $this->connection->fetchFirstColumn(
  584.             'SELECT DISTINCT LOWER(HEX(sales_channel_id)) as id FROM sales_channel_payment_method WHERE payment_method_id IN (:ids)',
  585.             ['ids' => Uuid::fromHexToBytesList($ids)],
  586.             ['ids' => Connection::PARAM_STR_ARRAY]
  587.         );
  588.         $tags = [];
  589.         if ($event->getDeletedPrimaryKeys(PaymentMethodDefinition::ENTITY_NAME)) {
  590.             $tags[] = CachedPaymentMethodRoute::ALL_TAG;
  591.         }
  592.         return array_merge($tagsarray_map([CachedPaymentMethodRoute::class, 'buildName'], $ids));
  593.     }
  594.     private function getChangedPaymentAssignments(EntityWrittenContainerEvent $event): array
  595.     {
  596.         //Used to detect changes to the language assignment of a sales channel
  597.         $ids $event->getPrimaryKeys(SalesChannelPaymentMethodDefinition::ENTITY_NAME);
  598.         $ids array_column($ids'salesChannelId');
  599.         return array_map([CachedPaymentMethodRoute::class, 'buildName'], $ids);
  600.     }
  601.     private function getChangedCategories(EntityWrittenContainerEvent $event): array
  602.     {
  603.         $ids $event->getPrimaryKeysWithPayload(CategoryDefinition::ENTITY_NAME);
  604.         if (empty($ids)) {
  605.             return [];
  606.         }
  607.         $ids array_map([CachedNavigationRoute::class, 'buildName'], $ids);
  608.         $ids[] = CachedNavigationRoute::BASE_NAVIGATION_TAG;
  609.         return $ids;
  610.     }
  611.     private function getChangedEntryPoints(EntityWrittenContainerEvent $event): array
  612.     {
  613.         $ids $event->getPrimaryKeysWithPropertyChange(
  614.             SalesChannelDefinition::ENTITY_NAME,
  615.             ['navigationCategoryId''navigationCategoryDepth''serviceCategoryId''footerCategoryId']
  616.         );
  617.         if (empty($ids)) {
  618.             return [];
  619.         }
  620.         return [CachedNavigationRoute::ALL_TAG];
  621.     }
  622.     private function getChangedCountries(EntityWrittenContainerEvent $event): array
  623.     {
  624.         $ids $event->getPrimaryKeys(CountryDefinition::ENTITY_NAME);
  625.         if (empty($ids)) {
  626.             return [];
  627.         }
  628.         //Used to detect changes to the country itself and invalidate the route for all sales channels in which the country is assigned.
  629.         $ids $this->connection->fetchFirstColumn(
  630.             'SELECT DISTINCT LOWER(HEX(sales_channel_id)) as id FROM sales_channel_country WHERE country_id IN (:ids)',
  631.             ['ids' => Uuid::fromHexToBytesList($ids)],
  632.             ['ids' => Connection::PARAM_STR_ARRAY]
  633.         );
  634.         $tags = [];
  635.         if ($event->getDeletedPrimaryKeys(CountryDefinition::ENTITY_NAME)) {
  636.             $tags[] = CachedCountryRoute::ALL_TAG;
  637.         }
  638.         return array_merge($tagsarray_map([CachedCountryRoute::class, 'buildName'], $ids));
  639.     }
  640.     private function getChangedCountryAssignments(EntityWrittenContainerEvent $event): array
  641.     {
  642.         //Used to detect changes to the country assignment of a sales channel
  643.         $ids $event->getPrimaryKeys(SalesChannelCountryDefinition::ENTITY_NAME);
  644.         $ids array_column($ids'salesChannelId');
  645.         return array_map([CachedCountryRoute::class, 'buildName'], $ids);
  646.     }
  647.     private function getChangedSalutations(EntityWrittenContainerEvent $event): array
  648.     {
  649.         $ids $event->getPrimaryKeys(SalutationDefinition::ENTITY_NAME);
  650.         if (empty($ids)) {
  651.             return [];
  652.         }
  653.         return [CachedSalutationRoute::ALL_TAG];
  654.     }
  655.     private function getChangedLanguages(EntityWrittenContainerEvent $event): array
  656.     {
  657.         $ids $event->getPrimaryKeys(LanguageDefinition::ENTITY_NAME);
  658.         if (empty($ids)) {
  659.             return [];
  660.         }
  661.         //Used to detect changes to the language itself and invalidate the route for all sales channels in which the language is assigned.
  662.         $ids $this->connection->fetchFirstColumn(
  663.             'SELECT DISTINCT LOWER(HEX(sales_channel_id)) as id FROM sales_channel_language WHERE language_id IN (:ids)',
  664.             ['ids' => Uuid::fromHexToBytesList($ids)],
  665.             ['ids' => Connection::PARAM_STR_ARRAY]
  666.         );
  667.         $tags = [];
  668.         if ($event->getDeletedPrimaryKeys(LanguageDefinition::ENTITY_NAME)) {
  669.             $tags[] = CachedLanguageRoute::ALL_TAG;
  670.         }
  671.         return array_merge($tagsarray_map([CachedLanguageRoute::class, 'buildName'], $ids));
  672.     }
  673.     private function getChangedLanguageAssignments(EntityWrittenContainerEvent $event): array
  674.     {
  675.         //Used to detect changes to the language assignment of a sales channel
  676.         $ids $event->getPrimaryKeys(SalesChannelLanguageDefinition::ENTITY_NAME);
  677.         $ids array_column($ids'salesChannelId');
  678.         return array_map([CachedLanguageRoute::class, 'buildName'], $ids);
  679.     }
  680.     private function getChangedCurrencies(EntityWrittenContainerEvent $event): array
  681.     {
  682.         $ids $event->getPrimaryKeys(CurrencyDefinition::ENTITY_NAME);
  683.         if (empty($ids)) {
  684.             return [];
  685.         }
  686.         //Used to detect changes to the currency itself and invalidate the route for all sales channels in which the currency is assigned.
  687.         $ids $this->connection->fetchFirstColumn(
  688.             'SELECT DISTINCT LOWER(HEX(sales_channel_id)) as id FROM sales_channel_currency WHERE currency_id IN (:ids)',
  689.             ['ids' => Uuid::fromHexToBytesList($ids)],
  690.             ['ids' => Connection::PARAM_STR_ARRAY]
  691.         );
  692.         $tags = [];
  693.         if ($event->getDeletedPrimaryKeys(CurrencyDefinition::ENTITY_NAME)) {
  694.             $tags[] = CachedCurrencyRoute::ALL_TAG;
  695.         }
  696.         return array_merge($tagsarray_map([CachedCurrencyRoute::class, 'buildName'], $ids));
  697.     }
  698.     private function getChangedCurrencyAssignments(EntityWrittenContainerEvent $event): array
  699.     {
  700.         //Used to detect changes to the currency assignment of a sales channel
  701.         $ids $event->getPrimaryKeys(SalesChannelCurrencyDefinition::ENTITY_NAME);
  702.         $ids array_column($ids'salesChannelId');
  703.         return array_map([CachedCurrencyRoute::class, 'buildName'], $ids);
  704.     }
  705. }