You are here

class JsonApiSchemaController in JSON:API Schema 8

Hierarchy

Expanded class hierarchy of JsonApiSchemaController

1 file declares its use of JsonApiSchemaController
Routes.php in src/Routing/Routes.php

File

src/Controller/JsonApiSchemaController.php, line 23

Namespace

Drupal\jsonapi_schema\Controller
View source
class JsonApiSchemaController extends ControllerBase {
  const JSON_SCHEMA_DRAFT = 'https://json-schema.org/draft/2019-09/hyper-schema';
  const JSONAPI_BASE_SCHEMA_URI = 'https://jsonapi.org/schema';

  /**
   * The JSON:API resource type repository.
   *
   * @var \Drupal\jsonapi\ResourceType\ResourceTypeRepositoryInterface
   */
  protected $resourceTypeRepository;

  /**
   * The serialization service.
   *
   * @var \Symfony\Component\Serializer\Normalizer\NormalizerInterface
   */
  protected $normalizer;

  /**
   * The entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * The static data definition extractor.
   *
   * @var \Drupal\jsonapi_schema\StaticDataDefinitionExtractor
   */
  protected $staticDataDefinitionExtractor;

  /**
   * JsonApiSchemaController constructor.
   *
   * @param \Drupal\jsonapi\ResourceType\ResourceTypeRepositoryInterface $resource_type_repository
   *   The JSON:API resource type repository.
   * @param \Symfony\Component\Serializer\Normalizer\NormalizerInterface $normalizer
   *   The serializer.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager.
   */
  public function __construct(ResourceTypeRepositoryInterface $resource_type_repository, NormalizerInterface $normalizer, EntityTypeManagerInterface $entity_type_manager, StaticDataDefinitionExtractor $static_data_definition_extractor) {
    $this->resourceTypeRepository = $resource_type_repository;
    $this->normalizer = $normalizer;
    $this->entityTypeManager = $entity_type_manager;
    $this->staticDataDefinitionExtractor = $static_data_definition_extractor;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static($container
      ->get('jsonapi.resource_type.repository'), $container
      ->get('serializer'), $container
      ->get('entity_type.manager'), $container
      ->get('jsonapi_schema.static_data_definition_extractor'));
  }
  public function getEntrypointSchema(Request $request) {
    $cacheability = new CacheableMetadata();
    $cacheability
      ->addCacheTags([
      'jsonapi_resource_types',
    ]);
    $collection_links = array_values(array_map(function (ResourceType $resource_type) use ($cacheability) {
      $schema_url = Url::fromRoute("jsonapi_schema.{$resource_type->getTypeName()}.collection")
        ->setAbsolute()
        ->toString(TRUE);
      $cacheability
        ->addCacheableDependency($schema_url);
      return [
        'href' => '{instanceHref}',
        'rel' => 'related',
        'title' => $this
          ->getSchemaTitle($resource_type, 'collection'),
        'targetMediaType' => 'application/vnd.api+json',
        'targetSchema' => $schema_url
          ->getGeneratedUrl(),
        'templatePointers' => [
          'instanceHref' => "/links/{$resource_type->getTypeName()}/href",
        ],
        'templateRequired' => [
          'instanceHref',
        ],
      ];
    }, array_filter($this->resourceTypeRepository
      ->all(), function (ResourceType $resource_type) {
      return !$resource_type
        ->isInternal() && $resource_type
        ->isLocatable();
    })));
    $schema = [
      '$schema' => static::JSON_SCHEMA_DRAFT,
      '$id' => $request
        ->getUri(),
      'allOf' => [
        [
          '$ref' => static::JSONAPI_BASE_SCHEMA_URI . '#/definitions/success',
        ],
        [
          'type' => 'object',
          'links' => $collection_links,
        ],
      ],
    ];
    return CacheableJsonResponse::create($schema)
      ->addCacheableDependency($cacheability);
  }
  public function getDocumentSchema(Request $request, $resource_type, $route_type) {
    if (is_array($resource_type)) {
      $titles = array_map(function (ResourceType $type) use ($route_type) {
        return $this
          ->getSchemaTitle($this->resourceTypeRepository
          ->getByTypeName($type), $route_type);
      }, $resource_type);
      $title = count($titles) === 2 ? implode(' and ', $titles) : implode(', ', array_slice($titles, -1)) . ', and ' . end($titles);
    }
    else {
      $title = $this
        ->getSchemaTitle($this->resourceTypeRepository
        ->getByTypeName($resource_type), $route_type);
    }
    $schema = [
      '$schema' => static::JSON_SCHEMA_DRAFT,
      '$id' => $request
        ->getUri(),
      'title' => $title,
      'allOf' => [
        [
          '$ref' => static::JSONAPI_BASE_SCHEMA_URI,
        ],
        [
          'if' => [
            '$ref' => static::JSONAPI_BASE_SCHEMA_URI . '#/definitions/success',
          ],
          'then' => [
            'type' => 'object',
            'properties' => [
              'data' => [
                '$ref' => '#/definitions/data',
              ],
            ],
            'required' => [
              'data',
            ],
          ],
        ],
      ],
    ];
    $cacheability = new CacheableMetadata();
    $get_schema_ref = function ($resource_type) use ($cacheability) {
      $schema_url = Url::fromRoute("jsonapi_schema.{$resource_type}.type")
        ->setAbsolute()
        ->toString(TRUE);
      $cacheability
        ->addCacheableDependency($schema_url);
      return [
        '$ref' => $schema_url
          ->getGeneratedUrl(),
      ];
    };
    $type_schema = is_array($resource_type) ? [
      'anyOf' => array_map($get_schema_ref, $resource_type),
    ] : $get_schema_ref($resource_type);
    switch ($route_type) {
      case 'item':
        $schema['definitions']['data'] = $type_schema;
        break;
      case 'collection':
        $schema['definitions']['data'] = [
          'type' => 'array',
          'items' => $type_schema,
        ];
        break;
      case 'relationship':
        assert('not implemented');
        break;
    }
    return CacheableJsonResponse::create($schema)
      ->addCacheableDependency($cacheability);
  }
  public function getResourceObjectSchema(Request $request, $resource_type) {
    $resource_type = $this->resourceTypeRepository
      ->getByTypeName($resource_type);
    $schema = [
      '$schema' => static::JSON_SCHEMA_DRAFT,
      '$id' => $request
        ->getUri(),
      'title' => $this
        ->getSchemaTitle($resource_type, 'item'),
      'allOf' => [
        [
          'type' => 'object',
          'properties' => [
            'type' => [
              '$ref' => '#definitions/type',
            ],
          ],
        ],
        [
          '$ref' => static::JSONAPI_BASE_SCHEMA_URI . '#/definitions/resource',
        ],
      ],
      'definitions' => [
        'type' => [
          'const' => $resource_type
            ->getTypeName(),
        ],
      ],
    ];
    $cacheability = new CacheableMetadata();
    $schema = $this
      ->addFieldsSchema($schema, $resource_type);
    $schema = $this
      ->addRelationshipsSchemaLinks($schema, $resource_type, $cacheability);
    return CacheableJsonResponse::create($schema)
      ->addCacheableDependency($cacheability);
  }
  protected function addFieldsSchema(array $schema, ResourceType $resource_type) {

    // Filter out disabled fields.
    $resource_fields = array_filter($resource_type
      ->getFields(), function (ResourceTypeField $field) {
      return $field
        ->isFieldEnabled();
    });
    if (empty($resource_fields)) {
      return $schema;
    }
    $schema['allOf'][0]['properties']['attributes'] = [
      '$ref' => '#/definitions/attributes',
    ];
    $normalizer = $this->normalizer;
    $entity_type = $this->entityTypeManager
      ->getDefinition($resource_type
      ->getEntityTypeId());
    $bundle = $resource_type
      ->getBundle();
    $fields = array_reduce($resource_fields, function ($carry, ResourceTypeField $field) use ($normalizer, $entity_type, $bundle) {
      $field_schema = $normalizer
        ->normalize($this->staticDataDefinitionExtractor
        ->extractField($entity_type, $bundle, $field
        ->getInternalName()), 'schema_json', [
        'name' => $field
          ->getPublicName(),
      ]);
      $fields_member = $field instanceof ResourceTypeAttribute ? 'attributes' : 'relationships';
      return NestedArray::mergeDeep($carry, [
        'type' => 'object',
        'properties' => [
          $fields_member => $field_schema,
        ],
      ]);
    }, []);
    $field_definitions = NestedArray::getValue($fields, [
      'properties',
    ]) ?: [];
    if (!empty($field_definitions['attributes'])) {
      $field_definitions['attributes']['additionalProperties'] = FALSE;
    }
    if (!empty($field_definitions['relationships'])) {
      $field_definitions['relationships']['additionalProperties'] = FALSE;
    }
    $schema['definitions'] = NestedArray::mergeDeep($schema['definitions'], $field_definitions);
    return $schema;
  }
  protected static function addRelationshipsSchemaLinks(array $schema, ResourceType $resource_type, CacheableMetadata $cacheability) {
    $resource_relationships = array_filter($resource_type
      ->getFields(), function (ResourceTypeField $field) {
      return $field
        ->isFieldEnabled() && $field instanceof ResourceTypeRelationship;
    });
    if (empty($resource_relationships)) {
      return $schema;
    }
    $schema['allOf'][0]['properties']['relationships'] = [
      '$ref' => '#/definitions/relationships',
    ];
    $relationships = array_reduce($resource_relationships, function ($relationships, ResourceTypeRelationship $relationship) use ($resource_type, $cacheability) {
      if ($resource_type
        ->isInternal() || !Routes::hasNonInternalTargetResourceTypes($relationship
        ->getRelatableResourceTypes())) {
        return $relationships;
      }
      $field_name = $relationship
        ->getPublicName();
      $resource_type_name = $resource_type
        ->getTypeName();
      $related_route_name = "jsonapi_schema.{$resource_type_name}.{$field_name}.related";
      $related_schema_uri = Url::fromRoute($related_route_name)
        ->setAbsolute()
        ->toString(TRUE);
      $cacheability
        ->addCacheableDependency($related_schema_uri);
      return NestedArray::mergeDeep($relationships, [
        $field_name => [
          'links' => [
            [
              'href' => '{instanceHref}',
              'rel' => 'related',
              'targetMediaType' => 'application/vnd.api+json',
              'targetSchema' => $related_schema_uri
                ->getGeneratedUrl(),
              'templatePointers' => [
                'instanceHref' => '/links/related/href',
              ],
              'templateRequired' => [
                'instanceHref',
              ],
            ],
          ],
        ],
      ]);
    }, []);
    $schema['definitions']['relationships'] = NestedArray::mergeDeep(empty($schema['definitions']['relationships']) ? [] : $schema['definitions']['relationships'], [
      'properties' => $relationships,
    ]);
    return $schema;
  }

  /**
   * Gets a schema title.
   *
   * @param \Drupal\jsonapi\ResourceType\ResourceType $resource_type
   *   A JSON:API resource type for which to generate a title.
   * @param $schema_type
   *   The type of schema. Either 'collection' or 'item'.
   *
   * @return \Drupal\Core\StringTranslation\TranslatableMarkup
   *   The schema title.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  protected function getSchemaTitle(ResourceType $resource_type, $schema_type) {
    $entity_type = $this->entityTypeManager
      ->getDefinition($resource_type
      ->getEntityTypeId());
    $entity_type_label = $schema_type === 'collection' ? $entity_type
      ->getPluralLabel() : $entity_type
      ->getSingularLabel();
    if ($bundle_type = $entity_type
      ->getBundleEntityType()) {
      $bundle = $this->entityTypeManager
        ->getStorage($bundle_type)
        ->load($resource_type
        ->getBundle());
      return $this
        ->t(rtrim('@bundle_label @entity_type_label'), [
        '@bundle_label' => Unicode::ucfirst($bundle
          ->label()),
        '@entity_type_label' => $entity_type_label,
      ]);
    }
    else {
      return $this
        ->t(rtrim('@entity_type_label'), [
        '@entity_type_label' => Unicode::ucfirst($entity_type_label),
      ]);
    }
  }

}

Members

Namesort descending Modifiers Type Description Overrides
ControllerBase::$configFactory protected property The configuration factory.
ControllerBase::$currentUser protected property The current user service. 1
ControllerBase::$entityFormBuilder protected property The entity form builder.
ControllerBase::$entityManager protected property The entity manager.
ControllerBase::$formBuilder protected property The form builder. 2
ControllerBase::$keyValue protected property The key-value storage. 1
ControllerBase::$languageManager protected property The language manager. 1
ControllerBase::$moduleHandler protected property The module handler. 2
ControllerBase::$stateService protected property The state service.
ControllerBase::cache protected function Returns the requested cache bin.
ControllerBase::config protected function Retrieves a configuration object.
ControllerBase::container private function Returns the service container.
ControllerBase::currentUser protected function Returns the current user. 1
ControllerBase::entityFormBuilder protected function Retrieves the entity form builder.
ControllerBase::entityManager Deprecated protected function Retrieves the entity manager service.
ControllerBase::entityTypeManager protected function Retrieves the entity type manager.
ControllerBase::formBuilder protected function Returns the form builder service. 2
ControllerBase::keyValue protected function Returns a key/value storage collection. 1
ControllerBase::languageManager protected function Returns the language manager service. 1
ControllerBase::moduleHandler protected function Returns the module handler. 2
ControllerBase::redirect protected function Returns a redirect response object for the specified route. Overrides UrlGeneratorTrait::redirect
ControllerBase::state protected function Returns the state storage service.
JsonApiSchemaController::$entityTypeManager protected property The entity type manager. Overrides ControllerBase::$entityTypeManager
JsonApiSchemaController::$normalizer protected property The serialization service.
JsonApiSchemaController::$resourceTypeRepository protected property The JSON:API resource type repository.
JsonApiSchemaController::$staticDataDefinitionExtractor protected property The static data definition extractor.
JsonApiSchemaController::addFieldsSchema protected function
JsonApiSchemaController::addRelationshipsSchemaLinks protected static function
JsonApiSchemaController::create public static function Instantiates a new instance of this class. Overrides ControllerBase::create
JsonApiSchemaController::getDocumentSchema public function
JsonApiSchemaController::getEntrypointSchema public function
JsonApiSchemaController::getResourceObjectSchema public function
JsonApiSchemaController::getSchemaTitle protected function Gets a schema title.
JsonApiSchemaController::JSONAPI_BASE_SCHEMA_URI constant
JsonApiSchemaController::JSON_SCHEMA_DRAFT constant
JsonApiSchemaController::__construct public function JsonApiSchemaController constructor.
LinkGeneratorTrait::$linkGenerator protected property The link generator. 1
LinkGeneratorTrait::getLinkGenerator Deprecated protected function Returns the link generator.
LinkGeneratorTrait::l Deprecated protected function Renders a link to a route given a route name and its parameters.
LinkGeneratorTrait::setLinkGenerator Deprecated public function Sets the link generator service.
LoggerChannelTrait::$loggerFactory protected property The logger channel factory service.
LoggerChannelTrait::getLogger protected function Gets the logger for a specific channel.
LoggerChannelTrait::setLoggerFactory public function Injects the logger channel factory.
MessengerTrait::$messenger protected property The messenger. 29
MessengerTrait::messenger public function Gets the messenger. 29
MessengerTrait::setMessenger public function Sets the messenger.
RedirectDestinationTrait::$redirectDestination protected property The redirect destination service. 1
RedirectDestinationTrait::getDestinationArray protected function Prepares a 'destination' URL query parameter for use with \Drupal\Core\Url.
RedirectDestinationTrait::getRedirectDestination protected function Returns the redirect destination service.
RedirectDestinationTrait::setRedirectDestination public function Sets the redirect destination service.
StringTranslationTrait::$stringTranslation protected property The string translation service. 1
StringTranslationTrait::formatPlural protected function Formats a string containing a count of items.
StringTranslationTrait::getNumberOfPlurals protected function Returns the number of plurals supported by a given language.
StringTranslationTrait::getStringTranslation protected function Gets the string translation service.
StringTranslationTrait::setStringTranslation public function Sets the string translation service to use. 2
StringTranslationTrait::t protected function Translates a string to the current language or to a given language.
UrlGeneratorTrait::$urlGenerator protected property The url generator.
UrlGeneratorTrait::getUrlGenerator Deprecated protected function Returns the URL generator service.
UrlGeneratorTrait::setUrlGenerator Deprecated public function Sets the URL generator service.
UrlGeneratorTrait::url Deprecated protected function Generates a URL or path for a specific route based on the given parameters.