diff --git a/core/modules/rest/src/RequestHandler.php b/core/modules/rest/src/RequestHandler.php index e5437cc..ca8318e 100644 --- a/core/modules/rest/src/RequestHandler.php +++ b/core/modules/rest/src/RequestHandler.php @@ -2,8 +2,10 @@ namespace Drupal\rest; +use Drupal\Component\Utility\ArgumentsResolver; use Drupal\Core\Cache\CacheableResponseInterface; use Drupal\Core\DependencyInjection\ContainerInjectionInterface; +use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Routing\RouteMatchInterface; use Symfony\Component\DependencyInjection\ContainerAwareInterface; @@ -119,17 +121,16 @@ public function handle(RouteMatchInterface $route_match, Request $request) { // 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; - } + $argument_resolver = $this->getArgumentResolver($route_match, $unserialized, $request); + try { + $arguments = $argument_resolver->getArguments([$resource, $method]); + } + catch (\RuntimeException $exception) { + $arguments = $this->getLegacyParameters($route_match, $unserialized, $request); } // Invoke the operation on the resource plugin. - $response = call_user_func_array([$resource, $method], array_merge($parameters, [$unserialized, $request])); + $response = $response = call_user_func_array([$resource, $method], $arguments); if ($response instanceof CacheableResponseInterface) { // Add rest config's cache tags. @@ -139,4 +140,96 @@ public function handle(RouteMatchInterface $route_match, Request $request) { return $response; } + /** + * Creates an argument resolver, which can pass along the 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 + */ + protected function getArgumentResolver(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; + + // \Drupal\rest\Plugin\ResourceInterface plugins 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. 0 or 1 of these will + // be used. + if (isset($unserialized)) { + $upcasted_route_arguments['entity'] = $unserialized; + $upcasted_route_arguments['data'] = $unserialized; + $upcasted_route_arguments['unserialized'] = $unserialized; + } + + // In the case of GET the entity is stored in $upcasted_route_arguments in + // the $entity_type_id key, not as 'entity', which + // \Drupal\rest\Plugin\rest\resource\EntityResource::get() expects as their name. + if (!in_array($request->getMethod(), ['PATCH', 'POST'], TRUE) && empty($upcasted_route_arguments['entity'])) { + // Try to find a parameter which is an entity. + foreach ($route_arguments as $value) { + if ($value instanceof EntityInterface) { + $upcasted_route_arguments['entity'] = $value; + } + } + } + + if (in_array($request->getMethod(), ['PATCH', 'POST'], TRUE) && empty($upcasted_route_arguments['original_entity'])) { + // Try to find a parameter which is an entity. + foreach ($route_arguments as $value) { + if ($value instanceof EntityInterface) { + $upcasted_route_arguments['original_entity'] = $value; + } + } + } + + // 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]; + if (isset($request)) { + $wildcard_arguments[] = $request; + } + if (isset($unserialized)) { + $wildcard_arguments[] = $unserialized; + } + + return new ArgumentsResolver($raw_route_arguments, $upcasted_route_arguments, $wildcard_arguments); + } + + /** + * @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 array + */ + protected function getLegacyParameters(RouteMatchInterface $route_match, $unserialized, Request $request) { + $route_parameters = $route_match->getParameters(); + $parameters = []; + // Filter out all internal parameters starting with "_". + foreach ($route_parameters as $key => $parameter) { + if ($key{0} !== '_') { + $parameters[] = $parameter; + } + } + + return array_merge($parameters, [$unserialized, $request]); + } + }