comparison core/modules/rest/src/RequestHandler.php @ 0:4c8ae668cc8c

Initial import (non-working)
author Chris Cannam
date Wed, 29 Nov 2017 16:09:58 +0000
parents
children 7a779792577d
comparison
equal deleted inserted replaced
-1:000000000000 0:4c8ae668cc8c
1 <?php
2
3 namespace Drupal\rest;
4
5 use Drupal\Core\Cache\CacheableResponseInterface;
6 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
7 use Drupal\Core\Entity\EntityStorageInterface;
8 use Drupal\Core\Routing\RouteMatchInterface;
9 use Symfony\Component\DependencyInjection\ContainerAwareInterface;
10 use Symfony\Component\DependencyInjection\ContainerAwareTrait;
11 use Symfony\Component\DependencyInjection\ContainerInterface;
12 use Symfony\Component\HttpFoundation\Request;
13 use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
14 use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
15 use Symfony\Component\Serializer\Exception\UnexpectedValueException;
16 use Symfony\Component\Serializer\Exception\InvalidArgumentException;
17
18 /**
19 * Acts as intermediate request forwarder for resource plugins.
20 *
21 * @see \Drupal\rest\EventSubscriber\ResourceResponseSubscriber
22 */
23 class RequestHandler implements ContainerAwareInterface, ContainerInjectionInterface {
24
25 use ContainerAwareTrait;
26
27 /**
28 * The resource configuration storage.
29 *
30 * @var \Drupal\Core\Entity\EntityStorageInterface
31 */
32 protected $resourceStorage;
33
34 /**
35 * Creates a new RequestHandler instance.
36 *
37 * @param \Drupal\Core\Entity\EntityStorageInterface $entity_storage
38 * The resource configuration storage.
39 */
40 public function __construct(EntityStorageInterface $entity_storage) {
41 $this->resourceStorage = $entity_storage;
42 }
43
44 /**
45 * {@inheritdoc}
46 */
47 public static function create(ContainerInterface $container) {
48 return new static($container->get('entity_type.manager')->getStorage('rest_resource_config'));
49 }
50
51 /**
52 * Handles a web API request.
53 *
54 * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
55 * The route match.
56 * @param \Symfony\Component\HttpFoundation\Request $request
57 * The HTTP request object.
58 *
59 * @return \Symfony\Component\HttpFoundation\Response
60 * The response object.
61 */
62 public function handle(RouteMatchInterface $route_match, Request $request) {
63 // Symfony is built to transparently map HEAD requests to a GET request. In
64 // the case of the REST module's RequestHandler though, we essentially have
65 // our own light-weight routing system on top of the Drupal/symfony routing
66 // system. So, we have to respect the decision that the routing system made:
67 // we look not at the request method, but at the route's method. All REST
68 // routes are guaranteed to have _method set.
69 // Response::prepare() will transform it to a HEAD response at the very last
70 // moment.
71 // @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.4
72 // @see \Symfony\Component\Routing\Matcher\UrlMatcher::matchCollection()
73 // @see \Symfony\Component\HttpFoundation\Response::prepare()
74 $method = strtolower($route_match->getRouteObject()->getMethods()[0]);
75 assert(count($route_match->getRouteObject()->getMethods()) === 1);
76
77
78 $resource_config_id = $route_match->getRouteObject()->getDefault('_rest_resource_config');
79 /** @var \Drupal\rest\RestResourceConfigInterface $resource_config */
80 $resource_config = $this->resourceStorage->load($resource_config_id);
81 $resource = $resource_config->getResourcePlugin();
82
83 // Deserialize incoming data if available.
84 /** @var \Symfony\Component\Serializer\SerializerInterface $serializer */
85 $serializer = $this->container->get('serializer');
86 $received = $request->getContent();
87 $unserialized = NULL;
88 if (!empty($received)) {
89 $format = $request->getContentType();
90
91 $definition = $resource->getPluginDefinition();
92
93 // First decode the request data. We can then determine if the
94 // serialized data was malformed.
95 try {
96 $unserialized = $serializer->decode($received, $format, ['request_method' => $method]);
97 }
98 catch (UnexpectedValueException $e) {
99 // If an exception was thrown at this stage, there was a problem
100 // decoding the data. Throw a 400 http exception.
101 throw new BadRequestHttpException($e->getMessage());
102 }
103
104 // Then attempt to denormalize if there is a serialization class.
105 if (!empty($definition['serialization_class'])) {
106 try {
107 $unserialized = $serializer->denormalize($unserialized, $definition['serialization_class'], $format, ['request_method' => $method]);
108 }
109 // These two serialization exception types mean there was a problem
110 // with the structure of the decoded data and it's not valid.
111 catch (UnexpectedValueException $e) {
112 throw new UnprocessableEntityHttpException($e->getMessage());
113 }
114 catch (InvalidArgumentException $e) {
115 throw new UnprocessableEntityHttpException($e->getMessage());
116 }
117 }
118 }
119
120 // Determine the request parameters that should be passed to the resource
121 // plugin.
122 $route_parameters = $route_match->getParameters();
123 $parameters = [];
124 // Filter out all internal parameters starting with "_".
125 foreach ($route_parameters as $key => $parameter) {
126 if ($key{0} !== '_') {
127 $parameters[] = $parameter;
128 }
129 }
130
131 // Invoke the operation on the resource plugin.
132 $response = call_user_func_array([$resource, $method], array_merge($parameters, [$unserialized, $request]));
133
134 if ($response instanceof CacheableResponseInterface) {
135 // Add rest config's cache tags.
136 $response->addCacheableDependency($resource_config);
137 }
138
139 return $response;
140 }
141
142 }