vendor/sensio/framework-extra-bundle/src/EventListener/HttpCacheListener.php line 42

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the Symfony package.
  4.  *
  5.  * (c) Fabien Potencier <fabien@symfony.com>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Sensio\Bundle\FrameworkExtraBundle\EventListener;
  11. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  12. use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
  13. use Symfony\Component\HttpFoundation\Response;
  14. use Symfony\Component\HttpKernel\Event\KernelEvent;
  15. use Symfony\Component\HttpKernel\KernelEvents;
  16. /**
  17.  * HttpCacheListener handles HTTP cache headers.
  18.  *
  19.  * It can be configured via the Cache annotation.
  20.  *
  21.  * @author Fabien Potencier <fabien@symfony.com>
  22.  */
  23. class HttpCacheListener implements EventSubscriberInterface
  24. {
  25.     private $lastModifiedDates;
  26.     private $etags;
  27.     private $expressionLanguage;
  28.     public function __construct()
  29.     {
  30.         $this->lastModifiedDates = new \SplObjectStorage();
  31.         $this->etags = new \SplObjectStorage();
  32.     }
  33.     /**
  34.      * Handles HTTP validation headers.
  35.      */
  36.     public function onKernelController(KernelEvent $event)
  37.     {
  38.         $request $event->getRequest();
  39.         if (!$configuration $request->attributes->get('_cache')) {
  40.             return;
  41.         }
  42.         $response = new Response();
  43.         $lastModifiedDate '';
  44.         if ($configuration->getLastModified()) {
  45.             $lastModifiedDate $this->getExpressionLanguage()->evaluate($configuration->getLastModified(), $request->attributes->all());
  46.             $response->setLastModified($lastModifiedDate);
  47.         }
  48.         $etag '';
  49.         if ($configuration->getEtag()) {
  50.             $etag hash('sha256'$this->getExpressionLanguage()->evaluate($configuration->getEtag(), $request->attributes->all()));
  51.             $response->setEtag($etag);
  52.         }
  53.         if ($response->isNotModified($request)) {
  54.             $event->setController(function () use ($response) {
  55.                 return $response;
  56.             });
  57.             $event->stopPropagation();
  58.         } else {
  59.             if ($etag) {
  60.                 $this->etags[$request] = $etag;
  61.             }
  62.             if ($lastModifiedDate) {
  63.                 $this->lastModifiedDates[$request] = $lastModifiedDate;
  64.             }
  65.         }
  66.     }
  67.     /**
  68.      * Modifies the response to apply HTTP cache headers when needed.
  69.      */
  70.     public function onKernelResponse(KernelEvent $event)
  71.     {
  72.         $request $event->getRequest();
  73.         if (!$configuration $request->attributes->get('_cache')) {
  74.             return;
  75.         }
  76.         $response $event->getResponse();
  77.         // http://tools.ietf.org/html/draft-ietf-httpbis-p4-conditional-12#section-3.1
  78.         if (!\in_array($response->getStatusCode(), [200203300301302304404410])) {
  79.             return;
  80.         }
  81.         if (!$response->headers->hasCacheControlDirective('s-maxage') && null !== $age $configuration->getSMaxAge()) {
  82.             $age $this->convertToSecondsIfNeeded($age);
  83.             $response->setSharedMaxAge($age);
  84.         }
  85.         if ($configuration->mustRevalidate()) {
  86.             $response->headers->addCacheControlDirective('must-revalidate');
  87.         }
  88.         if (!$response->headers->hasCacheControlDirective('max-age') && null !== $age $configuration->getMaxAge()) {
  89.             $age $this->convertToSecondsIfNeeded($age);
  90.             $response->setMaxAge($age);
  91.         }
  92.         if (!$response->headers->hasCacheControlDirective('max-stale') && null !== $stale $configuration->getMaxStale()) {
  93.             $stale $this->convertToSecondsIfNeeded($stale);
  94.             $response->headers->addCacheControlDirective('max-stale'$stale);
  95.         }
  96.         if (!$response->headers->hasCacheControlDirective('stale-while-revalidate') && null !== $staleWhileRevalidate $configuration->getStaleWhileRevalidate()) {
  97.             $staleWhileRevalidate $this->convertToSecondsIfNeeded($staleWhileRevalidate);
  98.             $response->headers->addCacheControlDirective('stale-while-revalidate'$staleWhileRevalidate);
  99.         }
  100.         if (!$response->headers->hasCacheControlDirective('stale-if-error') && null !== $staleIfError $configuration->getStaleIfError()) {
  101.             $staleIfError $this->convertToSecondsIfNeeded($staleIfError);
  102.             $response->headers->addCacheControlDirective('stale-if-error'$staleIfError);
  103.         }
  104.         if (!$response->headers->has('Expires') && null !== $configuration->getExpires()) {
  105.             $date \DateTime::createFromFormat('U'strtotime($configuration->getExpires()), new \DateTimeZone('UTC'));
  106.             $response->setExpires($date);
  107.         }
  108.         if (!$response->headers->has('Vary') && null !== $configuration->getVary()) {
  109.             $response->setVary($configuration->getVary());
  110.         }
  111.         if ($configuration->isPublic()) {
  112.             $response->setPublic();
  113.         }
  114.         if ($configuration->isPrivate()) {
  115.             $response->setPrivate();
  116.         }
  117.         if (!$response->headers->has('Last-Modified') && isset($this->lastModifiedDates[$request])) {
  118.             $response->setLastModified($this->lastModifiedDates[$request]);
  119.             unset($this->lastModifiedDates[$request]);
  120.         }
  121.         if (!$response->headers->has('Etag') && isset($this->etags[$request])) {
  122.             $response->setEtag($this->etags[$request]);
  123.             unset($this->etags[$request]);
  124.         }
  125.     }
  126.     /**
  127.      * @return array
  128.      */
  129.     public static function getSubscribedEvents()
  130.     {
  131.         return [
  132.             KernelEvents::CONTROLLER => 'onKernelController',
  133.             KernelEvents::RESPONSE => 'onKernelResponse',
  134.         ];
  135.     }
  136.     private function getExpressionLanguage()
  137.     {
  138.         if (null === $this->expressionLanguage) {
  139.             if (!class_exists(ExpressionLanguage::class)) {
  140.                 throw new \RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.');
  141.             }
  142.             $this->expressionLanguage = new ExpressionLanguage();
  143.         }
  144.         return $this->expressionLanguage;
  145.     }
  146.     /**
  147.      * @param int|string $time Time that can be either expressed in seconds or with relative time format (1 day, 2 weeks, ...)
  148.      *
  149.      * @return int
  150.      */
  151.     private function convertToSecondsIfNeeded($time)
  152.     {
  153.         if (!is_numeric($time)) {
  154.             $now microtime(true);
  155.             $time ceil(strtotime($time$now) - $now);
  156.         }
  157.         return $time;
  158.     }
  159. }