.../Core/Render/MainContent/HtmlRenderer.php | 3 -- .../EventSubscriber/DynamicPageCacheSubscriber.php | 45 ++++++++++++++++++++++ 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php b/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php index b6eaf59..4a4ab59 100644 --- a/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php +++ b/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php @@ -223,9 +223,6 @@ protected function prepare(array $main_content, Request $request, RouteMatchInte // @todo Remove this once https://www.drupal.org/node/2359901 lands. if (!empty($main_content)) { $this->renderer->executeInRenderContext(new RenderContext(), function() use (&$main_content) { - if (!isset($main_content['#cache'])) { - throw new \LogicException('Must have cacheability metadata.'); - } if (isset($main_content['#cache']['keys'])) { // Retain #title, otherwise, dynamically generated titles would be // missing for controllers whose entire returned render array is diff --git a/core/modules/dynamic_page_cache/src/EventSubscriber/DynamicPageCacheSubscriber.php b/core/modules/dynamic_page_cache/src/EventSubscriber/DynamicPageCacheSubscriber.php index 103b303..792d0cd 100644 --- a/core/modules/dynamic_page_cache/src/EventSubscriber/DynamicPageCacheSubscriber.php +++ b/core/modules/dynamic_page_cache/src/EventSubscriber/DynamicPageCacheSubscriber.php @@ -9,7 +9,9 @@ use Drupal\Core\Cache\Cache; use Drupal\Core\Cache\CacheableMetadata; +use Drupal\Core\Cache\CacheableResponse; use Drupal\Core\Cache\CacheableResponseInterface; +use Drupal\Core\EventSubscriber\MainContentViewSubscriber; use Drupal\Core\PageCache\RequestPolicyInterface; use Drupal\Core\PageCache\ResponsePolicyInterface; use Drupal\Core\Render\Element; @@ -17,6 +19,7 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\Event\FilterResponseEvent; use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent; use Symfony\Component\HttpKernel\KernelEvents; /** @@ -149,6 +152,44 @@ public function onRouteMatch(GetResponseEvent $event) { } /** + * Complain if a controller returns a render array without #cache. + * + * @param \Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent $event + * The event to process. + * + * @throws \LogicException + */ + public function onViewRenderArray(GetResponseForControllerResultEvent $event) { + $request = $event->getRequest(); + $result = $event->getControllerResult(); + + // Don't complain if Dynamic Page Cache's request subscriber did not fire, + // because that means it is impossible to have a Dynamic Page Cache hit, + // and hence complaining is unnecessary. + // @see ::onResponse() + if (!$request->attributes->has(self::ATTRIBUTE_REQUEST_POLICY_RESULT)) { + return; + } + + // Don't complain if the Dynamic Page Cache request & response policies are + // not met, because that means Dynamic Page Cache won't cache this response + // and hence complaining is unnecessary. + // @see onRouteMatch() + if ($request->attributes->get(self::ATTRIBUTE_REQUEST_POLICY_RESULT) === RequestPolicyInterface::DENY || $this->responsePolicy->check(new CacheableResponse(), $request) === ResponsePolicyInterface::DENY) { + return; + } + + // Check if MainContentViewSubscriber would render this render array, i.e. + // if this render array will in fact be converted into a response. + if (is_array($result) && ($request->query->has(MainContentViewSubscriber::WRAPPER_FORMAT) || $request->getRequestFormat() == 'html')) { + // If #cache is not set, complain. + if (!isset($result['#cache'])) { + throw new \LogicException('Controllers must provide cacheability metadata.'); + } + } + } + + /** * Stores a response in case of a Dynamic Page Cache miss, if cacheable. * * @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event @@ -323,6 +364,10 @@ public static function getSubscribedEvents() { // Run before HtmlResponseSubscriber::onRespond(), which has priority 0. $events[KernelEvents::RESPONSE][] = ['onResponse', 100]; + // Run before MainContentViewSubscriber::onViewRenderArray(), which has + // priority 0. + $events[KernelEvents::VIEW][] = ['onViewRenderArray', 100]; + return $events; }