Chris@18: serializer = $serializer; Chris@18: } Chris@18: Chris@18: /** Chris@18: * {@inheritdoc} Chris@18: * Chris@18: * @see \Drupal\rest\EventSubscriber\ResourceResponseSubscriber::getSubscribedEvents() Chris@18: * @see \Drupal\dynamic_page_cache\EventSubscriber\DynamicPageCacheSubscriber Chris@18: */ Chris@18: public static function getSubscribedEvents() { Chris@18: // Run before the dynamic page cache subscriber (priority 100), so that Chris@18: // Dynamic Page Cache can cache flattened responses. Chris@18: $events[KernelEvents::RESPONSE][] = ['onResponse', 128]; Chris@18: return $events; Chris@18: } Chris@18: Chris@18: /** Chris@18: * Serializes ResourceResponse responses' data, and removes that data. Chris@18: * Chris@18: * @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event Chris@18: * The event to process. Chris@18: */ Chris@18: public function onResponse(FilterResponseEvent $event) { Chris@18: $response = $event->getResponse(); Chris@18: if (!$response instanceof ResourceResponse) { Chris@18: return; Chris@18: } Chris@18: Chris@18: $request = $event->getRequest(); Chris@18: $format = 'api_json'; Chris@18: $this->renderResponseBody($request, $response, $this->serializer, $format); Chris@18: $event->setResponse($this->flattenResponse($response, $request)); Chris@18: } Chris@18: Chris@18: /** Chris@18: * Renders a resource response body. Chris@18: * Chris@18: * Serialization can invoke rendering (e.g., generating URLs), but the Chris@18: * serialization API does not provide a mechanism to collect the Chris@18: * bubbleable metadata associated with that (e.g., language and other Chris@18: * contexts), so instead, allow those to "leak" and collect them here in Chris@18: * a render context. Chris@18: * Chris@18: * @param \Symfony\Component\HttpFoundation\Request $request Chris@18: * The request object. Chris@18: * @param \Drupal\jsonapi\ResourceResponse $response Chris@18: * The response from the JSON:API resource. Chris@18: * @param \Symfony\Component\Serializer\SerializerInterface $serializer Chris@18: * The serializer to use. Chris@18: * @param string|null $format Chris@18: * The response format, or NULL in case the response does not need a format, Chris@18: * for example for the response to a DELETE request. Chris@18: * Chris@18: * @todo Add test coverage for language negotiation contexts in Chris@18: * https://www.drupal.org/node/2135829. Chris@18: */ Chris@18: protected function renderResponseBody(Request $request, ResourceResponse $response, SerializerInterface $serializer, $format) { Chris@18: $data = $response->getResponseData(); Chris@18: Chris@18: // If there is data to send, serialize and set it as the response body. Chris@18: if ($data !== NULL) { Chris@18: // First normalize the data. Note that error responses do not need a Chris@18: // normalization context, since there are no entities to normalize. Chris@18: // @see \Drupal\jsonapi\EventSubscriber\DefaultExceptionSubscriber::isJsonApiExceptionEvent() Chris@18: $context = !$response->isSuccessful() ? [] : static::generateContext($request); Chris@18: $jsonapi_doc_object = $serializer->normalize($data, $format, $context); Chris@18: // Having just normalized the data, we can associate its cacheability with Chris@18: // the response object. Chris@18: assert($jsonapi_doc_object instanceof CacheableNormalization); Chris@18: $response->addCacheableDependency($jsonapi_doc_object); Chris@18: // Finally, encode the normalized data (JSON:API's encoder rasterizes it Chris@18: // automatically). Chris@18: $response->setContent($serializer->encode($jsonapi_doc_object->getNormalization(), $format)); Chris@18: $response->headers->set('Content-Type', $request->getMimeType($format)); Chris@18: } Chris@18: } Chris@18: Chris@18: /** Chris@18: * Generates a top-level JSON:API normalization context. Chris@18: * Chris@18: * @param \Symfony\Component\HttpFoundation\Request $request Chris@18: * The request from which the context can be derived. Chris@18: * Chris@18: * @return array Chris@18: * The generated context. Chris@18: */ Chris@18: protected static function generateContext(Request $request) { Chris@18: // Build the expanded context. Chris@18: $context = [ Chris@18: 'account' => NULL, Chris@18: 'sparse_fieldset' => NULL, Chris@18: ]; Chris@18: if ($request->query->get('fields')) { Chris@18: $context['sparse_fieldset'] = array_map(function ($item) { Chris@18: return explode(',', $item); Chris@18: }, $request->query->get('fields')); Chris@18: } Chris@18: return $context; Chris@18: } Chris@18: Chris@18: /** Chris@18: * Flattens a fully rendered resource response. Chris@18: * Chris@18: * Ensures that complex data structures in ResourceResponse::getResponseData() Chris@18: * are not serialized. Not doing this means that caching this response object Chris@18: * requires deserializing the PHP data when reading this response object from Chris@18: * cache, which can be very costly, and is unnecessary. Chris@18: * Chris@18: * @param \Drupal\jsonapi\ResourceResponse $response Chris@18: * A fully rendered resource response. Chris@18: * @param \Symfony\Component\HttpFoundation\Request $request Chris@18: * The request for which this response is generated. Chris@18: * Chris@18: * @return \Drupal\Core\Cache\CacheableResponse|\Symfony\Component\HttpFoundation\Response Chris@18: * The flattened response. Chris@18: */ Chris@18: protected static function flattenResponse(ResourceResponse $response, Request $request) { Chris@18: $final_response = ($response instanceof CacheableResponseInterface && $request->isMethodCacheable()) ? new CacheableResponse() : new Response(); Chris@18: $final_response->setContent($response->getContent()); Chris@18: $final_response->setStatusCode($response->getStatusCode()); Chris@18: $final_response->setProtocolVersion($response->getProtocolVersion()); Chris@18: if ($charset = $response->getCharset()) { Chris@18: $final_response->setCharset($charset); Chris@18: } Chris@18: $final_response->headers = clone $response->headers; Chris@18: if ($final_response instanceof CacheableResponseInterface) { Chris@18: $final_response->addCacheableDependency($response->getCacheableMetadata()); Chris@18: } Chris@18: return $final_response; Chris@18: } Chris@18: Chris@18: }