You are here

protected function ResourceRoutes::ensureResourceImplementationValid in JSON:API Resources 8

Checks whether $definition identifies a valid JSON:API resource.

The purpose of this method is to anticipate developer mistakes and provide actionable errors. This also provides guarantees for code that is run during request processing (e.g. that the `process()` method exists). Doing so means errors are surfaced during development instead of as production errors visible to an API client.

Parameters

string $route_name: The name of the route to validate.

\Symfony\Component\Routing\Route $route: The route object to validate.

Throws

\Drupal\jsonapi_resources\Exception\RouteDefinitionException Thrown if a JSON:API resource route definition is invalid.

\Drupal\jsonapi_resources\Exception\ResourceImplementationException Thrown if a JSON:API resource class is improperly implemented.

1 call to ResourceRoutes::ensureResourceImplementationValid()
ResourceRoutes::decorateJsonapiResourceRoutes in src/Unstable/Routing/ResourceRoutes.php
Decorates JSON:API Resource routes.

File

src/Unstable/Routing/ResourceRoutes.php, line 137

Class

ResourceRoutes
Route subscriber to decorate JSON:API Resource routes.

Namespace

Drupal\jsonapi_resources\Unstable\Routing

Code

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.");
  }

  // JSON:API resource routes must not define a custom controller.
  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.");
  }

  // Routes that only support DELETE will return HTTP 204 and have no response
  // content, so do not validate _jsonapi_resource_types.
  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.");
  }

  // Ensure that every JSON:API resource path begins with `/%jsonapi%` so the
  // JSON:API base path can be substituted in its place.
  $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.");
  }
}