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

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