<?php declare(strict_types=1);
namespace Swkweb\ProductSet\Core\Content\ProductSet\SalesChannel;
use Psr\Log\LoggerInterface;
use Shopware\Core\Framework\Adapter\Cache\AbstractCacheTracer;
use Shopware\Core\Framework\Adapter\Cache\CacheCompressor;
use Shopware\Core\Framework\DataAbstractionLayer\Cache\EntityCacheKeyGenerator;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\Routing\Annotation\Entity;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Swkweb\ProductSet\Core\Content\ProductSet\Cache\ProductSetCacheKeyGenerator;
use Symfony\Component\Cache\Adapter\TagAwareAdapterInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route(defaults={"_routeScope"={"store-api"}})
*/
class CachedProductSetRoute implements ProductSetRouteInterface
{
private ProductSetRouteInterface $route;
private TagAwareAdapterInterface $cache;
private EntityCacheKeyGenerator $entityCacheKeyGenerator;
private ProductSetCacheKeyGenerator $productSetCacheKeyGenerator;
private AbstractCacheTracer $cacheTracer;
private LoggerInterface $logger;
/** @var string[] */
private array $states;
public function __construct(
ProductSetRouteInterface $route,
TagAwareAdapterInterface $cache,
EntityCacheKeyGenerator $entityCacheKeyGenerator,
ProductSetCacheKeyGenerator $productSetCacheKeyGenerator,
AbstractCacheTracer $cacheTracer,
LoggerInterface $logger
) {
$this->route = $route;
$this->cache = $cache;
$this->entityCacheKeyGenerator = $entityCacheKeyGenerator;
$this->productSetCacheKeyGenerator = $productSetCacheKeyGenerator;
$this->cacheTracer = $cacheTracer;
$this->logger = $logger;
$this->states = [];
}
/**
* @Entity("swkweb_product_set")
*
* @Route("/store-api/swkweb-product-set/{productId}", name="store-api.swkweb-product-set.detail", methods={"POST"})
*/
public function load(string $productId, ?string $path, Request $request, SalesChannelContext $context, Criteria $criteria): ProductSetRouteResponse
{
$name = self::buildName($productId);
if ($context->hasState(...$this->states)) {
$this->logger->info('cache-miss: ' . $name);
return $this->route->load($productId, $path, $request, $context, $criteria);
}
$item = $this->cache->getItem($this->generateKey($productId, $path, $request, $context, $criteria));
try {
if ($item->isHit() && $item->get()) {
$this->logger->info('cache-hit: ' . $name);
return CacheCompressor::uncompress($item);
}
} catch (\Throwable $e) {
$this->logger->error($e->getMessage());
}
$this->logger->info('cache-miss: ' . $name);
$response = $this->cacheTracer->trace($name, fn () => $this->route->load($productId, $path, $request, $context, $criteria));
assert($response instanceof ProductSetRouteResponse);
$item = CacheCompressor::compress($item, $response);
$item->tag($this->generateTags($name, $response));
$this->cache->save($item);
return $response;
}
public static function buildName(string $productId): string
{
return 'swkweb-product-set-route-' . $productId;
}
private function generateKey(string $productId, ?string $path, Request $request, SalesChannelContext $context, Criteria $criteria): string
{
$parts = [
self::buildName($productId),
$path,
$this->productSetCacheKeyGenerator->getRequestHash($request),
$this->entityCacheKeyGenerator->getSalesChannelContextHash($context),
$this->entityCacheKeyGenerator->getCriteriaHash($criteria),
];
return md5(json_encode($parts, \JSON_THROW_ON_ERROR));
}
/**
* @return string[]
*/
private function generateTags(string $name, ProductSetRouteResponse $response): array
{
$tags = [
$name,
...$this->cacheTracer->get($name),
...$this->productSetCacheKeyGenerator->extractTags($response->getProductSet()),
];
return array_unique(array_filter($tags));
}
}