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