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

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($keys, array_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($tags, array_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($tags, array_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($tags, array_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($tags, array_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($tags, array_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. }