Mercurial > hg > isophonics-drupal-site
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/modules/rest/src/RequestHandler.php Wed Nov 29 16:09:58 2017 +0000 @@ -0,0 +1,142 @@ +<?php + +namespace Drupal\rest; + +use Drupal\Core\Cache\CacheableResponseInterface; +use Drupal\Core\DependencyInjection\ContainerInjectionInterface; +use Drupal\Core\Entity\EntityStorageInterface; +use Drupal\Core\Routing\RouteMatchInterface; +use Symfony\Component\DependencyInjection\ContainerAwareInterface; +use Symfony\Component\DependencyInjection\ContainerAwareTrait; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; +use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException; +use Symfony\Component\Serializer\Exception\UnexpectedValueException; +use Symfony\Component\Serializer\Exception\InvalidArgumentException; + +/** + * Acts as intermediate request forwarder for resource plugins. + * + * @see \Drupal\rest\EventSubscriber\ResourceResponseSubscriber + */ +class RequestHandler implements ContainerAwareInterface, ContainerInjectionInterface { + + use ContainerAwareTrait; + + /** + * The resource configuration storage. + * + * @var \Drupal\Core\Entity\EntityStorageInterface + */ + protected $resourceStorage; + + /** + * Creates a new RequestHandler instance. + * + * @param \Drupal\Core\Entity\EntityStorageInterface $entity_storage + * The resource configuration storage. + */ + public function __construct(EntityStorageInterface $entity_storage) { + $this->resourceStorage = $entity_storage; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static($container->get('entity_type.manager')->getStorage('rest_resource_config')); + } + + /** + * Handles a web API request. + * + * @param \Drupal\Core\Routing\RouteMatchInterface $route_match + * The route match. + * @param \Symfony\Component\HttpFoundation\Request $request + * The HTTP request object. + * + * @return \Symfony\Component\HttpFoundation\Response + * The response object. + */ + public function handle(RouteMatchInterface $route_match, Request $request) { + // Symfony is built to transparently map HEAD requests to a GET request. In + // the case of the REST module's RequestHandler though, we essentially have + // our own light-weight routing system on top of the Drupal/symfony routing + // system. So, we have to respect the decision that the routing system made: + // we look not at the request method, but at the route's method. All REST + // routes are guaranteed to have _method set. + // Response::prepare() will transform it to a HEAD response at the very last + // moment. + // @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.4 + // @see \Symfony\Component\Routing\Matcher\UrlMatcher::matchCollection() + // @see \Symfony\Component\HttpFoundation\Response::prepare() + $method = strtolower($route_match->getRouteObject()->getMethods()[0]); + assert(count($route_match->getRouteObject()->getMethods()) === 1); + + + $resource_config_id = $route_match->getRouteObject()->getDefault('_rest_resource_config'); + /** @var \Drupal\rest\RestResourceConfigInterface $resource_config */ + $resource_config = $this->resourceStorage->load($resource_config_id); + $resource = $resource_config->getResourcePlugin(); + + // Deserialize incoming data if available. + /** @var \Symfony\Component\Serializer\SerializerInterface $serializer */ + $serializer = $this->container->get('serializer'); + $received = $request->getContent(); + $unserialized = NULL; + if (!empty($received)) { + $format = $request->getContentType(); + + $definition = $resource->getPluginDefinition(); + + // First decode the request data. We can then determine if the + // serialized data was malformed. + try { + $unserialized = $serializer->decode($received, $format, ['request_method' => $method]); + } + catch (UnexpectedValueException $e) { + // If an exception was thrown at this stage, there was a problem + // decoding the data. Throw a 400 http exception. + throw new BadRequestHttpException($e->getMessage()); + } + + // Then attempt to denormalize if there is a serialization class. + if (!empty($definition['serialization_class'])) { + try { + $unserialized = $serializer->denormalize($unserialized, $definition['serialization_class'], $format, ['request_method' => $method]); + } + // These two serialization exception types mean there was a problem + // with the structure of the decoded data and it's not valid. + catch (UnexpectedValueException $e) { + throw new UnprocessableEntityHttpException($e->getMessage()); + } + catch (InvalidArgumentException $e) { + throw new UnprocessableEntityHttpException($e->getMessage()); + } + } + } + + // Determine the request parameters that should be passed to the resource + // plugin. + $route_parameters = $route_match->getParameters(); + $parameters = []; + // Filter out all internal parameters starting with "_". + foreach ($route_parameters as $key => $parameter) { + if ($key{0} !== '_') { + $parameters[] = $parameter; + } + } + + // Invoke the operation on the resource plugin. + $response = call_user_func_array([$resource, $method], array_merge($parameters, [$unserialized, $request])); + + if ($response instanceof CacheableResponseInterface) { + // Add rest config's cache tags. + $response->addCacheableDependency($resource_config); + } + + return $response; + } + +}