Move into nested docroot
This commit is contained in:
parent
83a0d3a149
commit
c8b70abde9
13405 changed files with 0 additions and 0 deletions
web/core/modules/dynamic_page_cache
dynamic_page_cache.info.ymldynamic_page_cache.moduledynamic_page_cache.services.yml
src
EventSubscriber
PageCache
Tests
tests/dynamic_page_cache_test
|
@ -0,0 +1,6 @@
|
|||
name: Internal Dynamic Page Cache
|
||||
type: module
|
||||
description: 'Caches pages for any user, handling dynamic content correctly.'
|
||||
package: Core
|
||||
version: VERSION
|
||||
core: 8.x
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Caches HTML responses, request and response policies allowing.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
*/
|
||||
function dynamic_page_cache_help($route_name, RouteMatchInterface $route_match) {
|
||||
switch ($route_name) {
|
||||
case 'help.page.dynamic_page_cache':
|
||||
$output = '<h3>' . t('About') . '</h3>';
|
||||
$output .= '<p>' . t('The Internal Dynamic Page Cache module caches pages for all users in the database, handling dynamic content correctly. For more information, see the <a href=":dynamic_page_cache-documentation">online documentation for the Internal Dynamic Page Cache module</a>.', [':dynamic_page_cache-documentation' => 'https://www.drupal.org/documentation/modules/dynamic_page_cache']) . '</p>';
|
||||
$output .= '<h3>' . t('Uses') . '</h3>';
|
||||
$output .= '<dl>';
|
||||
$output .= '<dt>' . t('Speeding up your site') . '</dt>';
|
||||
$output .= '<dd>' . t('Pages which are suitable for caching are cached the first time they are requested, then the cached version is served for all later requests. Dynamic content is handled automatically so that both cache correctness and hit ratio is maintained.') . '</dd>';
|
||||
$output .= '<dd>' . t('The module requires no configuration. Every part of the page contains metadata that allows Internal Dynamic Page Cache to figure this out on its own.') . '</dd>';
|
||||
$output .= '</dl>';
|
||||
|
||||
return $output;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
services:
|
||||
cache.dynamic_page_cache:
|
||||
class: Drupal\Core\Cache\CacheBackendInterface
|
||||
tags:
|
||||
- { name: cache.bin }
|
||||
factory: cache_factory:get
|
||||
arguments: [dynamic_page_cache]
|
||||
dynamic_page_cache_subscriber:
|
||||
class: Drupal\dynamic_page_cache\EventSubscriber\DynamicPageCacheSubscriber
|
||||
arguments: ['@dynamic_page_cache_request_policy', '@dynamic_page_cache_response_policy', '@render_cache', '%renderer.config%']
|
||||
tags:
|
||||
- { name: event_subscriber }
|
||||
|
||||
# Request & response policies.
|
||||
dynamic_page_cache_request_policy:
|
||||
class: Drupal\dynamic_page_cache\PageCache\RequestPolicy\DefaultRequestPolicy
|
||||
tags:
|
||||
- { name: service_collector, tag: dynamic_page_cache_request_policy, call: addPolicy}
|
||||
dynamic_page_cache_response_policy:
|
||||
class: Drupal\Core\PageCache\ChainResponsePolicy
|
||||
tags:
|
||||
- { name: service_collector, tag: dynamic_page_cache_response_policy, call: addPolicy}
|
||||
lazy: true
|
||||
dynamic_page_cache_deny_admin_routes:
|
||||
class: Drupal\dynamic_page_cache\PageCache\ResponsePolicy\DenyAdminRoutes
|
||||
arguments: ['@current_route_match']
|
||||
public: false
|
||||
tags:
|
||||
- { name: dynamic_page_cache_response_policy }
|
|
@ -0,0 +1,323 @@
|
|||
<?php
|
||||
|
||||
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\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 {
|
||||
|
||||
/**
|
||||
* 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',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Internal cache of request policy results.
|
||||
*
|
||||
* @var \SplObjectStorage
|
||||
*/
|
||||
protected $requestPolicyResults;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
$this->requestPolicyResults = new \SplObjectStorage();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 static keyed by current request, so that
|
||||
// onResponse() does not have to redo the request policy check.
|
||||
$request = $event->getRequest();
|
||||
$request_policy_result = $this->requestPolicy->check($request);
|
||||
$this->requestPolicyResults[$request] = $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 (!isset($this->requestPolicyResults[$request])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't cache the response if the Dynamic Page Cache request & response
|
||||
// policies are not met.
|
||||
// @see onRouteMatch()
|
||||
if ($this->requestPolicyResults[$request] === 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 $response
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\dynamic_page_cache\PageCache\RequestPolicy;
|
||||
|
||||
use Drupal\Core\PageCache\ChainRequestPolicy;
|
||||
use Drupal\Core\PageCache\RequestPolicy\CommandLineOrUnsafeMethod;
|
||||
|
||||
/**
|
||||
* The default Dynamic Page Cache request policy.
|
||||
*
|
||||
* Delivery of cached pages is denied if either the application is running from
|
||||
* the command line or the request was not initiated with a safe method (GET or
|
||||
* HEAD).
|
||||
*/
|
||||
class DefaultRequestPolicy extends ChainRequestPolicy {
|
||||
|
||||
/**
|
||||
* Constructs the default Dynamic Page Cache request policy.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->addPolicy(new CommandLineOrUnsafeMethod());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\dynamic_page_cache\PageCache\ResponsePolicy;
|
||||
|
||||
use Drupal\Core\PageCache\ResponsePolicyInterface;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* Cache policy for routes with the '_admin_route' option set.
|
||||
*
|
||||
* This policy rule denies caching of responses generated for admin routes,
|
||||
* because admin routes have very low cache hit ratios due to low traffic and
|
||||
* form submissions.
|
||||
*/
|
||||
class DenyAdminRoutes implements ResponsePolicyInterface {
|
||||
|
||||
/**
|
||||
* The current route match.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\RouteMatchInterface
|
||||
*/
|
||||
protected $routeMatch;
|
||||
|
||||
/**
|
||||
* Constructs a deny admin route page cache policy.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
|
||||
* The current route match.
|
||||
*/
|
||||
public function __construct(RouteMatchInterface $route_match) {
|
||||
$this->routeMatch = $route_match;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function check(Response $response, Request $request) {
|
||||
if (($route = $this->routeMatch->getRouteObject()) && $route->getOption('_admin_route')) {
|
||||
return static::DENY;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\dynamic_page_cache\Tests;
|
||||
|
||||
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\dynamic_page_cache\EventSubscriber\DynamicPageCacheSubscriber;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Enables the Dynamic Page Cache and tests it in various scenarios.
|
||||
*
|
||||
* This does not test the self-healing of the redirect with conditional cache
|
||||
* contexts, because Dynamic Page Cache just reuses
|
||||
* \Drupal\Core\Render\RenderCache so that it doesn't have to implement and test
|
||||
* all of that again. It is tested in
|
||||
* RendererBubblingTest::testConditionalCacheContextBubblingSelfHealing().
|
||||
*
|
||||
* @group dynamic_page_cache
|
||||
*
|
||||
* @see \Drupal\dynamic_page_cache\EventSubscriber\DynamicPageCacheSubscriber
|
||||
*/
|
||||
class DynamicPageCacheIntegrationTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $dumpHeaders = TRUE;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['dynamic_page_cache_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Uninstall the page_cache module; we want to test the Dynamic Page Cache
|
||||
// alone.
|
||||
\Drupal::service('module_installer')->uninstall(['page_cache']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that Dynamic Page Cache works correctly, and verifies the edge cases.
|
||||
*/
|
||||
public function testDynamicPageCache() {
|
||||
// Controllers returning plain response objects are ignored by Dynamic Page
|
||||
// Cache.
|
||||
$url = Url::fromUri('route:dynamic_page_cache_test.response');
|
||||
$this->drupalGet($url);
|
||||
$this->assertFalse($this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Response object returned: Dynamic Page Cache is ignoring.');
|
||||
|
||||
// Controllers returning CacheableResponseInterface (cacheable response)
|
||||
// objects are handled by Dynamic Page Cache.
|
||||
$url = Url::fromUri('route:dynamic_page_cache_test.cacheable_response');
|
||||
$this->drupalGet($url);
|
||||
$this->assertEqual('MISS', $this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Cacheable response object returned: Dynamic Page Cache is active, Dynamic Page Cache MISS.');
|
||||
$this->drupalGet($url);
|
||||
$this->assertEqual('HIT', $this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Cacheable response object returned: Dynamic Page Cache is active, Dynamic Page Cache HIT.');
|
||||
|
||||
// Controllers returning render arrays, rendered as HTML responses, are
|
||||
// handled by Dynamic Page Cache.
|
||||
$url = Url::fromUri('route:dynamic_page_cache_test.html');
|
||||
$this->drupalGet($url);
|
||||
$this->assertEqual('MISS', $this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Render array returned, rendered as HTML response: Dynamic Page Cache is active, Dynamic Page Cache MISS.');
|
||||
$this->drupalGet($url);
|
||||
$this->assertEqual('HIT', $this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Render array returned, rendered as HTML response: Dynamic Page Cache is active, Dynamic Page Cache HIT.');
|
||||
|
||||
// The above is the simple case, where the render array returned by the
|
||||
// response contains no cache contexts. So let's now test a route/controller
|
||||
// that *does* vary by a cache context whose value we can easily control: it
|
||||
// varies by the 'animal' query argument.
|
||||
foreach (['llama', 'piggy', 'unicorn', 'kitten'] as $animal) {
|
||||
$url = Url::fromUri('route:dynamic_page_cache_test.html.with_cache_contexts', ['query' => ['animal' => $animal]]);
|
||||
$this->drupalGet($url);
|
||||
$this->assertRaw($animal);
|
||||
$this->assertEqual('MISS', $this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Render array returned, rendered as HTML response: Dynamic Page Cache is active, Dynamic Page Cache MISS.');
|
||||
$this->drupalGet($url);
|
||||
$this->assertRaw($animal);
|
||||
$this->assertEqual('HIT', $this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Render array returned, rendered as HTML response: Dynamic Page Cache is active, Dynamic Page Cache HIT.');
|
||||
|
||||
// Finally, let's also verify that the 'dynamic_page_cache_test.html'
|
||||
// route continued to see cache hits if we specify a query argument,
|
||||
// because it *should* ignore it and continue to provide Dynamic Page
|
||||
// Cache hits.
|
||||
$url = Url::fromUri('route:dynamic_page_cache_test.html', ['query' => ['animal' => 'piglet']]);
|
||||
$this->drupalGet($url);
|
||||
$this->assertEqual('HIT', $this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Render array returned, rendered as HTML response: Dynamic Page Cache is active, Dynamic Page Cache HIT.');
|
||||
}
|
||||
|
||||
// Controllers returning render arrays, rendered as anything except a HTML
|
||||
// response, are ignored by Dynamic Page Cache (but only because those
|
||||
// wrapper formats' responses do not implement CacheableResponseInterface).
|
||||
$this->drupalGet('dynamic-page-cache-test/html', array('query' => array(MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_ajax')));
|
||||
$this->assertFalse($this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Render array returned, rendered as AJAX response: Dynamic Page Cache is ignoring.');
|
||||
$this->drupalGet('dynamic-page-cache-test/html', array('query' => array(MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_dialog')));
|
||||
$this->assertFalse($this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Render array returned, rendered as dialog response: Dynamic Page Cache is ignoring.');
|
||||
$this->drupalGet('dynamic-page-cache-test/html', array('query' => array(MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_modal')));
|
||||
$this->assertFalse($this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Render array returned, rendered as modal response: Dynamic Page Cache is ignoring.');
|
||||
|
||||
// Admin routes are ignored by Dynamic Page Cache.
|
||||
$this->drupalGet('dynamic-page-cache-test/html/admin');
|
||||
$this->assertFalse($this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Response returned, rendered as HTML response, admin route: Dynamic Page Cache is ignoring');
|
||||
$this->drupalGet('dynamic-page-cache-test/response/admin');
|
||||
$this->assertFalse($this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Response returned, plain response, admin route: Dynamic Page Cache is ignoring');
|
||||
$this->drupalGet('dynamic-page-cache-test/cacheable-response/admin');
|
||||
$this->assertFalse($this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Response returned, cacheable response, admin route: Dynamic Page Cache is ignoring');
|
||||
|
||||
// Max-age = 0 responses are ignored by Dynamic Page Cache.
|
||||
$this->drupalGet('dynamic-page-cache-test/html/uncacheable/max-age');
|
||||
$this->assertEqual('UNCACHEABLE', $this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Render array returned, rendered as HTML response, but uncacheable: Dynamic Page Cache is running, but not caching.');
|
||||
|
||||
// 'user' cache context responses are ignored by Dynamic Page Cache.
|
||||
$this->drupalGet('dynamic-page-cache-test/html/uncacheable/contexts');
|
||||
$this->assertEqual('UNCACHEABLE', $this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Render array returned, rendered as HTML response, but uncacheable: Dynamic Page Cache is running, but not caching.');
|
||||
|
||||
// 'current-temperature' cache tag responses are ignored by Dynamic Page
|
||||
// Cache.
|
||||
$this->drupalGet('dynamic-page-cache-test/html/uncacheable/tags');
|
||||
$this->assertEqual('MISS', $this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'By default, Drupal has no auto-placeholdering cache tags.');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
name: 'Test Dynamic Page Cache'
|
||||
type: module
|
||||
description: 'Provides test routes/responses for Dynamic Page Cache.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
core: 8.x
|
|
@ -0,0 +1,75 @@
|
|||
dynamic_page_cache_test.response:
|
||||
path: '/dynamic-page-cache-test/response'
|
||||
defaults:
|
||||
_controller: '\Drupal\dynamic_page_cache_test\DynamicPageCacheTestController::response'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
|
||||
dynamic_page_cache_test.response.admin:
|
||||
path: '/dynamic-page-cache-test/response/admin'
|
||||
defaults:
|
||||
_controller: '\Drupal\dynamic_page_cache_test\DynamicPageCacheTestController::response'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
options:
|
||||
_admin_route: TRUE
|
||||
|
||||
dynamic_page_cache_test.cacheable_response:
|
||||
path: '/dynamic-page-cache-test/cacheable-response'
|
||||
defaults:
|
||||
_controller: '\Drupal\dynamic_page_cache_test\DynamicPageCacheTestController::cacheableResponse'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
|
||||
dynamic_page_cache_test.cacheable_response.admin:
|
||||
path: '/dynamic-page-cache-test/cacheable-response/admin'
|
||||
defaults:
|
||||
_controller: '\Drupal\dynamic_page_cache_test\DynamicPageCacheTestController::cacheableResponse'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
options:
|
||||
_admin_route: TRUE
|
||||
|
||||
dynamic_page_cache_test.html:
|
||||
path: '/dynamic-page-cache-test/html'
|
||||
defaults:
|
||||
_controller: '\Drupal\dynamic_page_cache_test\DynamicPageCacheTestController::html'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
|
||||
dynamic_page_cache_test.html.admin:
|
||||
path: '/dynamic-page-cache-test/html/admin'
|
||||
defaults:
|
||||
_controller: '\Drupal\dynamic_page_cache_test\DynamicPageCacheTestController::html'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
options:
|
||||
_admin_route: TRUE
|
||||
|
||||
dynamic_page_cache_test.html.with_cache_contexts:
|
||||
path: '/dynamic-page-cache-test/html/with-cache-contexts'
|
||||
defaults:
|
||||
_controller: '\Drupal\dynamic_page_cache_test\DynamicPageCacheTestController::htmlWithCacheContexts'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
|
||||
dynamic_page_cache_test.html.uncacheable.max_age:
|
||||
path: '/dynamic-page-cache-test/html/uncacheable/max-age'
|
||||
defaults:
|
||||
_controller: '\Drupal\dynamic_page_cache_test\DynamicPageCacheTestController::htmlUncacheableMaxAge'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
|
||||
dynamic_page_cache_test.html.uncacheable.contexts:
|
||||
path: '/dynamic-page-cache-test/html/uncacheable/contexts'
|
||||
defaults:
|
||||
_controller: '\Drupal\dynamic_page_cache_test\DynamicPageCacheTestController::htmlUncacheableContexts'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
|
||||
dynamic_page_cache_test.html.uncacheable.tags:
|
||||
path: '/dynamic-page-cache-test/html/uncacheable/tags'
|
||||
defaults:
|
||||
_controller: '\Drupal\dynamic_page_cache_test\DynamicPageCacheTestController::htmlUncacheableTags'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
|
@ -0,0 +1,140 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\dynamic_page_cache_test;
|
||||
|
||||
use Drupal\Core\Cache\CacheableResponse;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\user\Entity\User;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* Controller routines for dynamic_page_cache_test routes.
|
||||
*/
|
||||
class DynamicPageCacheTestController {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* A route returning a Response object.
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
* A Response object.
|
||||
*/
|
||||
public function response() {
|
||||
return new Response('foobar');
|
||||
}
|
||||
|
||||
/**
|
||||
* A route returning a CacheableResponse object.
|
||||
*
|
||||
* @return \Drupal\Core\Cache\CacheableResponseInterface
|
||||
* A CacheableResponseInterface object.
|
||||
*/
|
||||
public function cacheableResponse() {
|
||||
$user = User::load(1);
|
||||
$response = new CacheableResponse($user->label());
|
||||
$response->addCacheableDependency($user);
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* A route returning a render array (without cache contexts, so cacheable).
|
||||
*
|
||||
* @return array
|
||||
* A render array.
|
||||
*/
|
||||
public function html() {
|
||||
return [
|
||||
'content' => [
|
||||
'#markup' => 'Hello world.',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* A route returning a render array (with cache contexts, so cacheable).
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The current request.
|
||||
*
|
||||
* @return array
|
||||
* A render array.
|
||||
*
|
||||
* @see html()
|
||||
*/
|
||||
public function htmlWithCacheContexts(Request $request) {
|
||||
$build = $this->html();
|
||||
$build['dynamic_part'] = [
|
||||
'#markup' => $this->t('Hello there, %animal.', ['%animal' => $request->query->get('animal')]),
|
||||
'#cache' => [
|
||||
'contexts' => [
|
||||
'url.query_args:animal',
|
||||
],
|
||||
],
|
||||
];
|
||||
return $build;
|
||||
}
|
||||
|
||||
/**
|
||||
* A route returning a render array (with max-age=0, so uncacheable)
|
||||
*
|
||||
* @return array
|
||||
* A render array.
|
||||
*
|
||||
* @see html()
|
||||
*/
|
||||
public function htmlUncacheableMaxAge() {
|
||||
$build = $this->html();
|
||||
$build['very_dynamic_part'] = [
|
||||
'#markup' => 'Drupal cannot handle the awesomeness of llamas.',
|
||||
'#cache' => [
|
||||
'max-age' => 0,
|
||||
],
|
||||
];
|
||||
return $build;
|
||||
}
|
||||
|
||||
/**
|
||||
* A route returning a render array (with 'user' context, so uncacheable)
|
||||
*
|
||||
* @return array
|
||||
* A render array.
|
||||
*
|
||||
* @see html()
|
||||
*/
|
||||
public function htmlUncacheableContexts() {
|
||||
$build = $this->html();
|
||||
$build['very_dynamic_part'] = [
|
||||
'#markup' => $this->t('@username cannot handle the awesomeness of llamas.', ['@username' => \Drupal::currentUser()->getDisplayName()]),
|
||||
'#cache' => [
|
||||
'contexts' => [
|
||||
'user',
|
||||
],
|
||||
],
|
||||
];
|
||||
return $build;
|
||||
}
|
||||
|
||||
/**
|
||||
* A route returning a render array (with a cache tag preventing caching).
|
||||
*
|
||||
* @return array
|
||||
* A render array.
|
||||
*
|
||||
* @see html()
|
||||
*/
|
||||
public function htmlUncacheableTags() {
|
||||
$build = $this->html();
|
||||
$build['very_dynamic_part'] = [
|
||||
'#markup' => 'Drupal cannot handle the awesomeness of llamas.',
|
||||
'#cache' => [
|
||||
'tags' => [
|
||||
'current-temperature',
|
||||
],
|
||||
],
|
||||
];
|
||||
return $build;
|
||||
}
|
||||
|
||||
}
|
Reference in a new issue