<?php
namespace App\EventSubscriber;
use App\Entity\Page;
use App\Repository\PageRepository;
use App\Service\CacheService;
use DateTime;
use Doctrine\ORM\NonUniqueResultException;
use Exception;
use Psr\Cache\CacheException;
use Psr\Cache\InvalidArgumentException;
use Symfony\Component\Cache\Adapter\TagAwareAdapter;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use function dd;
class CacheSubscriber implements EventSubscriberInterface
{
protected TagAwareAdapter $cache;
/**
* CacheSubscriber constructor.
*/
public function __construct(
protected string $env,
protected CacheService $cacheService,
protected PageRepository $pageRepository,
protected RequestStack $requestStack,
protected TokenStorageInterface $tokenStorage,
private string $masterLocale
) {
$this->cache = $this->cacheService->getAdapter();
}
/**
* @throws InvalidArgumentException
* @throws NonUniqueResultException
*/
public function onKernelRequest(RequestEvent $event): void
{
$request = $event->getRequest();
if (1 == $event->getRequestType() && $this->shouldUseCache($request)) {
$routeParams = $request->attributes->get('_route_params');
if (isset($routeParams['path'])) {
$cachingLevel = $this->pageRepository->getCachingLevelByPath($routeParams['path'], $routeParams['_locale'] ?? null);
$cacheKey = null;
$url = isset($routeParams['_locale']) ? $routeParams['_locale'] . '/' . $routeParams['path'] : $routeParams['path'];
if ($url == "") {
$url = "home";
}
switch ($cachingLevel['cachingLevel']) {
case Page::CACHING_BASIC:
if (!$request->query->count()) {
$cacheKey = $this->cacheService->getCacheKey($url);
}
break;
case Page::CACHING_SIMPLE:
$cacheKey = $this->cacheService->getCacheKey($url);
break;
case Page::CACHING_AGGRESSIVE:
$cacheKey = $this->cacheService->getCacheKey($url, $request->query->all());
break;
}
if ($cacheKey) {
$data = $this->cache->getItem($cacheKey);
if ($data->isHit()) {
$response = new Response($data->get());
$response->headers->set('cached-data', ['true']);
$event->setResponse($response);
}
}
}
}
}
/**
* @throws CacheException
* @throws InvalidArgumentException
* @throws NonUniqueResultException
*/
public function onKernelResponse(ResponseEvent $event): void
{
$request = $event->getRequest();
$response = $event->getResponse();
if (1 == $event->getRequestType() && Response::HTTP_OK == $response->getStatusCode() && $this->shouldUseCache($request) && !$response->headers->has('cached-data')) {
$routeParams = $request->attributes->get('_route_params');
if (isset($routeParams['path'])) {
/** @var Page $page */
$page = $this->pageRepository->findOneByPath($routeParams['path'], $routeParams['_locale'] ?? null);
if (null !== $page && $page->isVisible() && $page->canBeSeen()) {
switch ($page->getCachingLevel()) {
case Page::CACHING_BASIC:
if (!$request->query->count()) {
$response = $this->cachePage($response, $page);
}
break;
case Page::CACHING_SIMPLE:
$response = $this->cachePage($response, $page);
break;
case Page::CACHING_AGGRESSIVE:
$response = $this->cachePage($response, $page, $request->query->all());
break;
}
}
}
$event->setResponse($response);
}
}
/**
* @throws CacheException
* @throws InvalidArgumentException
* @throws Exception
*/
public function cachePage(Response $response, Page $page, array $queryParams = []): Response
{
if ($page->getCurrentPath(false)->getLocale() == $this->masterLocale) {
$cacheKey = $this->cacheService->getCacheKey($page->getCurrentPath(true) == "" ? "home" : $page->getCurrentPath(true), $queryParams);
} else {
$cacheKey = $this->cacheService->getCacheKey($page->getCurrentPath(false)->getLocale() . '/' . $page->getCurrentPath(true), $queryParams);
}
$data = $this->cache->getItem($cacheKey);
$view = $response->getContent();
$ttl = $this->cacheService->handleTtl();
$data->expiresAfter($ttl);
$data->set($view);
$data->tag($this->cacheService->getTags($page));
$this->cache->save($data);
$response->setCache([
'etag' => md5($cacheKey),
'last_modified' => new DateTime(),
'max_age' => $ttl,
's_maxage' => $ttl,
'public' => true,
]);
return $response;
}
protected function shouldUseCache(Request $request): bool
{
return
(!$this->tokenStorage->getToken() || !$this->tokenStorage->getToken()->getUser() || 'anon.' == $this->tokenStorage->getToken()->getUser())
&& 'GET' == $request->getMethod()
&& $this->cacheService->isEnabled()
&& 'prod' == strtolower($this->env);
}
public static function getSubscribedEvents(): array
{
return [
KernelEvents::REQUEST => [['onKernelRequest', 1]],
KernelEvents::RESPONSE => [['onKernelResponse', 2]],
];
}
}