diff --git a/core/modules/rest/src/RequestHandler.php b/core/modules/rest/src/RequestHandler.php index e5437ccb68..aea0509fea 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,91 @@ 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; + + // 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. + if (in_array($request->getMethod(), ['PATCH', 'POST'], TRUE)) { + $upcasted_route_arguments['entity'] = $unserialized; + $upcasted_route_arguments['data'] = $unserialized; + $upcasted_route_arguments['unserialized'] = $unserialized; + + // Try to find a parameter which is an entity. + foreach ($route_arguments as $value) { + if ($value instanceof EntityInterface) { + $upcasted_route_arguments['original_entity'] = $value; + } + } + } + else { + // Try to find a parameter which is an entity. + foreach ($route_arguments as $value) { + if ($value instanceof EntityInterface) { + $upcasted_route_arguments['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]); + } + } diff --git a/core/modules/rest/tests/modules/rest_test/src/Plugin/rest/resource/NoSerializationClassTestResource.php b/core/modules/rest/tests/modules/rest_test/src/Plugin/rest/resource/NoSerializationClassTestResource.php index 3e83a4ce74..2689588481 100644 --- a/core/modules/rest/tests/modules/rest_test/src/Plugin/rest/resource/NoSerializationClassTestResource.php +++ b/core/modules/rest/tests/modules/rest_test/src/Plugin/rest/resource/NoSerializationClassTestResource.php @@ -25,7 +25,7 @@ class NoSerializationClassTestResource extends ResourceBase { * * @return \Drupal\rest\ResourceResponse */ - public function post(array $data = []) { + public function post(array $data) { return new ResourceResponse($data); } diff --git a/core/modules/rest/tests/src/Kernel/RequestHandlerTest.php b/core/modules/rest/tests/src/Kernel/RequestHandlerTest.php index 2b8855917e..110b19745d 100644 --- a/core/modules/rest/tests/src/Kernel/RequestHandlerTest.php +++ b/core/modules/rest/tests/src/Kernel/RequestHandlerTest.php @@ -95,7 +95,7 @@ class StubRequestHandlerResourcePlugin extends ResourceBase { public function get() {} public function post() {} - public function patch() {} + public function patch($original, Request $request) {} public function delete() {} }