Mercurial > hg > cmmr2012-drupal-site
comparison core/modules/jsonapi/src/EventSubscriber/ResourceResponseSubscriber.php @ 5:12f9dff5fda9 tip
Update to Drupal core 8.7.1
author | Chris Cannam |
---|---|
date | Thu, 09 May 2019 15:34:47 +0100 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
4:a9cd425dd02b | 5:12f9dff5fda9 |
---|---|
1 <?php | |
2 | |
3 namespace Drupal\jsonapi\EventSubscriber; | |
4 | |
5 use Drupal\Core\Cache\CacheableResponse; | |
6 use Drupal\Core\Cache\CacheableResponseInterface; | |
7 use Drupal\jsonapi\Normalizer\Value\CacheableNormalization; | |
8 use Drupal\jsonapi\ResourceResponse; | |
9 use Symfony\Component\EventDispatcher\EventSubscriberInterface; | |
10 use Symfony\Component\HttpFoundation\Request; | |
11 use Symfony\Component\HttpFoundation\Response; | |
12 use Symfony\Component\HttpKernel\Event\FilterResponseEvent; | |
13 use Symfony\Component\HttpKernel\KernelEvents; | |
14 use Symfony\Component\Serializer\SerializerInterface; | |
15 | |
16 /** | |
17 * Response subscriber that serializes and removes ResourceResponses' data. | |
18 * | |
19 * @internal JSON:API maintains no PHP API. The API is the HTTP API. This class | |
20 * may change at any time and could break any dependencies on it. | |
21 * | |
22 * @see https://www.drupal.org/project/jsonapi/issues/3032787 | |
23 * @see jsonapi.api.php | |
24 * | |
25 * This is 99% identical to: | |
26 * | |
27 * \Drupal\rest\EventSubscriber\ResourceResponseSubscriber | |
28 * | |
29 * but with a few differences: | |
30 * 1. It has the @jsonapi.serializer service injected instead of @serializer | |
31 * 2. It has the @current_route_match service no longer injected | |
32 * 3. It hardcodes the format to 'api_json' | |
33 * 4. It adds the CacheableNormalization object returned by JSON:API | |
34 * normalization to the response object. | |
35 * 5. It flattens only to a cacheable response if the HTTP method is cacheable. | |
36 * | |
37 * @see \Drupal\rest\EventSubscriber\ResourceResponseSubscriber | |
38 */ | |
39 class ResourceResponseSubscriber implements EventSubscriberInterface { | |
40 | |
41 /** | |
42 * The serializer. | |
43 * | |
44 * @var \Symfony\Component\Serializer\SerializerInterface | |
45 */ | |
46 protected $serializer; | |
47 | |
48 /** | |
49 * Constructs a ResourceResponseSubscriber object. | |
50 * | |
51 * @param \Symfony\Component\Serializer\SerializerInterface $serializer | |
52 * The serializer. | |
53 */ | |
54 public function __construct(SerializerInterface $serializer) { | |
55 $this->serializer = $serializer; | |
56 } | |
57 | |
58 /** | |
59 * {@inheritdoc} | |
60 * | |
61 * @see \Drupal\rest\EventSubscriber\ResourceResponseSubscriber::getSubscribedEvents() | |
62 * @see \Drupal\dynamic_page_cache\EventSubscriber\DynamicPageCacheSubscriber | |
63 */ | |
64 public static function getSubscribedEvents() { | |
65 // Run before the dynamic page cache subscriber (priority 100), so that | |
66 // Dynamic Page Cache can cache flattened responses. | |
67 $events[KernelEvents::RESPONSE][] = ['onResponse', 128]; | |
68 return $events; | |
69 } | |
70 | |
71 /** | |
72 * Serializes ResourceResponse responses' data, and removes that data. | |
73 * | |
74 * @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event | |
75 * The event to process. | |
76 */ | |
77 public function onResponse(FilterResponseEvent $event) { | |
78 $response = $event->getResponse(); | |
79 if (!$response instanceof ResourceResponse) { | |
80 return; | |
81 } | |
82 | |
83 $request = $event->getRequest(); | |
84 $format = 'api_json'; | |
85 $this->renderResponseBody($request, $response, $this->serializer, $format); | |
86 $event->setResponse($this->flattenResponse($response, $request)); | |
87 } | |
88 | |
89 /** | |
90 * Renders a resource response body. | |
91 * | |
92 * Serialization can invoke rendering (e.g., generating URLs), but the | |
93 * serialization API does not provide a mechanism to collect the | |
94 * bubbleable metadata associated with that (e.g., language and other | |
95 * contexts), so instead, allow those to "leak" and collect them here in | |
96 * a render context. | |
97 * | |
98 * @param \Symfony\Component\HttpFoundation\Request $request | |
99 * The request object. | |
100 * @param \Drupal\jsonapi\ResourceResponse $response | |
101 * The response from the JSON:API resource. | |
102 * @param \Symfony\Component\Serializer\SerializerInterface $serializer | |
103 * The serializer to use. | |
104 * @param string|null $format | |
105 * The response format, or NULL in case the response does not need a format, | |
106 * for example for the response to a DELETE request. | |
107 * | |
108 * @todo Add test coverage for language negotiation contexts in | |
109 * https://www.drupal.org/node/2135829. | |
110 */ | |
111 protected function renderResponseBody(Request $request, ResourceResponse $response, SerializerInterface $serializer, $format) { | |
112 $data = $response->getResponseData(); | |
113 | |
114 // If there is data to send, serialize and set it as the response body. | |
115 if ($data !== NULL) { | |
116 // First normalize the data. Note that error responses do not need a | |
117 // normalization context, since there are no entities to normalize. | |
118 // @see \Drupal\jsonapi\EventSubscriber\DefaultExceptionSubscriber::isJsonApiExceptionEvent() | |
119 $context = !$response->isSuccessful() ? [] : static::generateContext($request); | |
120 $jsonapi_doc_object = $serializer->normalize($data, $format, $context); | |
121 // Having just normalized the data, we can associate its cacheability with | |
122 // the response object. | |
123 assert($jsonapi_doc_object instanceof CacheableNormalization); | |
124 $response->addCacheableDependency($jsonapi_doc_object); | |
125 // Finally, encode the normalized data (JSON:API's encoder rasterizes it | |
126 // automatically). | |
127 $response->setContent($serializer->encode($jsonapi_doc_object->getNormalization(), $format)); | |
128 $response->headers->set('Content-Type', $request->getMimeType($format)); | |
129 } | |
130 } | |
131 | |
132 /** | |
133 * Generates a top-level JSON:API normalization context. | |
134 * | |
135 * @param \Symfony\Component\HttpFoundation\Request $request | |
136 * The request from which the context can be derived. | |
137 * | |
138 * @return array | |
139 * The generated context. | |
140 */ | |
141 protected static function generateContext(Request $request) { | |
142 // Build the expanded context. | |
143 $context = [ | |
144 'account' => NULL, | |
145 'sparse_fieldset' => NULL, | |
146 ]; | |
147 if ($request->query->get('fields')) { | |
148 $context['sparse_fieldset'] = array_map(function ($item) { | |
149 return explode(',', $item); | |
150 }, $request->query->get('fields')); | |
151 } | |
152 return $context; | |
153 } | |
154 | |
155 /** | |
156 * Flattens a fully rendered resource response. | |
157 * | |
158 * Ensures that complex data structures in ResourceResponse::getResponseData() | |
159 * are not serialized. Not doing this means that caching this response object | |
160 * requires deserializing the PHP data when reading this response object from | |
161 * cache, which can be very costly, and is unnecessary. | |
162 * | |
163 * @param \Drupal\jsonapi\ResourceResponse $response | |
164 * A fully rendered resource response. | |
165 * @param \Symfony\Component\HttpFoundation\Request $request | |
166 * The request for which this response is generated. | |
167 * | |
168 * @return \Drupal\Core\Cache\CacheableResponse|\Symfony\Component\HttpFoundation\Response | |
169 * The flattened response. | |
170 */ | |
171 protected static function flattenResponse(ResourceResponse $response, Request $request) { | |
172 $final_response = ($response instanceof CacheableResponseInterface && $request->isMethodCacheable()) ? new CacheableResponse() : new Response(); | |
173 $final_response->setContent($response->getContent()); | |
174 $final_response->setStatusCode($response->getStatusCode()); | |
175 $final_response->setProtocolVersion($response->getProtocolVersion()); | |
176 if ($charset = $response->getCharset()) { | |
177 $final_response->setCharset($charset); | |
178 } | |
179 $final_response->headers = clone $response->headers; | |
180 if ($final_response instanceof CacheableResponseInterface) { | |
181 $final_response->addCacheableDependency($response->getCacheableMetadata()); | |
182 } | |
183 return $final_response; | |
184 } | |
185 | |
186 } |