View source
<?php
namespace Drupal\jsonapi_resources\Unstable\Routing;
use Drupal\Core\Routing\RouteBuildEvent;
use Drupal\Core\Routing\RoutingEvents;
use Drupal\jsonapi\ResourceType\ResourceTypeRepositoryInterface;
use Drupal\jsonapi\Routing\Routes as JsonapiRoutes;
use Drupal\jsonapi_resources\Exception\ResourceImplementationException;
use Drupal\jsonapi_resources\Exception\RouteDefinitionException;
use Drupal\jsonapi_resources\Resource\ResourceBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Routing\Route;
final class ResourceRoutes implements EventSubscriberInterface {
protected $resourceTypeRepository;
protected $providerIds;
protected $jsonApiBasePath;
protected $container;
public function __construct(ResourceTypeRepositoryInterface $resource_type_repository, array $authentication_providers, $jsonapi_base_path, ContainerInterface $container) {
$this->resourceTypeRepository = $resource_type_repository;
$this->providerIds = array_keys($authentication_providers);
$this->jsonApiBasePath = $jsonapi_base_path;
$this->container = $container;
}
public static function getSubscribedEvents() {
$events[RoutingEvents::ALTER][] = [
'decorateJsonapiResourceRoutes',
6000,
];
return $events;
}
public function decorateJsonapiResourceRoutes(RouteBuildEvent $event) {
$route_collection = $event
->getRouteCollection();
foreach ($route_collection as $route_name => $route) {
if ($route
->getDefault('_jsonapi_resource') === NULL) {
continue;
}
$this
->ensureResourceImplementationValid($route_name, $route);
$path_segments = array_slice(explode('/', $route
->getPath()), 1);
assert(isset($path_segments[0]) && $path_segments[0] === '%jsonapi%');
$path_segments[0] = $this->jsonApiBasePath;
$route
->setPath(implode('/', $path_segments));
$route
->addRequirements([
'_content_type_format' => 'api_json',
'_format' => 'api_json',
]);
$route
->addOptions([
'_auth' => $this->providerIds,
]);
$route
->addDefaults([
JsonapiRoutes::JSON_API_ROUTE_FLAG_KEY => TRUE,
]);
$methods = $route
->getMethods();
if (empty($methods)) {
$route
->setMethods([
'GET',
]);
}
}
}
protected function ensureResourceImplementationValid($route_name, Route $route) {
$resource_name = $route
->getDefault('_jsonapi_resource');
if ($this->container
->has($resource_name)) {
$instance = $this->container
->get($resource_name);
$implementation_class = get_class($instance);
}
elseif (strpos($resource_name, '\\') !== FALSE) {
$implementation_class = $resource_name;
}
else {
throw new RouteDefinitionException("The {$resource_name} service, used by the the {$route_name} route, does not exist.");
}
try {
$reflection = new \ReflectionClass($implementation_class);
} catch (\ReflectionException $e) {
throw new RouteDefinitionException("The {$resource_name} class, used by the the {$route_name} route, does not exist.");
}
$jsonapi_resource_base_class = ResourceBase::class;
$implements_interface = $reflection
->isSubclassOf(ResourceBase::class);
if (!$implements_interface) {
throw new ResourceImplementationException("{$resource_name}, used by the the {$route_name} route, must extend {$jsonapi_resource_base_class}.");
}
try {
$implements_public_process_method = $reflection
->getMethod('process')
->isPublic();
} catch (\ReflectionException $e) {
throw new ResourceImplementationException("{$resource_name}, used by the the {$route_name} route, must declare a public process() method.");
}
if (!$implements_public_process_method) {
throw new ResourceImplementationException("{$resource_name}, used by the the {$route_name} route, declares a process() method but it must be public.");
}
if ($route
->getDefault('_controller')) {
throw new RouteDefinitionException("The {$route_name} route definition must not declare a _controller route default when the _jsonapi_resource route default is declared.");
}
if ($route
->getMethods() === [
'DELETE',
]) {
return;
}
if (!$route
->hasDefault('_jsonapi_resource_types')) {
$method_name = 'getRouteResourceTypes';
try {
$declaring_class = $reflection
->getMethod($method_name)
->getDeclaringClass();
$base_class_name = ResourceBase::class;
if ($declaring_class
->getName() === $base_class_name) {
throw new RouteDefinitionException("{$resource_name}, used by the the {$route_name} route, must override {$base_class_name}::getRouteResourceTypes() or _jsonapi_resource_types must be defined as a route default.");
}
} catch (\ReflectionException $e) {
assert(FALSE, "It should be impossible to reach this state because the code above already ensured that the resource class declares a {$method_name}() method since it extends {$jsonapi_resource_base_class}.");
}
}
elseif (!is_array($route
->getDefault('_jsonapi_resource_types'))) {
throw new RouteDefinitionException("The {$route_name} route definition's _jsonapi_resource_types route default must be an array.");
}
elseif (empty($route
->getDefault('_jsonapi_resource_types'))) {
throw new RouteDefinitionException("The {$route_name} route definition's _jsonapi_resource_types route default must declare at least one resource type.");
}
$path_segments = array_slice(explode('/', $route
->getPath()), 1);
$prefix = $path_segments[0] ?? NULL;
if ($prefix !== '%jsonapi%') {
throw new RouteDefinitionException("The {$route_name} route definition's path, `{$route->getPath()}`, must begin with `/%jsonapi%` so that the JSON:API base path can be substituted in its place.");
}
}
}