annotate 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
rev   line source
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 }