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

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