class RequestHandler

Same name in other branches
  1. 8.9.x core/modules/rest/src/RequestHandler.php \Drupal\rest\RequestHandler
  2. 10 core/modules/rest/src/RequestHandler.php \Drupal\rest\RequestHandler
  3. 11.x core/modules/rest/src/RequestHandler.php \Drupal\rest\RequestHandler

Acts as intermediate request forwarder for resource plugins.

Hierarchy

Expanded class hierarchy of RequestHandler

See also

\Drupal\rest\EventSubscriber\ResourceResponseSubscriber

2 files declare their use of RequestHandler
FileUploadResource.php in core/modules/file/src/Plugin/rest/resource/FileUploadResource.php
RequestHandlerTest.php in core/modules/rest/tests/src/Kernel/RequestHandlerTest.php

File

core/modules/rest/src/RequestHandler.php, line 24

Namespace

Drupal\rest
View source
class RequestHandler implements ContainerInjectionInterface {
    
    /**
     * The serializer.
     *
     * @var \Symfony\Component\Serializer\SerializerInterface|\Symfony\Component\Serializer\Encoder\DecoderInterface
     */
    protected $serializer;
    
    /**
     * Creates a new RequestHandler instance.
     *
     * @param \Symfony\Component\Serializer\SerializerInterface|\Symfony\Component\Serializer\Encoder\DecoderInterface $serializer
     *   The serializer.
     */
    public function __construct(SerializerInterface $serializer) {
        $this->serializer = $serializer;
    }
    
    /**
     * {@inheritdoc}
     */
    public static function create(ContainerInterface $container) {
        return new static($container->get('serializer'));
    }
    
    /**
     * Handles a REST API request.
     *
     * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
     *   The route match.
     * @param \Symfony\Component\HttpFoundation\Request $request
     *   The HTTP request object.
     * @param \Drupal\rest\RestResourceConfigInterface $_rest_resource_config
     *   The REST resource config entity.
     *
     * @return \Drupal\rest\ResourceResponseInterface|\Symfony\Component\HttpFoundation\Response
     *   The REST resource response.
     */
    public function handle(RouteMatchInterface $route_match, Request $request, RestResourceConfigInterface $_rest_resource_config) {
        $resource = $_rest_resource_config->getResourcePlugin();
        $unserialized = $this->deserialize($route_match, $request, $resource);
        $response = $this->delegateToRestResourcePlugin($route_match, $request, $unserialized, $resource);
        return $this->prepareResponse($response, $_rest_resource_config);
    }
    
    /**
     * Handles a REST API request without deserializing the request body.
     *
     * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
     *   The route match.
     * @param \Symfony\Component\HttpFoundation\Request $request
     *   The HTTP request object.
     * @param \Drupal\rest\RestResourceConfigInterface $_rest_resource_config
     *   The REST resource config entity.
     *
     * @return \Symfony\Component\HttpFoundation\Response|\Drupal\rest\ResourceResponseInterface
     *   The REST resource response.
     */
    public function handleRaw(RouteMatchInterface $route_match, Request $request, RestResourceConfigInterface $_rest_resource_config) {
        $resource = $_rest_resource_config->getResourcePlugin();
        $response = $this->delegateToRestResourcePlugin($route_match, $request, NULL, $resource);
        return $this->prepareResponse($response, $_rest_resource_config);
    }
    
    /**
     * Prepares the REST resource response.
     *
     * @param \Drupal\rest\ResourceResponseInterface $response
     *   The REST resource response.
     * @param \Drupal\rest\RestResourceConfigInterface $resource_config
     *   The REST resource config entity.
     *
     * @return \Drupal\rest\ResourceResponseInterface
     *   The prepared REST resource response.
     */
    protected function prepareResponse($response, RestResourceConfigInterface $resource_config) {
        if ($response instanceof CacheableResponseInterface) {
            $response->addCacheableDependency($resource_config);
        }
        return $response;
    }
    
    /**
     * Gets the normalized HTTP request method of the matched route.
     *
     * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
     *   The route match.
     *
     * @return string
     *   The normalized HTTP request method.
     */
    protected static function getNormalizedRequestMethod(RouteMatchInterface $route_match) {
        // 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);
        return $method;
    }
    
    /**
     * Deserializes request body, if any.
     *
     * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
     *   The route match.
     * @param \Symfony\Component\HttpFoundation\Request $request
     *   The HTTP request object.
     * @param \Drupal\rest\Plugin\ResourceInterface $resource
     *   The REST resource plugin.
     *
     * @return array|null
     *   An object normalization, ikf there is a valid request body. NULL if there
     *   is no request body.
     *
     * @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException
     *   Thrown if the request body cannot be decoded.
     * @throws \Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException
     *   Thrown if the request body cannot be denormalized.
     */
    protected function deserialize(RouteMatchInterface $route_match, Request $request, ResourceInterface $resource) {
        // Deserialize incoming data if available.
        $received = $request->getContent();
        $unserialized = NULL;
        if (!empty($received)) {
            $method = static::getNormalizedRequestMethod($route_match);
            $format = $request->getContentType();
            $definition = $resource->getPluginDefinition();
            // First decode the request data. We can then determine if the
            // serialized data was malformed.
            try {
                $unserialized = $this->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 = $this->serializer
                        ->denormalize($unserialized, $definition['serialization_class'], $format, [
                        'request_method' => $method,
                    ]);
                } catch (UnexpectedValueException $e) {
                    throw new UnprocessableEntityHttpException($e->getMessage());
                } catch (InvalidArgumentException $e) {
                    throw new UnprocessableEntityHttpException($e->getMessage());
                }
            }
        }
        return $unserialized;
    }
    
    /**
     * Delegates an incoming request to the appropriate REST resource plugin.
     *
     * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
     *   The route match.
     * @param \Symfony\Component\HttpFoundation\Request $request
     *   The HTTP request object.
     * @param mixed|null $unserialized
     *   The unserialized request body, if any.
     * @param \Drupal\rest\Plugin\ResourceInterface $resource
     *   The REST resource plugin.
     *
     * @return \Symfony\Component\HttpFoundation\Response|\Drupal\rest\ResourceResponseInterface
     *   The REST resource response.
     */
    protected function delegateToRestResourcePlugin(RouteMatchInterface $route_match, Request $request, $unserialized, ResourceInterface $resource) {
        $method = static::getNormalizedRequestMethod($route_match);
        // Determine the request parameters that should be passed to the resource
        // plugin.
        $argument_resolver = $this->createArgumentResolver($route_match, $unserialized, $request);
        $arguments = $argument_resolver->getArguments([
            $resource,
            $method,
        ]);
        // Invoke the operation on the resource plugin.
        return call_user_func_array([
            $resource,
            $method,
        ], $arguments);
    }
    
    /**
     * Creates an argument resolver, containing all REST parameters.
     *
     * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
     *   The route match.
     * @param mixed $unserialized
     *   The unserialized data.
     * @param \Symfony\Component\HttpFoundation\Request $request
     *   The request.
     *
     * @return \Drupal\Component\Utility\ArgumentsResolver
     *   An instance of the argument resolver containing information like the
     *   'entity' we process and the 'unserialized' content from the request body.
     */
    protected function createArgumentResolver(RouteMatchInterface $route_match, $unserialized, Request $request) {
        $route = $route_match->getRouteObject();
        // Defaults for the parameters defined on the route object need to be added
        // to the raw arguments.
        $raw_route_arguments = $route_match->getRawParameters()
            ->all() + $route->getDefaults();
        $route_arguments = $route_match->getParameters()
            ->all();
        $upcasted_route_arguments = $route_arguments;
        // For request methods that have request bodies, ResourceInterface plugin
        // methods historically receive the unserialized request body as the N+1th
        // method argument, where N is the number of route parameters specified on
        // the accompanying route. To be able to use the argument resolver, which is
        // not based on position but on name and typehint, specify commonly used
        // names here. Similarly, those methods receive the original stored object
        // as the first method argument.
        $route_arguments_entity = NULL;
        // Try to find a parameter which is an entity.
        foreach ($route_arguments as $value) {
            if ($value instanceof EntityInterface) {
                $route_arguments_entity = $value;
                break;
            }
        }
        if (in_array($request->getMethod(), [
            'PATCH',
            'POST',
        ], TRUE)) {
            if (is_object($unserialized)) {
                $upcasted_route_arguments['entity'] = $unserialized;
                $upcasted_route_arguments['data'] = $unserialized;
                $upcasted_route_arguments['unserialized'] = $unserialized;
            }
            else {
                $raw_route_arguments['data'] = $unserialized;
                $raw_route_arguments['unserialized'] = $unserialized;
            }
            $upcasted_route_arguments['original_entity'] = $route_arguments_entity;
        }
        else {
            $upcasted_route_arguments['entity'] = $route_arguments_entity;
        }
        // Parameters which are not defined on the route object, but still are
        // essential for access checking are passed as wildcards to the argument
        // resolver.
        $wildcard_arguments = [
            $route,
            $route_match,
        ];
        $wildcard_arguments[] = $request;
        if (isset($unserialized)) {
            $wildcard_arguments[] = $unserialized;
        }
        return new ArgumentsResolver($raw_route_arguments, $upcasted_route_arguments, $wildcard_arguments);
    }

}

Members

Title Sort descending Modifiers Object type Summary Overriden Title
RequestHandler::$serializer protected property The serializer.
RequestHandler::create public static function Instantiates a new instance of this class. Overrides ContainerInjectionInterface::create
RequestHandler::createArgumentResolver protected function Creates an argument resolver, containing all REST parameters.
RequestHandler::delegateToRestResourcePlugin protected function Delegates an incoming request to the appropriate REST resource plugin.
RequestHandler::deserialize protected function Deserializes request body, if any.
RequestHandler::getNormalizedRequestMethod protected static function Gets the normalized HTTP request method of the matched route.
RequestHandler::handle public function Handles a REST API request.
RequestHandler::handleRaw public function Handles a REST API request without deserializing the request body.
RequestHandler::prepareResponse protected function Prepares the REST resource response.
RequestHandler::__construct public function Creates a new RequestHandler instance.

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.