custom/plugins/PickwareErpStarter/src/Cache/CacheInvalidationSubscriber.php line 63

Open in your IDE?
  1. <?php
  2. /*
  3.  * Copyright (c) Pickware GmbH. All rights reserved.
  4.  * This file is part of software that is released under a proprietary license.
  5.  * You must not copy, modify, distribute, make publicly available, or execute
  6.  * its contents or parts thereof without express permission by the copyright
  7.  * holder, unless otherwise permitted by law.
  8.  */
  9. declare(strict_types=1);
  10. namespace Pickware\PickwareErpStarter\Cache;
  11. use Doctrine\DBAL\Connection;
  12. use Pickware\PickwareErpStarter\Stock\Model\StockMovementDefinition;
  13. use Pickware\PickwareErpStarter\Stock\ProductAvailableStockUpdatedEvent;
  14. use Pickware\PickwareErpStarter\Stock\StockUpdatedForStockMovementsEvent;
  15. use Shopware\Core\Content\Product\SalesChannel\Detail\CachedProductDetailRoute;
  16. use Shopware\Core\Defaults;
  17. use Shopware\Core\Framework\Adapter\Cache\CacheInvalidator;
  18. use Shopware\Core\Framework\DataAbstractionLayer\Cache\EntityCacheKeyGenerator;
  19. use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityWrittenEvent;
  20. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  21. class CacheInvalidationSubscriber implements EventSubscriberInterface
  22. {
  23.     private Connection $connection;
  24.     private CacheInvalidator $cacheInvalidator;
  25.     public function __construct(
  26.         Connection $connection,
  27.         CacheInvalidator $cacheInvalidator
  28.     ) {
  29.         $this->connection $connection;
  30.         $this->cacheInvalidator $cacheInvalidator;
  31.     }
  32.     public static function getSubscribedEvents(): array
  33.     {
  34.         return [
  35.             ProductAvailableStockUpdatedEvent::class => [
  36.                 'onProductAvailableStockUpdated',
  37.                 PHP_INT_MIN,
  38.             ],
  39.             StockMovementDefinition::ENTITY_WRITTEN_EVENT => [
  40.                 'stockMovementWritten',
  41.                 10000// Set a high priority to execute the invalidation of the cache before any other updates are written
  42.             ],
  43.             StockUpdatedForStockMovementsEvent::class => [
  44.                 'onStockUpdatedForStockMovements',
  45.                 10000// Set a high priority to execute the invalidation of the cache before any other updates are written
  46.             ],
  47.         ];
  48.     }
  49.     public function onProductAvailableStockUpdated(ProductAvailableStockUpdatedEvent $event): void
  50.     {
  51.         $this->invalidateProductCache($event->getProductIds());
  52.     }
  53.     public function stockMovementWritten(EntityWrittenEvent $entityWrittenEvent): void
  54.     {
  55.         if ($entityWrittenEvent->getContext()->getVersionId() !== Defaults::LIVE_VERSION) {
  56.             return;
  57.         }
  58.         $productIds = [];
  59.         foreach ($entityWrittenEvent->getWriteResults() as $writeResult) {
  60.             if ($writeResult->getExistence()->exists()) {
  61.                 // Updating stock movements is not supported yet
  62.                 // In case a stock location is deleted, this code path is also reached. This is because an
  63.                 // EntityWrittenEvent is triggered when an entity field gets null-ed because of a SET NULL constraint
  64.                 // of a FK.
  65.                 continue;
  66.             }
  67.             $payload $writeResult->getPayload();
  68.             $productIds[] = $payload['productId'];
  69.         }
  70.         $this->invalidateProductStreams($productIds);
  71.     }
  72.     public function onStockUpdatedForStockMovements(StockUpdatedForStockMovementsEvent $event): void
  73.     {
  74.         $productIds array_values(array_map(
  75.             fn (array $stockMovement) => $stockMovement['productId'],
  76.             $event->getStockMovements(),
  77.         ));
  78.         $this->invalidateProductStreams($productIds);
  79.     }
  80.     private function invalidateProductCache(array $productIds): void
  81.     {
  82.         // Invalidate the storefront api cache if the products stock or reserved stock was updated and in turn the
  83.         // product availability was recalculated. For variant products the variant and main product cache need to be
  84.         // invalidated.
  85.         $parentIds $this->connection->fetchFirstColumn(
  86.             'SELECT DISTINCT LOWER(HEX(COALESCE(parent_id, id)))
  87.                     FROM product
  88.                     WHERE id in (:productIds) AND version_id = :version',
  89.             [
  90.                 'productIds' => array_map('hex2bin'$productIds),
  91.                 'version' => hex2bin(Defaults::LIVE_VERSION),
  92.             ],
  93.             [
  94.                 'productIds' => Connection::PARAM_STR_ARRAY,
  95.             ],
  96.         );
  97.         $productIds array_merge($productIds$parentIds);
  98.         $this->invalidateDetailRoute($productIds);
  99.         $this->invalidateProductIds($productIds);
  100.         $this->invalidateProductStreams($productIds);
  101.     }
  102.     private function invalidateDetailRoute(array $productIds): void
  103.     {
  104.         $this->cacheInvalidator->invalidate(
  105.             array_map([CachedProductDetailRoute::class, 'buildName'], $productIds),
  106.         );
  107.     }
  108.     private function invalidateProductIds(array $productIds): void
  109.     {
  110.         $this->cacheInvalidator->invalidate(
  111.             array_map([EntityCacheKeyGenerator::class, 'buildProductTag'], $productIds),
  112.         );
  113.     }
  114.     private function invalidateProductStreams(array $productIds): void
  115.     {
  116.         if (count($productIds) === 0) {
  117.             return;
  118.         }
  119.         $ids $this->connection->fetchFirstColumn(
  120.             'SELECT DISTINCT LOWER(HEX(product_stream_id))
  121.              FROM product_stream_mapping
  122.              WHERE product_stream_mapping.product_id IN (:ids)
  123.              AND product_stream_mapping.product_version_id = :version',
  124.             [
  125.                 'ids' => array_map('hex2bin'$productIds),
  126.                 'version' => hex2bin(Defaults::LIVE_VERSION),
  127.             ],
  128.             [
  129.                 'ids' => Connection::PARAM_STR_ARRAY,
  130.             ],
  131.         );
  132.         if (count($ids) === 0) {
  133.             return;
  134.         }
  135.         $this->cacheInvalidator->invalidate(
  136.             array_map([EntityCacheKeyGenerator::class, 'buildStreamTag'], $ids),
  137.         );
  138.     }
  139. }