custom/plugins/ThemeOkeOnline/src/Storefront/Product/Subscriber/StorefrontProductSearchResultLoadedSubscriber.php line 86

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace ThemeOkeOnline\Storefront\Product\Subscriber;
  3. use Psr\Log\LoggerInterface;
  4. use Shopware\Core\Framework\Context;
  5. use Shopware\Core\Content\Product\ProductEntity;
  6. use Shopware\Core\Content\Product\ProductCollection;
  7. use Shopware\Core\Framework\Adapter\Cache\CacheCompressor;
  8. use Shopware\Core\System\SystemConfig\SystemConfigService;
  9. use Symfony\Component\Cache\Adapter\TagAwareAdapterInterface;
  10. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  11. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  12. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
  13. use Shopware\Core\Content\Product\Events\ProductListingCriteriaEvent;
  14. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\MultiFilter;
  15. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
  16. use Shopware\Core\Framework\DataAbstractionLayer\Event\EntitySearchResultLoadedEvent;
  17. class StorefrontProductSearchResultLoadedSubscriber implements EventSubscriberInterface
  18. {
  19.     /**
  20.      * @var EntityRepository
  21.      */
  22.     private $productRepository;
  23.     /**
  24.      * @var SystemConfigService
  25.      */
  26.     private $systemConfigService;
  27.     /**
  28.      * @var TagAwareAdapterInterface
  29.      */
  30.     private $cache;
  31.     /** 
  32.      * @var LoggerInterface
  33.      */
  34.     private $logger;
  35.     /**
  36.      * @var Context
  37.      */
  38.     private $context;
  39.     /**
  40.      * @var string
  41.      */
  42.     public $commonPropertyGroupId;
  43.     public function __construct(
  44.         EntityRepository $productRepository,
  45.         SystemConfigService $systemConfigService,
  46.         TagAwareAdapterInterface $cache,
  47.         LoggerInterface $logger
  48.     )
  49.     {
  50.         $this->productRepository $productRepository;
  51.         $this->systemConfigService $systemConfigService;
  52.         $this->cache $cache;
  53.         $this->logger $logger;
  54.         $this->commonPropertyGroupId $this->systemConfigService->get('ThemeOkeOnline.config.parentProperty') ?? null;
  55.     }
  56.     public static function getSubscribedEvents(): array
  57.     {
  58.         return [
  59.             ProductListingCriteriaEvent::class => ['addPropertiesToProductListingCriteria'],
  60.             'sales_channel.product.search.result.loaded' => ['productListingLoaded']
  61.         ];
  62.     }
  63.     public function addPropertiesToProductListingCriteria(ProductListingCriteriaEvent $event): void
  64.     {
  65.         if($this->commonPropertyGroupId)
  66.         {
  67.             $criteria $event->getCriteria();
  68.             $criteria->addAssociation('properties');
  69.         }
  70.     }
  71.     public function productListingLoaded(EntitySearchResultLoadedEvent $event): void
  72.     {
  73.         // set context for later use
  74.         $this->context $event->getContext();
  75.         // if there is no setting set, we're done
  76.         if( ! $this->commonPropertyGroupId)
  77.             return;
  78.         // collect a list with all used property-connections
  79.         $loadedProducts $event->getResult()->getElements();
  80.         // loop trough all products
  81.         foreach($loadedProducts as $product)
  82.         {
  83.             // get the propertyGroupOptionIds based on the setting which defines the common-PropertyGroup
  84.             $commonPropertyGroupOptionIds $this->getCommonPropertyGroupOptionIds($product$this->commonPropertyGroupId);
  85.             // get the siblings, out of the cache or database
  86.             $siblings $this->getSiblingsOfProduct($product$commonPropertyGroupOptionIdstrue);
  87.             // remove the product itself from that list
  88.             $siblings $this->without($siblings$product->getId());
  89.             // add an extension
  90.             $product->addExtension('siblings'$siblings);
  91.         }
  92.     }
  93.     private function getCommonPropertyGroupOptionIds(ProductEntity $product$commonPropertyGroupId): array
  94.     {
  95.         // get the properties from the product, this can be null or an empty array, if so, we can return an empty array
  96.         $commonProductPropertyGroupOptions $product->getProperties();
  97.         if( ! $commonProductPropertyGroupOptions)
  98.             return [];
  99.         $commonProductPropertyGroupOptionValues $commonProductPropertyGroupOptions->filterByGroupId($commonPropertyGroupId);
  100.         if($commonProductPropertyGroupOptionValues->count() == 0)
  101.             return [];
  102.         // eventually, return the ids as an array
  103.         return $commonProductPropertyGroupOptionValues->getIds();
  104.     }
  105.     private function getSiblingsOfProduct(ProductEntity $product, array $commonPropertyGroupOptionIds): ProductCollection
  106.     {
  107.         // check if there are some in cache, if so, return ProductCollection from cache
  108.         // If not, get from database...
  109.         $cacheKey sprintf('product-siblings-%s'$product->getId());
  110.         // try to find the key in the cache
  111.         $item $this->cache->getItem($cacheKey);
  112.         try {
  113.             // if the tag is a hit, and has content
  114.             if ($item->isHit() && $item->get()) {
  115.                 $this->logger->info('cache-hit: ' $cacheKey);
  116.                 // uncompress and return its value
  117.                 return CacheCompressor::uncompress($item);
  118.             }
  119.         } catch (\Throwable $e) {
  120.             $this->logger->error($e->getMessage());
  121.         }
  122.         $this->logger->info('cache-miss: ' $cacheKey);
  123.         // No result found in Cache, so;
  124.         // do what you should normally do without cache, and $item->save() it, so we have it the next time out of the cache.  
  125.         // if $commonPropertyGroupOptionIds had no elements, we should not query, but we should save a empty cache item.
  126.         // set cache expire time to
  127.         $item->expiresAfter(86400);
  128.         if( ! count($commonPropertyGroupOptionIds) > 0)
  129.         {
  130.             $result = new ProductCollection();
  131.             $item CacheCompressor::compress($item$result);
  132.             $this->cache->save($item);
  133.             // and were done.
  134.             return $result;
  135.         }
  136.         // We have commonPropertyGroupOptionIds, so we should query them, to find any siblings
  137.         $criteria = new Criteria();
  138.         $criteria->setTitle('load-siblings');
  139.         $criteria->resetAssociations();
  140.         $criteria->addAssociation('cover');
  141.         // create a multi-filter, because the product we're checking could have more than one common-propertyGroupOptions (it is a multi-select in back-office)
  142.         $multiFilter = [];
  143.         foreach($commonPropertyGroupOptionIds as $commonPropertyGroupOptionId)
  144.         {
  145.             $multiFilter[] = new EqualsFilter('properties.id'$commonPropertyGroupOptionId);
  146.         }
  147.         $criteria->addFilter(
  148.             new MultiFilter(
  149.                 MultiFilter::CONNECTION_OR,
  150.                 $multiFilter
  151.             )
  152.         );
  153.         // query it!
  154.         $result $this->productRepository->search($criteria$this->getContext());
  155.         // if there are products, save them in the cache, and return them.
  156.         if($result->count() > 0)
  157.         {
  158.             $item CacheCompressor::compress($item$result->getEntities());
  159.             $this->cache->save($item);
  160.             return $result->getEntities();
  161.         }
  162.         // fallback, empty product Collection
  163.         return new ProductCollection();
  164.     }
  165.     private function without(ProductCollection $collection$id): ProductCollection
  166.     {
  167.         $collection->remove($id);
  168.         return $collection;
  169.     }
  170.     private function getContext(): ?Context
  171.     {
  172.         return $this->context;
  173.     }
  174. }