PageRenderTime 42ms CodeModel.GetById 0ms RepoModel.GetById 0ms app.codeStats 0ms

/web/core/modules/rest/src/RequestHandler.php

https://gitlab.com/mohamed_hussein/prodt
PHP | 291 lines | 115 code | 28 blank | 148 comment | 9 complexity | 90e81a799ac81a9c6ac7c18d9fa97a9c MD5 | raw file
  1. <?php
  2. namespace Drupal\rest;
  3. use Drupal\Component\Utility\ArgumentsResolver;
  4. use Drupal\Core\Cache\CacheableResponseInterface;
  5. use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
  6. use Drupal\Core\Entity\EntityInterface;
  7. use Drupal\Core\Routing\RouteMatchInterface;
  8. use Drupal\rest\Plugin\ResourceInterface;
  9. use Symfony\Component\DependencyInjection\ContainerInterface;
  10. use Symfony\Component\HttpFoundation\Request;
  11. use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
  12. use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
  13. use Symfony\Component\Serializer\Exception\UnexpectedValueException;
  14. use Symfony\Component\Serializer\Exception\InvalidArgumentException;
  15. use Symfony\Component\Serializer\SerializerInterface;
  16. /**
  17. * Acts as intermediate request forwarder for resource plugins.
  18. *
  19. * @see \Drupal\rest\EventSubscriber\ResourceResponseSubscriber
  20. */
  21. class RequestHandler implements ContainerInjectionInterface {
  22. /**
  23. * The serializer.
  24. *
  25. * @var \Symfony\Component\Serializer\SerializerInterface|\Symfony\Component\Serializer\Encoder\DecoderInterface
  26. */
  27. protected $serializer;
  28. /**
  29. * Creates a new RequestHandler instance.
  30. *
  31. * @param \Symfony\Component\Serializer\SerializerInterface|\Symfony\Component\Serializer\Encoder\DecoderInterface $serializer
  32. * The serializer.
  33. */
  34. public function __construct(SerializerInterface $serializer) {
  35. $this->serializer = $serializer;
  36. }
  37. /**
  38. * {@inheritdoc}
  39. */
  40. public static function create(ContainerInterface $container) {
  41. return new static(
  42. $container->get('serializer')
  43. );
  44. }
  45. /**
  46. * Handles a REST API request.
  47. *
  48. * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
  49. * The route match.
  50. * @param \Symfony\Component\HttpFoundation\Request $request
  51. * The HTTP request object.
  52. * @param \Drupal\rest\RestResourceConfigInterface $_rest_resource_config
  53. * The REST resource config entity.
  54. *
  55. * @return \Drupal\rest\ResourceResponseInterface|\Symfony\Component\HttpFoundation\Response
  56. * The REST resource response.
  57. */
  58. public function handle(RouteMatchInterface $route_match, Request $request, RestResourceConfigInterface $_rest_resource_config) {
  59. $resource = $_rest_resource_config->getResourcePlugin();
  60. $unserialized = $this->deserialize($route_match, $request, $resource);
  61. $response = $this->delegateToRestResourcePlugin($route_match, $request, $unserialized, $resource);
  62. return $this->prepareResponse($response, $_rest_resource_config);
  63. }
  64. /**
  65. * Handles a REST API request without deserializing the request body.
  66. *
  67. * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
  68. * The route match.
  69. * @param \Symfony\Component\HttpFoundation\Request $request
  70. * The HTTP request object.
  71. * @param \Drupal\rest\RestResourceConfigInterface $_rest_resource_config
  72. * The REST resource config entity.
  73. *
  74. * @return \Symfony\Component\HttpFoundation\Response|\Drupal\rest\ResourceResponseInterface
  75. * The REST resource response.
  76. */
  77. public function handleRaw(RouteMatchInterface $route_match, Request $request, RestResourceConfigInterface $_rest_resource_config) {
  78. $resource = $_rest_resource_config->getResourcePlugin();
  79. $response = $this->delegateToRestResourcePlugin($route_match, $request, NULL, $resource);
  80. return $this->prepareResponse($response, $_rest_resource_config);
  81. }
  82. /**
  83. * Prepares the REST resource response.
  84. *
  85. * @param \Drupal\rest\ResourceResponseInterface $response
  86. * The REST resource response.
  87. * @param \Drupal\rest\RestResourceConfigInterface $resource_config
  88. * The REST resource config entity.
  89. *
  90. * @return \Drupal\rest\ResourceResponseInterface
  91. * The prepared REST resource response.
  92. */
  93. protected function prepareResponse($response, RestResourceConfigInterface $resource_config) {
  94. if ($response instanceof CacheableResponseInterface) {
  95. $response->addCacheableDependency($resource_config);
  96. }
  97. return $response;
  98. }
  99. /**
  100. * Gets the normalized HTTP request method of the matched route.
  101. *
  102. * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
  103. * The route match.
  104. *
  105. * @return string
  106. * The normalized HTTP request method.
  107. */
  108. protected static function getNormalizedRequestMethod(RouteMatchInterface $route_match) {
  109. // Symfony is built to transparently map HEAD requests to a GET request. In
  110. // the case of the REST module's RequestHandler though, we essentially have
  111. // our own light-weight routing system on top of the Drupal/symfony routing
  112. // system. So, we have to respect the decision that the routing system made:
  113. // we look not at the request method, but at the route's method. All REST
  114. // routes are guaranteed to have _method set.
  115. // Response::prepare() will transform it to a HEAD response at the very last
  116. // moment.
  117. // @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.4
  118. // @see \Symfony\Component\Routing\Matcher\UrlMatcher::matchCollection()
  119. // @see \Symfony\Component\HttpFoundation\Response::prepare()
  120. $method = strtolower($route_match->getRouteObject()->getMethods()[0]);
  121. assert(count($route_match->getRouteObject()->getMethods()) === 1);
  122. return $method;
  123. }
  124. /**
  125. * Deserializes request body, if any.
  126. *
  127. * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
  128. * The route match.
  129. * @param \Symfony\Component\HttpFoundation\Request $request
  130. * The HTTP request object.
  131. * @param \Drupal\rest\Plugin\ResourceInterface $resource
  132. * The REST resource plugin.
  133. *
  134. * @return array|null
  135. * An object normalization, ikf there is a valid request body. NULL if there
  136. * is no request body.
  137. *
  138. * @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException
  139. * Thrown if the request body cannot be decoded.
  140. * @throws \Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException
  141. * Thrown if the request body cannot be denormalized.
  142. */
  143. protected function deserialize(RouteMatchInterface $route_match, Request $request, ResourceInterface $resource) {
  144. // Deserialize incoming data if available.
  145. $received = $request->getContent();
  146. $unserialized = NULL;
  147. if (!empty($received)) {
  148. $method = static::getNormalizedRequestMethod($route_match);
  149. $format = $request->getContentType();
  150. $definition = $resource->getPluginDefinition();
  151. // First decode the request data. We can then determine if the
  152. // serialized data was malformed.
  153. try {
  154. $unserialized = $this->serializer->decode($received, $format, ['request_method' => $method]);
  155. }
  156. catch (UnexpectedValueException $e) {
  157. // If an exception was thrown at this stage, there was a problem
  158. // decoding the data. Throw a 400 http exception.
  159. throw new BadRequestHttpException($e->getMessage());
  160. }
  161. // Then attempt to denormalize if there is a serialization class.
  162. if (!empty($definition['serialization_class'])) {
  163. try {
  164. $unserialized = $this->serializer->denormalize($unserialized, $definition['serialization_class'], $format, ['request_method' => $method]);
  165. }
  166. // These two serialization exception types mean there was a problem
  167. // with the structure of the decoded data and it's not valid.
  168. catch (UnexpectedValueException $e) {
  169. throw new UnprocessableEntityHttpException($e->getMessage());
  170. }
  171. catch (InvalidArgumentException $e) {
  172. throw new UnprocessableEntityHttpException($e->getMessage());
  173. }
  174. }
  175. }
  176. return $unserialized;
  177. }
  178. /**
  179. * Delegates an incoming request to the appropriate REST resource plugin.
  180. *
  181. * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
  182. * The route match.
  183. * @param \Symfony\Component\HttpFoundation\Request $request
  184. * The HTTP request object.
  185. * @param mixed|null $unserialized
  186. * The unserialized request body, if any.
  187. * @param \Drupal\rest\Plugin\ResourceInterface $resource
  188. * The REST resource plugin.
  189. *
  190. * @return \Symfony\Component\HttpFoundation\Response|\Drupal\rest\ResourceResponseInterface
  191. * The REST resource response.
  192. */
  193. protected function delegateToRestResourcePlugin(RouteMatchInterface $route_match, Request $request, $unserialized, ResourceInterface $resource) {
  194. $method = static::getNormalizedRequestMethod($route_match);
  195. // Determine the request parameters that should be passed to the resource
  196. // plugin.
  197. $argument_resolver = $this->createArgumentResolver($route_match, $unserialized, $request);
  198. $arguments = $argument_resolver->getArguments([$resource, $method]);
  199. // Invoke the operation on the resource plugin.
  200. return call_user_func_array([$resource, $method], $arguments);
  201. }
  202. /**
  203. * Creates an argument resolver, containing all REST parameters.
  204. *
  205. * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
  206. * The route match.
  207. * @param mixed $unserialized
  208. * The unserialized data.
  209. * @param \Symfony\Component\HttpFoundation\Request $request
  210. * The request.
  211. *
  212. * @return \Drupal\Component\Utility\ArgumentsResolver
  213. * An instance of the argument resolver containing information like the
  214. * 'entity' we process and the 'unserialized' content from the request body.
  215. */
  216. protected function createArgumentResolver(RouteMatchInterface $route_match, $unserialized, Request $request) {
  217. $route = $route_match->getRouteObject();
  218. // Defaults for the parameters defined on the route object need to be added
  219. // to the raw arguments.
  220. $raw_route_arguments = $route_match->getRawParameters()->all() + $route->getDefaults();
  221. $route_arguments = $route_match->getParameters()->all();
  222. $upcasted_route_arguments = $route_arguments;
  223. // For request methods that have request bodies, ResourceInterface plugin
  224. // methods historically receive the unserialized request body as the N+1th
  225. // method argument, where N is the number of route parameters specified on
  226. // the accompanying route. To be able to use the argument resolver, which is
  227. // not based on position but on name and typehint, specify commonly used
  228. // names here. Similarly, those methods receive the original stored object
  229. // as the first method argument.
  230. $route_arguments_entity = NULL;
  231. // Try to find a parameter which is an entity.
  232. foreach ($route_arguments as $value) {
  233. if ($value instanceof EntityInterface) {
  234. $route_arguments_entity = $value;
  235. break;
  236. }
  237. }
  238. if (in_array($request->getMethod(), ['PATCH', 'POST'], TRUE)) {
  239. if (is_object($unserialized)) {
  240. $upcasted_route_arguments['entity'] = $unserialized;
  241. $upcasted_route_arguments['data'] = $unserialized;
  242. $upcasted_route_arguments['unserialized'] = $unserialized;
  243. }
  244. else {
  245. $raw_route_arguments['data'] = $unserialized;
  246. $raw_route_arguments['unserialized'] = $unserialized;
  247. }
  248. $upcasted_route_arguments['original_entity'] = $route_arguments_entity;
  249. }
  250. else {
  251. $upcasted_route_arguments['entity'] = $route_arguments_entity;
  252. }
  253. // Parameters which are not defined on the route object, but still are
  254. // essential for access checking are passed as wildcards to the argument
  255. // resolver.
  256. $wildcard_arguments = [$route, $route_match];
  257. $wildcard_arguments[] = $request;
  258. if (isset($unserialized)) {
  259. $wildcard_arguments[] = $unserialized;
  260. }
  261. return new ArgumentsResolver($raw_route_arguments, $upcasted_route_arguments, $wildcard_arguments);
  262. }
  263. }