diff --git a/core/modules/rest/src/Entity/RestEndpoint.php b/core/modules/rest/src/Entity/RestEndpoint.php index d863121..0defe31 100644 --- a/core/modules/rest/src/Entity/RestEndpoint.php +++ b/core/modules/rest/src/Entity/RestEndpoint.php @@ -15,6 +15,7 @@ use Symfony\Component\DependencyInjection\ContainerAwareTrait; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\TaggedContainerInterface; +use Symfony\Component\Routing\RouteCollection; /** * Defines a RestEndpoint configuration entity class. @@ -61,8 +62,16 @@ class RestEndpoint extends ConfigEntityBase implements RestEndpointInterface { */ protected $pluginManager; + /** + * A logger instance. + * + * @var \Psr\Log\LoggerInterface + */ + protected $logger; + public function __construct(array $values, $entity_type) { $this->pluginManager = \Drupal::service('plugin.manager.rest'); + $this->logger = \Drupal::service('logger.factory')->get('rest'); $this->setContainer(\Drupal::getContainer()); parent::__construct($values, $entity_type); // The config entity id looks like the plugin id but uses _ instead of : because : is not valid for config entities. @@ -225,6 +234,79 @@ public function removeSupportedFormat($method, $format) { } /** + * {@inheritdoc} + */ + public function routes() { + $collection = new RouteCollection(); + + $plugin = $this->getResourcePlugin(); + $plugin_id = $plugin->getPluginId(); + $plugin_def = $plugin->getPluginDefinition(); + + $canonical_path = isset($plugin_def['uri_paths']['canonical']) ? $plugin_def['uri_paths']['canonical'] : '/' . strtr($plugin_id, ':', '/') . '/{id}'; + $create_path = isset($definition['uri_paths']['http://drupal.org/link-relations/create']) ? $definition['uri_paths']['http://drupal.org/link-relations/create'] : '/' . strtr($plugin_id, ':', '/'); + + $route_name = strtr($plugin_id, ':', '.'); + + $methods = $plugin->availableMethods(); + foreach ($methods as $method) { + if ($this->isRequestMethodEnabled($method)) { + // Check that authentication providers are defined. + if (!$this->supportsAuthenticationProviders($method)) { + $this->logger->error('At least one authentication provider must be defined for resource @id', array(':id' => $plugin_id)); + continue; + } + + // Check that formats are defined. + if (!$this->hasSupportedFormats($method)) { + $this->logger->error('At least one format must be defined for resource @id', array(':id' => $plugin_id)); + continue; + } + + $route = $plugin->getBaseRoute($canonical_path, $method); + $route->setRequirement('_access_rest_csrf', 'TRUE'); + $route->setOption('_auth', $this->getSupportedAuthenticationProviders($method)); + $route->setDefault('_rest_endpoint', $this->id()); + + $supported_formats = $this->getSupportedFormats($method); + switch ($method) { + case 'POST': + $route->setPath($create_path); + // Restrict the incoming HTTP Content-type header to the known + // serialization formats. + $route->addRequirements(array('_content_type_format' => implode('|', $supported_formats))); + $collection->add("rest.$route_name.$method", $route); + break; + case 'PATCH': + // Restrict the incoming HTTP Content-type header to the known + // serialization formats. + $route->addRequirements(array('_content_type_format' => implode('|', $supported_formats))); + $collection->add("rest.$route_name.$method", $route); + break; + + case 'GET': + case 'HEAD': + // Restrict GET and HEAD requests to the media type specified in the + // HTTP Accept headers. + foreach ($supported_formats as $format_name) { + // Expose one route per available format. + $format_route = clone $route; + $format_route->addRequirements(array('_format' => $format_name)); + $collection->add("rest.$route_name.$method.$format_name", $format_route); + } + break; + + default: + $collection->add("rest.$route_name.$method", $route); + break; + } + } + } + + return $collection; + } + + /** * Returns the plugin collections used by this entity. * * @return \Drupal\Component\Plugin\LazyPluginCollection[] @@ -238,7 +320,7 @@ public function getPluginCollections() { } /** - * @return array + * (@inheritdoc) */ public function calculateDependencies() { parent::calculateDependencies(); diff --git a/core/modules/rest/src/Plugin/ResourceBase.php b/core/modules/rest/src/Plugin/ResourceBase.php index 46f77f6..3a4bb7b 100644 --- a/core/modules/rest/src/Plugin/ResourceBase.php +++ b/core/modules/rest/src/Plugin/ResourceBase.php @@ -94,59 +94,6 @@ public function permissions() { } /** - * Implements ResourceInterface::routes(). - */ - public function routes() { - $collection = new RouteCollection(); - - $definition = $this->getPluginDefinition(); - $canonical_path = isset($definition['uri_paths']['canonical']) ? $definition['uri_paths']['canonical'] : '/' . strtr($this->pluginId, ':', '/') . '/{id}'; - $create_path = isset($definition['uri_paths']['http://drupal.org/link-relations/create']) ? $definition['uri_paths']['http://drupal.org/link-relations/create'] : '/' . strtr($this->pluginId, ':', '/'); - - $route_name = strtr($this->pluginId, ':', '.'); - - $methods = $this->availableMethods(); - foreach ($methods as $method) { - $route = $this->getBaseRoute($canonical_path, $method); - - switch ($method) { - case 'POST': - $route->setPattern($create_path); - // Restrict the incoming HTTP Content-type header to the known - // serialization formats. - $route->addRequirements(array('_content_type_format' => implode('|', $this->serializerFormats))); - $collection->add("$route_name.$method", $route); - break; - - case 'PATCH': - // Restrict the incoming HTTP Content-type header to the known - // serialization formats. - $route->addRequirements(array('_content_type_format' => implode('|', $this->serializerFormats))); - $collection->add("$route_name.$method", $route); - break; - - case 'GET': - case 'HEAD': - // Restrict GET and HEAD requests to the media type specified in the - // HTTP Accept headers. - foreach ($this->serializerFormats as $format_name) { - // Expose one route per available format. - $format_route = clone $route; - $format_route->addRequirements(array('_format' => $format_name)); - $collection->add("$route_name.$method.$format_name", $format_route); - } - break; - - default: - $collection->add("$route_name.$method", $route); - break; - } - } - - return $collection; - } - - /** * Provides predefined HTTP request methods. * * Plugins can override this method to provide additional custom request @@ -170,7 +117,7 @@ protected function requestMethods() { } /** - * Implements ResourceInterface::availableMethods(). + * {@inheritdoc} */ public function availableMethods() { $methods = $this->requestMethods(); @@ -185,23 +132,13 @@ public function availableMethods() { } /** - * Setups the base route for all HTTP methods. - * - * @param string $canonical_path - * The canonical path for the resource. - * @param string $method - * The HTTP method to be used for the route. - * - * @return \Symfony\Component\Routing\Route - * The created base route. + * {@inheritdoc} */ - protected function getBaseRoute($canonical_path, $method) { + public function getBaseRoute($canonical_path, $method) { $lower_method = strtolower($method); $route = new Route($canonical_path, array( '_controller' => 'Drupal\rest\RequestHandler::handle', - // Pass the resource plugin ID along as default property. - '_plugin' => $this->pluginId, ), array( // The HTTP method is a requirement for this route. '_method' => $method, diff --git a/core/modules/rest/src/Plugin/ResourceInterface.php b/core/modules/rest/src/Plugin/ResourceInterface.php index 2aef8324..847729c 100644 --- a/core/modules/rest/src/Plugin/ResourceInterface.php +++ b/core/modules/rest/src/Plugin/ResourceInterface.php @@ -8,6 +8,7 @@ namespace Drupal\rest\Plugin; use Drupal\Component\Plugin\PluginInspectionInterface; +use Symfony\Component\Routing\Route; /** * Specifies the publicly available methods of a resource plugin. @@ -20,18 +21,6 @@ * @ingroup third_party */ interface ResourceInterface extends PluginInspectionInterface { - - /** - * Returns a collection of routes with URL path information for the resource. - * - * This method determines where a resource is reachable, what path - * replacements are used, the required HTTP method for the operation etc. - * - * @return \Symfony\Component\Routing\RouteCollection - * A collection of routes that should be registered for this resource. - */ - public function routes(); - /** * Provides an array of permissions suitable for .permissions.yml files. * @@ -51,4 +40,16 @@ public function permissions(); */ public function availableMethods(); + /** + * Setups the base route for all HTTP methods. + * + * @param string $canonical_path + * The canonical path for the resource. + * @param string $method + * The HTTP method to be used for the route. + * + * @return \Symfony\Component\Routing\Route + * The created base route. + */ + public function getBaseRoute($canonical_path, $method); } diff --git a/core/modules/rest/src/Plugin/rest/resource/EntityResource.php b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php index 7dcec34..8ddff25 100644 --- a/core/modules/rest/src/Plugin/rest/resource/EntityResource.php +++ b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php @@ -267,7 +267,7 @@ protected function validate(EntityInterface $entity) { /** * {@inheritdoc} */ - protected function getBaseRoute($canonical_path, $method) { + public function getBaseRoute($canonical_path, $method) { $route = parent::getBaseRoute($canonical_path, $method); $definition = $this->getPluginDefinition(); diff --git a/core/modules/rest/src/RequestHandler.php b/core/modules/rest/src/RequestHandler.php index ea69fea..33933fa 100644 --- a/core/modules/rest/src/RequestHandler.php +++ b/core/modules/rest/src/RequestHandler.php @@ -38,18 +38,11 @@ class RequestHandler implements ContainerAwareInterface { * The response object. */ public function handle(RouteMatchInterface $route_match, Request $request) { - - $plugin = $route_match->getRouteObject()->getDefault('_plugin'); $method = strtolower($request->getMethod()); - - /** @var \Drupal\rest\Plugin\ResourceInterface $resource */ - $resource = $this->container - ->get('plugin.manager.rest') - ->getInstance(array('id' => $plugin)); - + $endpoint_id = $route_match->getRouteObject()->getDefault('_rest_endpoint'); /** @var \Drupal\rest\RestEndpointInterface $endpoint */ - $endpoint_id = str_replace(':', '__', $plugin); $endpoint = RestEndpoint::load($endpoint_id); + $resource = $endpoint->getResourcePlugin(); // Deserialize incoming data if available. $serializer = $this->container->get('serializer'); diff --git a/core/modules/rest/src/RestEndpointInterface.php b/core/modules/rest/src/RestEndpointInterface.php index 5446075..30d14b6 100644 --- a/core/modules/rest/src/RestEndpointInterface.php +++ b/core/modules/rest/src/RestEndpointInterface.php @@ -125,4 +125,16 @@ public function addSupportedFormat($method, $format); * @return $this */ public function removeSupportedFormat($method, $format); + + + /** + * Returns a collection of routes with URL path information for this REST endpoint. + * + * This method determines where a resource is reachable, what path + * replacements are used, the required HTTP method for the operation etc. + * + * @return \Symfony\Component\Routing\RouteCollection + * A collection of routes that should be registered for this REST endpoint. + */ + public function routes(); } diff --git a/core/modules/rest/src/Routing/ResourceRoutes.php b/core/modules/rest/src/Routing/ResourceRoutes.php index b2394fb..f52ec82 100644 --- a/core/modules/rest/src/Routing/ResourceRoutes.php +++ b/core/modules/rest/src/Routing/ResourceRoutes.php @@ -63,47 +63,12 @@ public function __construct(ResourcePluginManager $manager, EntityManagerInterfa * @return array */ protected function alterRoutes(RouteCollection $collection) { - $routes = array(); - - // Iterate over all enabled resource plugins. + // Iterate over all enabled REST endpoints. /** @var \Drupal\rest\RestEndpointInterface[] $endpoints */ $endpoints = $this->endpoint_storage->loadMultiple(); foreach ($endpoints as $endpoint) { - $plugin = $endpoint->getResourcePlugin(); - - /** @var \Symfony\Component\Routing\Route $route */ - foreach ($plugin->routes() as $name => $route) { - $method = $route->getRequirement('_method'); - // Only expose routes where the method is enabled in the configuration. - if ($method && $endpoint->isRequestMethodEnabled($method)) { - $route->setRequirement('_access_rest_csrf', 'TRUE'); - - // Check that authentication providers are defined. - if (!$endpoint->supportsAuthenticationProviders($method)) { - $this->logger->error('At least one authentication provider must be defined for resource @id', array(':id' => $endpoint->getResourcePluginID())); - continue; - } - - // Check that formats are defined. - if (!$endpoint->hasSupportedFormats($method)) { - $this->logger->error('At least one format must be defined for resource @id', array(':id' => $endpoint->getResourcePluginID())); - continue; - } - - // If the route has a format requirement, then verify that the - // resource has it. - $format_requirement = $route->getRequirement('_format'); - if ($format_requirement && !$endpoint->supportsFormat($method, $format_requirement)) { - continue; - } - - // The configuration seems legit at this point, so we set the - // authentication provider and add the route. - $route->setOption('_auth', $endpoint->getSupportedAuthenticationProviders($method)); - $routes["rest.$name"] = $route; - $collection->add("rest.$name", $route); - } - } + $endpoint_routes = $endpoint->routes(); + $collection->addCollection($endpoint_routes); } }