class ResourceResponseSubscriber in JSON:API 8.2
Same name and namespace in other branches
- 8 src/EventSubscriber/ResourceResponseSubscriber.php \Drupal\jsonapi\EventSubscriber\ResourceResponseSubscriber
Response subscriber that serializes and removes ResourceResponses' data.
@internal JSON:API maintains no PHP API. The API is the HTTP API. This class may change at any time and could break any dependencies on it.
This is 99% identical to:
\Drupal\rest\EventSubscriber\ResourceResponseSubscriber
but with a few differences: 1. It has the @jsonapi.serializer service injected instead of @serializer 2. It has the @current_route_match service no longer injected 3. It hardcodes the format to 'api_json' 4. It adds the CacheableNormalization object returned by JSON:API normalization to the response object. 5. It flattens only to a cacheable response if the HTTP method is cacheable.
Hierarchy
- class \Drupal\jsonapi\EventSubscriber\ResourceResponseSubscriber implements \Symfony\Component\EventDispatcher\EventSubscriberInterface
Expanded class hierarchy of ResourceResponseSubscriber
See also
https://www.drupal.org/project/jsonapi/issues/3032787
\Drupal\rest\EventSubscriber\ResourceResponseSubscriber
1 string reference to 'ResourceResponseSubscriber'
1 service uses ResourceResponseSubscriber
File
- src/
EventSubscriber/ ResourceResponseSubscriber.php, line 39
Namespace
Drupal\jsonapi\EventSubscriberView source
class ResourceResponseSubscriber implements EventSubscriberInterface {
/**
* The serializer.
*
* @var \Symfony\Component\Serializer\SerializerInterface
*/
protected $serializer;
/**
* Constructs a ResourceResponseSubscriber object.
*
* @param \Symfony\Component\Serializer\SerializerInterface $serializer
* The serializer.
*/
public function __construct(SerializerInterface $serializer) {
$this->serializer = $serializer;
}
/**
* {@inheritdoc}
*
* @see \Drupal\rest\EventSubscriber\ResourceResponseSubscriber::getSubscribedEvents()
* @see \Drupal\dynamic_page_cache\EventSubscriber\DynamicPageCacheSubscriber
*/
public static function getSubscribedEvents() {
// Run before the dynamic page cache subscriber (priority 100), so that
// Dynamic Page Cache can cache flattened responses.
$events[KernelEvents::RESPONSE][] = [
'onResponse',
128,
];
return $events;
}
/**
* Serializes ResourceResponse responses' data, and removes that data.
*
* @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event
* The event to process.
*/
public function onResponse(FilterResponseEvent $event) {
$response = $event
->getResponse();
if (!$response instanceof ResourceResponse) {
return;
}
$request = $event
->getRequest();
$format = 'api_json';
$this
->renderResponseBody($request, $response, $this->serializer, $format);
$event
->setResponse($this
->flattenResponse($response, $request));
}
/**
* Renders a resource response body.
*
* Serialization can invoke rendering (e.g., generating URLs), but the
* serialization API does not provide a mechanism to collect the
* bubbleable metadata associated with that (e.g., language and other
* contexts), so instead, allow those to "leak" and collect them here in
* a render context.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The request object.
* @param \Drupal\jsonapi\ResourceResponse $response
* The response from the JSON:API resource.
* @param \Symfony\Component\Serializer\SerializerInterface $serializer
* The serializer to use.
* @param string|null $format
* The response format, or NULL in case the response does not need a format,
* for example for the response to a DELETE request.
*
* @todo Add test coverage for language negotiation contexts in
* https://www.drupal.org/node/2135829.
*/
protected function renderResponseBody(Request $request, ResourceResponse $response, SerializerInterface $serializer, $format) {
$data = $response
->getResponseData();
// If there is data to send, serialize and set it as the response body.
if ($data !== NULL) {
// First normalize the data. Note that error responses do not need a
// normalization context, since there are no entities to normalize.
// @see \Drupal\jsonapi\EventSubscriber\DefaultExceptionSubscriber::isJsonApiExceptionEvent()
$context = !$response
->isSuccessful() ? [] : static::generateContext($request);
$jsonapi_doc_object = $serializer
->normalize($data, $format, $context);
// Having just normalized the data, we can associate its cacheability with
// the response object.
assert($jsonapi_doc_object instanceof CacheableNormalization);
$response
->addCacheableDependency($jsonapi_doc_object);
// Finally, encode the normalized data (JSON:API's encoder rasterizes it
// automatically).
$response
->setContent($serializer
->encode($jsonapi_doc_object
->getNormalization(), $format));
$response->headers
->set('Content-Type', $request
->getMimeType($format));
}
}
/**
* Generates a top-level JSON:API normalization context.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The request from which the context can be derived.
*
* @return array
* The generated context.
*/
protected static function generateContext(Request $request) {
// Build the expanded context.
$context = [
'account' => NULL,
'sparse_fieldset' => NULL,
];
if ($request->query
->get('fields')) {
$context['sparse_fieldset'] = array_map(function ($item) {
return explode(',', $item);
}, $request->query
->get('fields'));
}
return $context;
}
/**
* Flattens a fully rendered resource response.
*
* Ensures that complex data structures in ResourceResponse::getResponseData()
* are not serialized. Not doing this means that caching this response object
* requires deserializing the PHP data when reading this response object from
* cache, which can be very costly, and is unnecessary.
*
* @param \Drupal\jsonapi\ResourceResponse $response
* A fully rendered resource response.
* @param \Symfony\Component\HttpFoundation\Request $request
* The request for which this response is generated.
*
* @return \Drupal\Core\Cache\CacheableResponse|\Symfony\Component\HttpFoundation\Response
* The flattened response.
*/
protected static function flattenResponse(ResourceResponse $response, Request $request) {
$final_response = $response instanceof CacheableResponseInterface && $request
->isMethodCacheable() ? new CacheableResponse() : new Response();
$final_response
->setContent($response
->getContent());
$final_response
->setStatusCode($response
->getStatusCode());
$final_response
->setProtocolVersion($response
->getProtocolVersion());
if ($charset = $response
->getCharset()) {
$final_response
->setCharset($charset);
}
$final_response->headers = clone $response->headers;
if ($final_response instanceof CacheableResponseInterface) {
$final_response
->addCacheableDependency($response
->getCacheableMetadata());
}
return $final_response;
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
ResourceResponseSubscriber:: |
protected | property | The serializer. | |
ResourceResponseSubscriber:: |
protected static | function | Flattens a fully rendered resource response. | |
ResourceResponseSubscriber:: |
protected static | function | Generates a top-level JSON:API normalization context. | |
ResourceResponseSubscriber:: |
public static | function | ||
ResourceResponseSubscriber:: |
public | function | Serializes ResourceResponse responses' data, and removes that data. | |
ResourceResponseSubscriber:: |
protected | function | Renders a resource response body. | |
ResourceResponseSubscriber:: |
public | function | Constructs a ResourceResponseSubscriber object. |