vendor/shopware/core/Framework/Adapter/Cache/CacheInvalidationSubscriber.php line 211

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