Chris@0: serializer = $serializer; Chris@0: $this->renderer = $renderer; Chris@0: $this->routeMatch = $route_match; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Serializes ResourceResponse responses' data, and removes that data. Chris@0: * Chris@0: * @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event Chris@0: * The event to process. Chris@0: */ Chris@0: public function onResponse(FilterResponseEvent $event) { Chris@0: $response = $event->getResponse(); Chris@0: if (!$response instanceof ResourceResponseInterface) { Chris@0: return; Chris@0: } Chris@0: Chris@0: $request = $event->getRequest(); Chris@0: $format = $this->getResponseFormat($this->routeMatch, $request); Chris@0: $this->renderResponseBody($request, $response, $this->serializer, $format); Chris@0: $event->setResponse($this->flattenResponse($response)); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Determines the format to respond in. Chris@0: * Chris@0: * Respects the requested format if one is specified. However, it is common to Chris@14: * forget to specify a response format in case of a POST or PATCH. Rather than Chris@0: * simply throwing an error, we apply the robustness principle: when POSTing Chris@0: * or PATCHing using a certain format, you probably expect a response in that Chris@0: * same format. Chris@0: * Chris@0: * @param \Drupal\Core\Routing\RouteMatchInterface $route_match Chris@0: * The current route match. Chris@0: * @param \Symfony\Component\HttpFoundation\Request $request Chris@0: * The current request. Chris@0: * Chris@0: * @return string Chris@0: * The response format. Chris@0: */ Chris@0: public function getResponseFormat(RouteMatchInterface $route_match, Request $request) { Chris@0: $route = $route_match->getRouteObject(); Chris@14: $acceptable_response_formats = $route->hasRequirement('_format') ? explode('|', $route->getRequirement('_format')) : []; Chris@14: $acceptable_request_formats = $route->hasRequirement('_content_type_format') ? explode('|', $route->getRequirement('_content_type_format')) : []; Chris@14: $acceptable_formats = $request->isMethodCacheable() ? $acceptable_response_formats : $acceptable_request_formats; Chris@0: Chris@0: $requested_format = $request->getRequestFormat(); Chris@0: $content_type_format = $request->getContentType(); Chris@0: Chris@14: // If an acceptable response format is requested, then use that. Otherwise, Chris@14: // including and particularly when the client forgot to specify a response Chris@14: // format, then use heuristics to select the format that is most likely Chris@14: // expected. Chris@14: if (in_array($requested_format, $acceptable_response_formats, TRUE)) { Chris@0: return $requested_format; Chris@0: } Chris@14: Chris@0: // If a request body is present, then use the format corresponding to the Chris@0: // request body's Content-Type for the response, if it's an acceptable Chris@0: // format for the request. Chris@14: if (!empty($request->getContent()) && in_array($content_type_format, $acceptable_request_formats, TRUE)) { Chris@0: return $content_type_format; Chris@0: } Chris@14: Chris@0: // Otherwise, use the first acceptable format. Chris@14: if (!empty($acceptable_formats)) { Chris@0: return $acceptable_formats[0]; Chris@0: } Chris@14: Chris@0: // Sometimes, there are no acceptable formats, e.g. DELETE routes. Chris@14: return NULL; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Renders a resource response body. Chris@0: * Chris@14: * During serialization, encoders and normalizers are able to explicitly Chris@14: * bubble cacheability metadata via the 'cacheability' key-value pair in the Chris@14: * received context. This bubbled cacheability metadata will be applied to the Chris@14: * the response. Chris@14: * Chris@14: * In versions of Drupal prior to 8.5, implicit bubbling of cacheability Chris@14: * metadata was allowed because there was no explicit cacheability metadata Chris@14: * bubbling API. To maintain backwards compatibility, we continue to support Chris@14: * this, but support for this will be dropped in Drupal 9.0.0. This is Chris@14: * especially useful when interacting with APIs that implicitly invoke Chris@14: * rendering (for example: generating URLs): this allows those to "leak", and Chris@14: * we collect their bubbled cacheability metadata automatically in a render Chris@14: * context. Chris@0: * Chris@0: * @param \Symfony\Component\HttpFoundation\Request $request Chris@0: * The request object. Chris@0: * @param \Drupal\rest\ResourceResponseInterface $response Chris@0: * The response from the REST resource. Chris@0: * @param \Symfony\Component\Serializer\SerializerInterface $serializer Chris@0: * The serializer to use. Chris@0: * @param string|null $format Chris@0: * The response format, or NULL in case the response does not need a format, Chris@0: * for example for the response to a DELETE request. Chris@0: * Chris@0: * @todo Add test coverage for language negotiation contexts in Chris@0: * https://www.drupal.org/node/2135829. Chris@0: */ Chris@0: protected function renderResponseBody(Request $request, ResourceResponseInterface $response, SerializerInterface $serializer, $format) { Chris@0: $data = $response->getResponseData(); Chris@0: Chris@0: // If there is data to send, serialize and set it as the response body. Chris@0: if ($data !== NULL) { Chris@14: $serialization_context = [ Chris@14: 'request' => $request, Chris@14: CacheableNormalizerInterface::SERIALIZATION_CONTEXT_CACHEABILITY => new CacheableMetadata(), Chris@14: ]; Chris@14: Chris@14: // @deprecated In Drupal 8.5.0, will be removed before Drupal 9.0.0. Use Chris@14: // explicit cacheability metadata bubbling instead. (The wrapping call to Chris@14: // executeInRenderContext() will be removed before Drupal 9.0.0.) Chris@0: $context = new RenderContext(); Chris@0: $output = $this->renderer Chris@14: ->executeInRenderContext($context, function () use ($serializer, $data, $format, $serialization_context) { Chris@14: return $serializer->serialize($data, $format, $serialization_context); Chris@0: }); Chris@14: if ($response instanceof CacheableResponseInterface) { Chris@14: if (!$context->isEmpty()) { Chris@14: @trigger_error('Implicit cacheability metadata bubbling (onto the global render context) in normalizers is deprecated since Drupal 8.5.0 and will be removed in Drupal 9.0.0. Use the "cacheability" serialization context instead, for explicit cacheability metadata bubbling. See https://www.drupal.org/node/2918937', E_USER_DEPRECATED); Chris@14: $response->addCacheableDependency($context->pop()); Chris@14: } Chris@14: $response->addCacheableDependency($serialization_context[CacheableNormalizerInterface::SERIALIZATION_CONTEXT_CACHEABILITY]); Chris@0: } Chris@0: Chris@0: $response->setContent($output); Chris@0: $response->headers->set('Content-Type', $request->getMimeType($format)); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Flattens a fully rendered resource response. Chris@0: * Chris@0: * Ensures that complex data structures in ResourceResponse::getResponseData() Chris@0: * are not serialized. Not doing this means that caching this response object Chris@0: * requires unserializing the PHP data when reading this response object from Chris@0: * cache, which can be very costly, and is unnecessary. Chris@0: * Chris@0: * @param \Drupal\rest\ResourceResponseInterface $response Chris@0: * A fully rendered resource response. Chris@0: * Chris@0: * @return \Drupal\Core\Cache\CacheableResponse|\Symfony\Component\HttpFoundation\Response Chris@0: * The flattened response. Chris@0: */ Chris@0: protected function flattenResponse(ResourceResponseInterface $response) { Chris@0: $final_response = ($response instanceof CacheableResponseInterface) ? new CacheableResponse() : new Response(); Chris@0: $final_response->setContent($response->getContent()); Chris@0: $final_response->setStatusCode($response->getStatusCode()); Chris@0: $final_response->setProtocolVersion($response->getProtocolVersion()); Chris@18: if ($response->getCharset()) { Chris@18: $final_response->setCharset($response->getCharset()); Chris@18: } Chris@0: $final_response->headers = clone $response->headers; Chris@0: if ($final_response instanceof CacheableResponseInterface) { Chris@0: $final_response->addCacheableDependency($response->getCacheableMetadata()); Chris@0: } Chris@0: return $final_response; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public static function getSubscribedEvents() { Chris@0: // Run before \Drupal\dynamic_page_cache\EventSubscriber\DynamicPageCacheSubscriber Chris@0: // (priority 100), so that Dynamic Page Cache can cache flattened responses. Chris@0: $events[KernelEvents::RESPONSE][] = ['onResponse', 128]; Chris@0: return $events; Chris@0: } Chris@0: Chris@0: }