vendor/contao/core-bundle/src/EventListener/SubrequestCacheSubscriber.php line 57

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. /*
  4.  * This file is part of Contao.
  5.  *
  6.  * (c) Leo Feyer
  7.  *
  8.  * @license LGPL-3.0-or-later
  9.  */
  10. namespace Contao\CoreBundle\EventListener;
  11. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  12. use Symfony\Component\HttpKernel\Event\RequestEvent;
  13. use Symfony\Component\HttpKernel\Event\ResponseEvent;
  14. use Symfony\Component\HttpKernel\HttpCache\ResponseCacheStrategy;
  15. use Symfony\Component\HttpKernel\KernelEvents;
  16. use Symfony\Component\HttpKernel\KernelInterface;
  17. use Symfony\Contracts\Service\ResetInterface;
  18. /**
  19.  * The Symfony HttpCache ships with a ResponseCacheStrategy, which is used to
  20.  * merge the caching information of multiple ESI subrequests with the main
  21.  * response. It will make sure that the final response has the lowest possible
  22.  * cache time.
  23.  *
  24.  * In Contao, we use the same cache strategy to merge inline fragments into the
  25.  * main page content. This means a fragment like a content element or frontend
  26.  * module can influence the cache time of the page. A user might configure a
  27.  * cache time of 1 day in the page settings, but the news list module might
  28.  * know there is a news item scheduled for publishing in 5 hours (start time),
  29.  * so the page cache time will be set to 5 hours instead.
  30.  *
  31.  * To apply the cache merging, a specific header needs to be present in both
  32.  * the main and subrequest response. The header is automatically set for the
  33.  * page content and classes implementing the abstract content element and
  34.  * module controllers.
  35.  *
  36.  * @internal
  37.  */
  38. class SubrequestCacheSubscriber implements EventSubscriberInterfaceResetInterface
  39. {
  40.     public const MERGE_CACHE_HEADER 'Contao-Merge-Cache-Control';
  41.     /**
  42.      * @var array<ResponseCacheStrategy>
  43.      */
  44.     private $strategyStack = [];
  45.     /**
  46.      * @var ResponseCacheStrategy|null
  47.      */
  48.     private $currentStrategy;
  49.     public function onKernelRequest(RequestEvent $event): void
  50.     {
  51.         if (KernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
  52.             return;
  53.         }
  54.         if ($this->currentStrategy) {
  55.             $this->strategyStack[] = $this->currentStrategy;
  56.         }
  57.         $this->currentStrategy = new ResponseCacheStrategy();
  58.     }
  59.     public function onKernelResponse(ResponseEvent $event): void
  60.     {
  61.         $response $event->getResponse();
  62.         $isMasterRequest KernelInterface::MASTER_REQUEST === $event->getRequestType();
  63.         if ($this->currentStrategy && $response->headers->has(self::MERGE_CACHE_HEADER)) {
  64.             if ($isMasterRequest) {
  65.                 $this->currentStrategy->update($response);
  66.             } elseif ($response->headers->has('Cache-Control')) {
  67.                 $this->currentStrategy->add($response);
  68.             }
  69.         }
  70.         if ($isMasterRequest) {
  71.             $this->currentStrategy array_pop($this->strategyStack);
  72.             $response->headers->remove(self::MERGE_CACHE_HEADER);
  73.         }
  74.     }
  75.     public function reset(): void
  76.     {
  77.         $this->currentStrategy null;
  78.         $this->strategyStack = [];
  79.     }
  80.     public static function getSubscribedEvents(): array
  81.     {
  82.         return [
  83.             KernelEvents::REQUEST => ['onKernelRequest'255],
  84.             KernelEvents::RESPONSE => ['onKernelResponse', -255],
  85.         ];
  86.     }
  87. }