class ResourceResponseSubscriber in Drupal 10
Same name in this branch
- 10 core/modules/jsonapi/src/EventSubscriber/ResourceResponseSubscriber.php \Drupal\jsonapi\EventSubscriber\ResourceResponseSubscriber
- 10 core/modules/rest/src/EventSubscriber/ResourceResponseSubscriber.php \Drupal\rest\EventSubscriber\ResourceResponseSubscriber
Same name and namespace in other branches
- 8 core/modules/jsonapi/src/EventSubscriber/ResourceResponseSubscriber.php \Drupal\jsonapi\EventSubscriber\ResourceResponseSubscriber
- 9 core/modules/jsonapi/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/drupal/issues/3032787
\Drupal\rest\EventSubscriber\ResourceResponseSubscriber
1 string reference to 'ResourceResponseSubscriber'
- jsonapi.services.yml in core/
modules/ jsonapi/ jsonapi.services.yml - core/modules/jsonapi/jsonapi.services.yml
1 service uses ResourceResponseSubscriber
File
- core/
modules/ jsonapi/ 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() : array {
// 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\ResponseEvent $event
* The event to process.
*/
public function onResponse(ResponseEvent $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.
if ($response instanceof CacheableResponseInterface) {
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
->has('fields')) {
$context['sparse_fieldset'] = array_map(function ($item) {
return explode(',', $item);
}, $request->query
->all('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;
}
}