Code Coverage  | 
     ||||||||||
Classes and Traits  | 
      Functions and Methods  | 
      Lines  | 
     ||||||||
| Total |         | 
      0.00%  | 
      0 / 1  | 
              | 
      0.00%  | 
      0 / 7  | 
      CRAP |         | 
      0.00%  | 
      0 / 71  | 
     
| DynamicPageCacheSubscriber |         | 
      0.00%  | 
      0 / 1  | 
              | 
      0.00%  | 
      0 / 7  | 
      380 |         | 
      0.00%  | 
      0 / 71  | 
     
| __construct |         | 
      0.00%  | 
      0 / 1  | 
      2 |         | 
      0.00%  | 
      0 / 5  | 
     |||
| onRouteMatch |         | 
      0.00%  | 
      0 / 1  | 
      12 |         | 
      0.00%  | 
      0 / 13  | 
     |||
| onResponse |         | 
      0.00%  | 
      0 / 1  | 
      56 |         | 
      0.00%  | 
      0 / 22  | 
     |||
| shouldCacheResponse |         | 
      0.00%  | 
      0 / 1  | 
      30 |         | 
      0.00%  | 
      0 / 13  | 
     |||
| responseToRenderArray |         | 
      0.00%  | 
      0 / 1  | 
      2 |         | 
      0.00%  | 
      0 / 11  | 
     |||
| renderArrayToResponse |         | 
      0.00%  | 
      0 / 1  | 
      2 |         | 
      0.00%  | 
      0 / 2  | 
     |||
| getSubscribedEvents |         | 
      0.00%  | 
      0 / 1  | 
      2 |         | 
      0.00%  | 
      0 / 5  | 
     |||
| <?php | |
| /** | |
| * @file | |
| * Contains \Drupal\dynamic_page_cache\EventSubscriber\DynamicPageCacheSubscriber. | |
| */ | |
| namespace Drupal\dynamic_page_cache\EventSubscriber; | |
| use Drupal\Core\Cache\Cache; | |
| use Drupal\Core\Cache\CacheableMetadata; | |
| use Drupal\Core\Cache\CacheableResponseInterface; | |
| use Drupal\Core\PageCache\RequestPolicyInterface; | |
| use Drupal\Core\PageCache\ResponsePolicyInterface; | |
| use Drupal\Core\Render\Element; | |
| use Drupal\Core\Render\RenderCacheInterface; | |
| use Symfony\Component\EventDispatcher\EventSubscriberInterface; | |
| use Symfony\Component\HttpKernel\Event\FilterResponseEvent; | |
| use Symfony\Component\HttpKernel\Event\GetResponseEvent; | |
| use Symfony\Component\HttpKernel\KernelEvents; | |
| /** | |
| * Returns cached responses as early and avoiding as much work as possible. | |
| * | |
| * Dynamic Page Cache is able to cache so much because it utilizes cache | |
| * contexts: the cache contexts that are present capture the variations of every | |
| * component of the page. That, combined with the fact that cacheability | |
| * metadata is bubbled, means that the cache contexts at the page level | |
| * represent the complete set of contexts that the page varies by. | |
| * | |
| * The reason Dynamic Page Cache is implemented as two event subscribers (a late | |
| * REQUEST subscriber immediately after routing for cache hits, and an early | |
| * RESPONSE subscriber for cache misses) is because many cache contexts can only | |
| * be evaluated after routing. (Examples: 'user', 'user.permissions', 'route' …) | |
| * Consequently, it is impossible to implement Dynamic Page Cache as a kernel | |
| * middleware that simply caches per URL. | |
| * | |
| * @see \Drupal\Core\Render\MainContent\HtmlRenderer | |
| * @see \Drupal\Core\Cache\CacheableResponseInterface | |
| */ | |
| class DynamicPageCacheSubscriber implements EventSubscriberInterface { | |
| /** | |
| * Attribute name of the Dynamic Page Cache request policy result. | |
| * | |
| * @see onRouteMatch() | |
| * @see onRespond() | |
| */ | |
| const ATTRIBUTE_REQUEST_POLICY_RESULT = '_dynamic_page_cache_request_policy_result'; | |
| /** | |
| * Name of Dynamic Page Cache's response header. | |
| */ | |
| const HEADER = 'X-Drupal-Dynamic-Cache'; | |
| /** | |
| * A request policy rule determining the cacheability of a response. | |
| * | |
| * @var \Drupal\Core\PageCache\RequestPolicyInterface | |
| */ | |
| protected $requestPolicy; | |
| /** | |
| * A response policy rule determining the cacheability of the response. | |
| * | |
| * @var \Drupal\Core\PageCache\ResponsePolicyInterface | |
| */ | |
| protected $responsePolicy; | |
| /** | |
| * The render cache. | |
| * | |
| * @var \Drupal\Core\Render\RenderCacheInterface | |
| */ | |
| protected $renderCache; | |
| /** | |
| * The renderer configuration array. | |
| * | |
| * @var array | |
| */ | |
| protected $rendererConfig; | |
| /** | |
| * Dynamic Page Cache's redirect render array. | |
| * | |
| * @var array | |
| */ | |
| protected $dynamicPageCacheRedirectRenderArray = [ | |
| '#cache' => [ | |
| 'keys' => ['response'], | |
| 'contexts' => [ | |
| 'route', | |
| // Some routes' controllers rely on the request format (they don't have | |
| // a separate route for each request format). Additionally, a controller | |
| // may be returning a domain object that a KernelEvents::VIEW subscriber | |
| // must turn into an actual response, but perhaps a format is being | |
| // requested that the subscriber does not support. | |
| // @see \Drupal\Core\EventSubscriber\AcceptNegotiation406::onViewDetect406() | |
| 'request_format', | |
| ], | |
| 'bin' => 'dynamic_page_cache', | |
| ], | |
| ]; | |
| /** | |
| * Constructs a new DynamicPageCacheSubscriber object. | |
| * | |
| * @param \Drupal\Core\PageCache\RequestPolicyInterface $request_policy | |
| * A policy rule determining the cacheability of a request. | |
| * @param \Drupal\Core\PageCache\ResponsePolicyInterface $response_policy | |
| * A policy rule determining the cacheability of the response. | |
| * @param \Drupal\Core\Render\RenderCacheInterface $render_cache | |
| * The render cache. | |
| * @param array $renderer_config | |
| * The renderer configuration array. | |
| */ | |
| public function __construct(RequestPolicyInterface $request_policy, ResponsePolicyInterface $response_policy, RenderCacheInterface $render_cache, array $renderer_config) { | |
| $this->requestPolicy = $request_policy; | |
| $this->responsePolicy = $response_policy; | |
| $this->renderCache = $render_cache; | |
| $this->rendererConfig = $renderer_config; | |
| } | |
| /** | |
| * Sets a response in case of a Dynamic Page Cache hit. | |
| * | |
| * @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event | |
| * The event to process. | |
| */ | |
| public function onRouteMatch(GetResponseEvent $event) { | |
| // Don't cache the response if the Dynamic Page Cache request policies are | |
| // not met. Store the result in a request attribute, so that onResponse() | |
| // does not have to redo the request policy check. | |
| $request = $event->getRequest(); | |
| $request_policy_result = $this->requestPolicy->check($request); | |
| $request->attributes->set(self::ATTRIBUTE_REQUEST_POLICY_RESULT, $request_policy_result); | |
| if ($request_policy_result === RequestPolicyInterface::DENY) { | |
| return; | |
| } | |
| // Sets the response for the current route, if cached. | |
| $cached = $this->renderCache->get($this->dynamicPageCacheRedirectRenderArray); | |
| if ($cached) { | |
| $response = $this->renderArrayToResponse($cached); | |
| $response->headers->set(self::HEADER, 'HIT'); | |
| $event->setResponse($response); | |
| } | |
| } | |
| /** | |
| * Stores a response in case of a Dynamic Page Cache miss, if cacheable. | |
| * | |
| * @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event | |
| * The event to process. | |
| */ | |
| public function onResponse(FilterResponseEvent $event) { | |
| $response = $event->getResponse(); | |
| // Dynamic Page Cache only works with cacheable responses. It does not work | |
| // with plain Response objects. (Dynamic Page Cache needs to be able to | |
| // access and modify the cacheability metadata associated with the | |
| // response.) | |
| if (!$response instanceof CacheableResponseInterface) { | |
| return; | |
| } | |
| // There's no work left to be done if this is a Dynamic Page Cache hit. | |
| if ($response->headers->get(self::HEADER) === 'HIT') { | |
| return; | |
| } | |
| // There's no work left to be done if this is an uncacheable response. | |
| if (!$this->shouldCacheResponse($response)) { | |
| // The response is uncacheable, mark it as such. | |
| $response->headers->set(self::HEADER, 'UNCACHEABLE'); | |
| return; | |
| } | |
| // Don't cache the response if Dynamic Page Cache's request subscriber did | |
| // not fire, because that means it is impossible to have a Dynamic Page | |
| // Cache hit. (This can happen when the master request is for example a 403 | |
| // or 404, in which case a subrequest is performed by the router. In that | |
| // case, it is the subrequest's response that is cached by Dynamic Page | |
| // Cache, because the routing happens in a request subscriber earlier than | |
| // Dynamic Page Cache's and immediately sets a response, i.e. the one | |
| // returned by the subrequest, and thus causes Dynamic Page Cache's request | |
| // subscriber to not fire for the master request.) | |
| // @see \Drupal\Core\Routing\AccessAwareRouter::checkAccess() | |
| // @see \Drupal\Core\EventSubscriber\DefaultExceptionHtmlSubscriber::on403() | |
| $request = $event->getRequest(); | |
| if (!$request->attributes->has(self::ATTRIBUTE_REQUEST_POLICY_RESULT)) { | |
| return; | |
| } | |
| // Don't cache the response if the Dynamic Page Cache request & response | |
| // policies are not met. | |
| // @see onRouteMatch() | |
| if ($request->attributes->get(self::ATTRIBUTE_REQUEST_POLICY_RESULT) === RequestPolicyInterface::DENY || $this->responsePolicy->check($response, $request) === ResponsePolicyInterface::DENY) { | |
| return; | |
| } | |
| // Embed the response object in a render array so that RenderCache is able | |
| // to cache it, handling cache redirection for us. | |
| $response_as_render_array = $this->responseToRenderArray($response); | |
| $this->renderCache->set($response_as_render_array, $this->dynamicPageCacheRedirectRenderArray); | |
| // The response was generated, mark the response as a cache miss. The next | |
| // time, it will be a cache hit. | |
| $response->headers->set(self::HEADER, 'MISS'); | |
| } | |
| /** | |
| * Whether the given response should be cached by Dynamic Page Cache. | |
| * | |
| * We consider any response that has cacheability metadata meeting the auto- | |
| * placeholdering conditions to be uncacheable. Because those conditions | |
| * indicate poor cacheability, and if it doesn't make sense to cache parts of | |
| * a page, then neither does it make sense to cache an entire page. | |
| * | |
| * But note that auto-placeholdering avoids such cacheability metadata ever | |
| * bubbling to the response level: while rendering, the Renderer checks every | |
| * subtree to see if meets the auto-placeholdering conditions. If it does, it | |
| * is automatically placeholdered, and consequently the cacheability metadata | |
| * of the placeholdered content does not bubble up to the response level. | |
| * | |
| * @param \Drupal\Core\Cache\CacheableResponseInterface | |
| * The response whose cacheability to analyze. | |
| * | |
| * @return bool | |
| * Whether the given response should be cached. | |
| * | |
| * @see \Drupal\Core\Render\Renderer::shouldAutomaticallyPlaceholder() | |
| */ | |
| protected function shouldCacheResponse(CacheableResponseInterface $response) { | |
| $conditions = $this->rendererConfig['auto_placeholder_conditions']; | |
| $cacheability = $response->getCacheableMetadata(); | |
| // Response's max-age is at or below the configured threshold. | |
| if ($cacheability->getCacheMaxAge() !== Cache::PERMANENT && $cacheability->getCacheMaxAge() <= $conditions['max-age']) { | |
| return FALSE; | |
| } | |
| // Response has a high-cardinality cache context. | |
| if (array_intersect($cacheability->getCacheContexts(), $conditions['contexts'])) { | |
| return FALSE; | |
| } | |
| // Response has a high-invalidation frequency cache tag. | |
| if (array_intersect($cacheability->getCacheTags(), $conditions['tags'])) { | |
| return FALSE; | |
| } | |
| return TRUE; | |
| } | |
| /** | |
| * Embeds a Response object in a render array so that RenderCache can cache it. | |
| * | |
| * @param \Drupal\Core\Cache\CacheableResponseInterface $response | |
| * A cacheable response. | |
| * | |
| * @return array | |
| * A render array that embeds the given cacheable response object, with the | |
| * cacheability metadata of the response object present in the #cache | |
| * property of the render array. | |
| * | |
| * @see renderArrayToResponse() | |
| * | |
| * @todo Refactor/remove once https://www.drupal.org/node/2551419 lands. | |
| */ | |
| protected function responseToRenderArray(CacheableResponseInterface $response) { | |
| $response_as_render_array = $this->dynamicPageCacheRedirectRenderArray + [ | |
| // The data we actually care about. | |
| '#response' => $response, | |
| // Tell RenderCache to cache the #response property: the data we actually | |
| // care about. | |
| '#cache_properties' => ['#response'], | |
| // These exist only to fulfill the requirements of the RenderCache, which | |
| // is designed to work with render arrays only. We don't care about these. | |
| '#markup' => '', | |
| '#attached' => '', | |
| ]; | |
| // Merge the response's cacheability metadata, so that RenderCache can take | |
| // care of cache redirects for us. | |
| CacheableMetadata::createFromObject($response->getCacheableMetadata()) | |
| ->merge(CacheableMetadata::createFromRenderArray($response_as_render_array)) | |
| ->applyTo($response_as_render_array); | |
| return $response_as_render_array; | |
| } | |
| /** | |
| * Gets the embedded Response object in a render array. | |
| * | |
| * @param array $render_array | |
| * A render array with a #response property. | |
| * | |
| * @return \Drupal\Core\Cache\CacheableResponseInterface | |
| * The cacheable response object. | |
| * | |
| * @see responseToRenderArray() | |
| */ | |
| protected function renderArrayToResponse(array $render_array) { | |
| return $render_array['#response']; | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public static function getSubscribedEvents() { | |
| $events = []; | |
| // Run after AuthenticationSubscriber (necessary for the 'user' cache | |
| // context; priority 300) and MaintenanceModeSubscriber (Dynamic Page Cache | |
| // should not be polluted by maintenance mode-specific behavior; priority | |
| // 30), but before ContentControllerSubscriber (updates _controller, but | |
| // that is a no-op when Dynamic Page Cache runs; priority 25). | |
| $events[KernelEvents::REQUEST][] = ['onRouteMatch', 27]; | |
| // Run before HtmlResponseSubscriber::onRespond(), which has priority 0. | |
| $events[KernelEvents::RESPONSE][] = ['onResponse', 100]; | |
| return $events; | |
| } | |
| } |