Chris@0: argumentResolver = $argument_resolver; Chris@0: $this->renderer = $renderer; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Ensures bubbleable metadata from early rendering is not lost. Chris@0: * Chris@0: * @param \Symfony\Component\HttpKernel\Event\FilterControllerEvent $event Chris@0: * The controller event. Chris@0: */ Chris@0: public function onController(FilterControllerEvent $event) { Chris@0: $controller = $event->getController(); Chris@0: Chris@0: // See \Symfony\Component\HttpKernel\HttpKernel::handleRaw(). Chris@17: $arguments = $this->argumentResolver->getArguments($event->getRequest(), $controller); Chris@0: Chris@0: $event->setController(function () use ($controller, $arguments) { Chris@0: return $this->wrapControllerExecutionInRenderContext($controller, $arguments); Chris@0: }); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Wraps a controller execution in a render context. Chris@0: * Chris@0: * @param callable $controller Chris@0: * The controller to execute. Chris@0: * @param array $arguments Chris@0: * The arguments to pass to the controller. Chris@0: * Chris@0: * @return mixed Chris@0: * The return value of the controller. Chris@0: * Chris@0: * @throws \LogicException Chris@0: * When early rendering has occurred in a controller that returned a Chris@0: * Response or domain object that cares about attachments or cacheability. Chris@0: * Chris@0: * @see \Symfony\Component\HttpKernel\HttpKernel::handleRaw() Chris@0: */ Chris@0: protected function wrapControllerExecutionInRenderContext($controller, array $arguments) { Chris@0: $context = new RenderContext(); Chris@0: Chris@0: $response = $this->renderer->executeInRenderContext($context, function () use ($controller, $arguments) { Chris@0: // Now call the actual controller, just like HttpKernel does. Chris@0: return call_user_func_array($controller, $arguments); Chris@0: }); Chris@0: Chris@0: // If early rendering happened, i.e. if code in the controller called Chris@0: // drupal_render() outside of a render context, then the bubbleable metadata Chris@0: // for that is stored in the current render context. Chris@0: if (!$context->isEmpty()) { Chris@0: /** @var \Drupal\Core\Render\BubbleableMetadata $early_rendering_bubbleable_metadata */ Chris@0: $early_rendering_bubbleable_metadata = $context->pop(); Chris@0: Chris@0: // If a render array or AjaxResponse is returned by the controller, merge Chris@0: // the "lost" bubbleable metadata. Chris@0: if (is_array($response)) { Chris@0: BubbleableMetadata::createFromRenderArray($response) Chris@0: ->merge($early_rendering_bubbleable_metadata) Chris@0: ->applyTo($response); Chris@0: } Chris@0: elseif ($response instanceof AjaxResponse) { Chris@0: $response->addAttachments($early_rendering_bubbleable_metadata->getAttachments()); Chris@0: // @todo Make AjaxResponse cacheable in Chris@0: // https://www.drupal.org/node/956186. Meanwhile, allow contrib Chris@0: // subclasses to be. Chris@0: if ($response instanceof CacheableResponseInterface) { Chris@0: $response->addCacheableDependency($early_rendering_bubbleable_metadata); Chris@0: } Chris@0: } Chris@0: // If a non-Ajax Response or domain object is returned and it cares about Chris@0: // attachments or cacheability, then throw an exception: early rendering Chris@0: // is not permitted in that case. It is the developer's responsibility Chris@0: // to not use early rendering. Chris@0: elseif ($response instanceof AttachmentsInterface || $response instanceof CacheableResponseInterface || $response instanceof CacheableDependencyInterface) { Chris@0: throw new \LogicException(sprintf('The controller result claims to be providing relevant cache metadata, but leaked metadata was detected. Please ensure you are not rendering content too early. Returned object class: %s.', get_class($response))); Chris@0: } Chris@0: else { Chris@0: // A Response or domain object is returned that does not care about Chris@0: // attachments nor cacheability; for instance, a RedirectResponse. It is Chris@0: // safe to discard any early rendering metadata. Chris@0: } Chris@0: } Chris@0: Chris@0: return $response; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public static function getSubscribedEvents() { Chris@0: $events[KernelEvents::CONTROLLER][] = ['onController']; Chris@0: Chris@0: return $events; Chris@0: } Chris@0: Chris@0: }