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

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