vendor/friendsofsymfony/http-cache-bundle/src/EventListener/CacheControlListener.php line 107

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the FOSHttpCacheBundle package.
  4.  *
  5.  * (c) FriendsOfSymfony <http://friendsofsymfony.github.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 FOS\HttpCacheBundle\EventListener;
  11. use FOS\HttpCacheBundle\Http\RuleMatcherInterface;
  12. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  13. use Symfony\Component\HttpFoundation\Request;
  14. use Symfony\Component\HttpFoundation\Response;
  15. use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
  16. use Symfony\Component\HttpKernel\Event\ResponseEvent;
  17. use Symfony\Component\HttpKernel\Kernel;
  18. use Symfony\Component\HttpKernel\KernelEvents;
  19. if (Kernel::MAJOR_VERSION >= 5) {
  20.     class_alias(ResponseEvent::class, 'FOS\HttpCacheBundle\EventListener\CacheControlResponseEvent');
  21. } else {
  22.     class_alias(FilterResponseEvent::class, 'FOS\HttpCacheBundle\EventListener\CacheControlResponseEvent');
  23. }
  24. /**
  25.  * Set caching settings on matching response according to the configurations.
  26.  *
  27.  * The first matching ruleset is applied.
  28.  *
  29.  * @author Lea Haensenberger <lea.haensenberger@gmail.com>
  30.  * @author David Buchmann <mail@davidbu.ch>
  31.  */
  32. class CacheControlListener implements EventSubscriberInterface
  33. {
  34.     /**
  35.      * Whether to skip this response and not set any cache headers.
  36.      *
  37.      * @var bool
  38.      */
  39.     private $skip false;
  40.     /**
  41.      * Cache control directives directly supported by Response.
  42.      *
  43.      * @var array
  44.      */
  45.     private $supportedDirectives = [
  46.         'max_age' => true,
  47.         's_maxage' => true,
  48.         'private' => true,
  49.         'public' => true,
  50.     ];
  51.     /**
  52.      * @var array List of arrays with RuleMatcherInterface, settings array
  53.      */
  54.     private $rulesMap = [];
  55.     /**
  56.      * If not empty, add a debug header with that name to all responses,
  57.      * telling the cache proxy to add debug output.
  58.      *
  59.      * @var string|bool Name of the header or false to add no header
  60.      */
  61.     private $debugHeader;
  62.     /**
  63.      * @param string|bool $debugHeader Header to set to trigger debugging, or false to send no header
  64.      */
  65.     public function __construct($debugHeader false)
  66.     {
  67.         $this->debugHeader $debugHeader;
  68.     }
  69.     /**
  70.      * {@inheritdoc}
  71.      */
  72.     public static function getSubscribedEvents()
  73.     {
  74.         return [
  75.             KernelEvents::RESPONSE => ['onKernelResponse'10],
  76.         ];
  77.     }
  78.     /**
  79.      * Set whether to skip this response completely.
  80.      *
  81.      * This can be called when other parts of the application took care of all
  82.      * cache headers. No attempt to merge cache headers is made anymore.
  83.      *
  84.      * The debug header is still added if configured.
  85.      *
  86.      * @param bool $skip
  87.      */
  88.     public function setSkip($skip true)
  89.     {
  90.         $this->skip $skip;
  91.     }
  92.     /**
  93.      * Apply the header rules if the request matches.
  94.      */
  95.     public function onKernelResponse(CacheControlResponseEvent $event)
  96.     {
  97.         $request $event->getRequest();
  98.         $response $event->getResponse();
  99.         if ($this->debugHeader) {
  100.             $response->headers->set($this->debugHeader1false);
  101.         }
  102.         // do not change cache directives on non-cacheable requests.
  103.         if ($this->skip || !$request->isMethodCacheable()) {
  104.             return;
  105.         }
  106.         $options $this->matchRule($request$response);
  107.         if (false === $options) {
  108.             return;
  109.         }
  110.         if (!empty($options['cache_control'])) {
  111.             $directives array_intersect_key($options['cache_control'], $this->supportedDirectives);
  112.             $extraDirectives array_diff_key($options['cache_control'], $directives);
  113.             if (!empty($directives)) {
  114.                 $this->setCache($response$directives$options['overwrite']);
  115.             }
  116.             if (!empty($extraDirectives)) {
  117.                 $this->setExtraCacheDirectives($response$extraDirectives$options['overwrite']);
  118.             }
  119.         }
  120.         if (isset($options['reverse_proxy_ttl'])
  121.             && null !== $options['reverse_proxy_ttl']
  122.             && !$response->headers->has('X-Reverse-Proxy-TTL')
  123.         ) {
  124.             $response->headers->set('X-Reverse-Proxy-TTL', (int) $options['reverse_proxy_ttl'], false);
  125.         }
  126.         if (!empty($options['vary'])) {
  127.             $response->setVary($options['vary'], $options['overwrite']);
  128.         }
  129.         if (!empty($options['etag'])
  130.             && ($options['overwrite'] || null === $response->getEtag())
  131.         ) {
  132.             $response->setEtag(md5($response->getContent()), 'weak' === $options['etag']);
  133.         }
  134.         if (isset($options['last_modified'])
  135.             && ($options['overwrite'] || null === $response->getLastModified())
  136.         ) {
  137.             $response->setLastModified(new \DateTime($options['last_modified']));
  138.         }
  139.     }
  140.     /**
  141.      * Add a rule matcher with a list of header directives to apply if the
  142.      * request and response are matched.
  143.      *
  144.      * @param RuleMatcherInterface $ruleMatcher The headers apply to request and response matched by this matcher
  145.      * @param array                $settings    An array of header configuration
  146.      */
  147.     public function addRule(
  148.         RuleMatcherInterface $ruleMatcher,
  149.         array $settings = []
  150.     ) {
  151.         $this->rulesMap[] = [$ruleMatcher$settings];
  152.     }
  153.     /**
  154.      * Return the settings for the current request if any rule matches.
  155.      *
  156.      * @return array|false Settings to apply or false if no rule matched
  157.      */
  158.     private function matchRule(Request $requestResponse $response)
  159.     {
  160.         foreach ($this->rulesMap as $elements) {
  161.             if ($elements[0]->matches($request$response)) {
  162.                 return $elements[1];
  163.             }
  164.         }
  165.         return false;
  166.     }
  167.     /**
  168.      * Set cache headers on response.
  169.      *
  170.      * @param bool $overwrite Whether to keep existing cache headers or to overwrite them
  171.      */
  172.     private function setCache(Response $response, array $directives$overwrite)
  173.     {
  174.         if ($overwrite) {
  175.             $response->setCache($directives);
  176.             return;
  177.         }
  178.         if (false !== strpos($response->headers->get('Cache-Control'''), 'no-cache')) {
  179.             // this single header is set by default. if its the only thing, we override it.
  180.             $response->setCache($directives);
  181.             return;
  182.         }
  183.         foreach (array_keys($this->supportedDirectives) as $key) {
  184.             $directive str_replace('_''-'$key);
  185.             if ($response->headers->hasCacheControlDirective($directive)) {
  186.                 $directives[$key] = $response->headers->getCacheControlDirective($directive);
  187.             }
  188.             if ('public' === $directive && $response->headers->hasCacheControlDirective('private')
  189.                 || 'private' === $directive && $response->headers->hasCacheControlDirective('public')
  190.             ) {
  191.                 unset($directives[$key]);
  192.             }
  193.         }
  194.         $response->setCache($directives);
  195.     }
  196.     /**
  197.      * Add extra cache control directives on response.
  198.      *
  199.      * @param bool $overwrite Whether to keep existing cache headers or to overwrite them
  200.      */
  201.     private function setExtraCacheDirectives(Response $response, array $controls$overwrite)
  202.     {
  203.         $flags = ['must_revalidate''proxy_revalidate''no_transform''no_cache''no_store'];
  204.         $options = ['stale_if_error''stale_while_revalidate'];
  205.         foreach ($flags as $key) {
  206.             $flag str_replace('_''-'$key);
  207.             if (!empty($controls[$key])
  208.                 && ($overwrite || !$response->headers->hasCacheControlDirective($flag))
  209.             ) {
  210.                 $response->headers->addCacheControlDirective($flag);
  211.             }
  212.         }
  213.         foreach ($options as $key) {
  214.             $option str_replace('_''-'$key);
  215.             if (isset($controls[$key])
  216.                 && ($overwrite || !$response->headers->hasCacheControlDirective($option))
  217.             ) {
  218.                 $response->headers->addCacheControlDirective($option$controls[$key]);
  219.             }
  220.         }
  221.     }
  222. }