diff --git a/core/modules/jsonapi/src/ResourceType/ResourceType.php b/core/modules/jsonapi/src/ResourceType/ResourceType.php index 5610184615..b6053fb064 100644 --- a/core/modules/jsonapi/src/ResourceType/ResourceType.php +++ b/core/modules/jsonapi/src/ResourceType/ResourceType.php @@ -18,6 +18,13 @@ */ class ResourceType { + /** + * A string which is used as path separator in resource type names. + * + * @see getPath() + */ + const TYPE_NAME_URI_PATH_SEPARATOR = '--'; + /** * The entity type ID. * @@ -338,8 +345,10 @@ public function isVersionable() { * (optional) Whether the resource type is versionable. * @param \Drupal\jsonapi\ResourceType\ResourceTypeField[] $fields * (optional) The resource type fields, keyed by internal field name. + * @param null|string $type_name + * The resource type name. */ - public function __construct($entity_type_id, $bundle, $deserialization_target_class, $internal = FALSE, $is_locatable = TRUE, $is_mutable = TRUE, $is_versionable = FALSE, array $fields = []) { + public function __construct($entity_type_id, $bundle, $deserialization_target_class, $internal = FALSE, $is_locatable = TRUE, $is_mutable = TRUE, $is_versionable = FALSE, array $fields = [], $type_name = NULL) { $this->entityTypeId = $entity_type_id; $this->bundle = $bundle; $this->deserializationTargetClass = $deserialization_target_class; @@ -349,9 +358,12 @@ public function __construct($entity_type_id, $bundle, $deserialization_target_cl $this->isVersionable = $is_versionable; $this->fields = $fields; - $this->typeName = $this->bundle === '?' - ? 'unknown' - : sprintf('%s--%s', $this->entityTypeId, $this->bundle); + $this->typeName = $type_name; + if ($type_name === NULL) { + $this->typeName = $this->bundle === '?' + ? 'unknown' + : sprintf('%s' . self::TYPE_NAME_URI_PATH_SEPARATOR . '%s', $this->entityTypeId, $this->bundle); + } $this->fieldMapping = array_flip(array_map(function (ResourceTypeField $field) { return $field->getPublicName(); @@ -420,12 +432,13 @@ public function getRelatableResourceTypesByField($field_name) { * Get the resource path. * * @return string - * The path to access this resource type. Default: /entity_type_id/bundle. + * The path to access this resource type. This function replaces "--" with + * a "/" in the uri path. Example: "node--article" -> "node/article" * * @see jsonapi.base_path */ public function getPath() { - return sprintf('/%s/%s', $this->getEntityTypeId(), $this->getBundle()); + return '/' . implode('/', explode(self::TYPE_NAME_URI_PATH_SEPARATOR, $this->typeName)); } } diff --git a/core/modules/jsonapi/src/ResourceType/ResourceTypeBuildEvent.php b/core/modules/jsonapi/src/ResourceType/ResourceTypeBuildEvent.php index 5aebcfde8d..69fb88182e 100644 --- a/core/modules/jsonapi/src/ResourceType/ResourceTypeBuildEvent.php +++ b/core/modules/jsonapi/src/ResourceType/ResourceTypeBuildEvent.php @@ -17,9 +17,9 @@ class ResourceTypeBuildEvent extends Event { /** * The JSON:API resource type name of the instance to be built. * - * @var string + * @var null|string */ - protected $resourceTypeName; + protected $resourceTypeName = NULL; /** * The fields of the resource type to be built. @@ -67,7 +67,7 @@ protected function __construct($resource_type_name, array $fields) { * A new event. */ public static function createFromEntityTypeAndBundle(EntityTypeInterface $entity_type, $bundle, array $fields) { - return new static(sprintf('%s--%s', $entity_type->id(), $bundle), $fields); + return new static(sprintf('%s' . ResourceType::TYPE_NAME_URI_PATH_SEPARATOR . '%s', $entity_type->id(), $bundle), $fields); } /** @@ -80,6 +80,17 @@ public function getResourceTypeName() { return $this->resourceTypeName; } + /** + * Sets the name of the resource type to be built. + * + * @param string $resource_type_name + * The resource type name. The resource replaces "--" with + * a "/" in the uri path. Example: "node--article" -> "node/article" + */ + public function setResourceTypeName(string $resource_type_name) { + $this->resourceTypeName = $resource_type_name; + } + /** * Disables the resource type to be built. */ diff --git a/core/modules/jsonapi/src/ResourceType/ResourceTypeRepository.php b/core/modules/jsonapi/src/ResourceType/ResourceTypeRepository.php index aeadd2741a..683fbd9a8b 100644 --- a/core/modules/jsonapi/src/ResourceType/ResourceTypeRepository.php +++ b/core/modules/jsonapi/src/ResourceType/ResourceTypeRepository.php @@ -150,6 +150,7 @@ public function all() { * A JSON:API resource type. */ protected function createResourceType(EntityTypeInterface $entity_type, $bundle) { + $type_name = NULL; $raw_fields = $this->getAllFieldNames($entity_type, $bundle); $internalize_resource_type = $entity_type->isInternal(); $fields = static::getFields($raw_fields, $entity_type, $bundle); @@ -158,6 +159,7 @@ protected function createResourceType(EntityTypeInterface $entity_type, $bundle) $this->eventDispatcher->dispatch($event, ResourceTypeBuildEvents::BUILD); $internalize_resource_type = $event->resourceTypeShouldBeDisabled(); $fields = $event->getFields(); + $type_name = $event->getResourceTypeName(); } return new ResourceType( $entity_type->id(), @@ -167,7 +169,8 @@ protected function createResourceType(EntityTypeInterface $entity_type, $bundle) static::isLocatableResourceType($entity_type, $bundle), static::isMutableResourceType($entity_type, $bundle), static::isVersionableResourceType($entity_type), - $fields + $fields, + $type_name ); } @@ -180,7 +183,17 @@ public function get($entity_type_id, $bundle) { throw new PreconditionFailedHttpException('Server error. The current route is malformed.'); } - return static::lookupResourceType($this->all(), $entity_type_id, $bundle); + $map_id = sprintf('jsonapi.resource_type.%s.%s', $entity_type_id, $bundle); + $cached = $this->cache->get($map_id); + + if ($cached) { + return $cached->data; + } + + $resource_type = static::lookupResourceType($this->all(), $entity_type_id, $bundle); + $this->cache->set($map_id, $resource_type, Cache::PERMANENT, $this->cacheTags); + + return $resource_type; } /** @@ -508,8 +521,8 @@ protected function getAllBundlesForEntityType($entity_type_id) { * The resource type or NULL if one cannot be found. */ protected static function lookupResourceType(array $resource_types, $entity_type_id, $bundle) { - if (isset($resource_types["$entity_type_id--$bundle"])) { - return $resource_types["$entity_type_id--$bundle"]; + if (isset($resource_types[$entity_type_id . ResourceType::TYPE_NAME_URI_PATH_SEPARATOR . $bundle])) { + return $resource_types[$entity_type_id . ResourceType::TYPE_NAME_URI_PATH_SEPARATOR . $bundle]; } foreach ($resource_types as $resource_type) { diff --git a/core/modules/jsonapi/tests/modules/jsonapi_test_collection_count/src/ResourceType/CountableResourceTypeRepository.php b/core/modules/jsonapi/tests/modules/jsonapi_test_collection_count/src/ResourceType/CountableResourceTypeRepository.php index 39a3974c25..719c1a29e4 100644 --- a/core/modules/jsonapi/tests/modules/jsonapi_test_collection_count/src/ResourceType/CountableResourceTypeRepository.php +++ b/core/modules/jsonapi/tests/modules/jsonapi_test_collection_count/src/ResourceType/CountableResourceTypeRepository.php @@ -14,16 +14,17 @@ class CountableResourceTypeRepository extends ResourceTypeRepository { * {@inheritdoc} */ protected function createResourceType(EntityTypeInterface $entity_type, $bundle) { - $raw_fields = $this->getAllFieldNames($entity_type, $bundle); + $resource_type = parent::createResourceType($entity_type, $bundle); return new CountableResourceType( - $entity_type->id(), - $bundle, - $entity_type->getClass(), - $entity_type->isInternal(), - static::isLocatableResourceType($entity_type, $bundle), - static::isMutableResourceType($entity_type, $bundle), - static::isVersionableResourceType($entity_type), - static::getFields($raw_fields, $entity_type, $bundle) + $resource_type->getEntityTypeId(), + $resource_type->getBundle(), + $resource_type->getDeserializationTargetClass(), + $resource_type->isInternal(), + $resource_type->isLocatable(), + $resource_type->isMutable(), + $resource_type->isVersionable(), + $resource_type->getFields(), + $resource_type->getTypeName() ); } diff --git a/core/modules/jsonapi/tests/modules/jsonapi_test_resource_type_aliasing/jsonapi_test_resource_type_aliasing.info.yml b/core/modules/jsonapi/tests/modules/jsonapi_test_resource_type_aliasing/jsonapi_test_resource_type_aliasing.info.yml deleted file mode 100644 index 828588185d..0000000000 --- a/core/modules/jsonapi/tests/modules/jsonapi_test_resource_type_aliasing/jsonapi_test_resource_type_aliasing.info.yml +++ /dev/null @@ -1,3 +0,0 @@ -name: 'JSON:API test resource type aliasing' -type: module -package: Testing diff --git a/core/modules/jsonapi/tests/modules/jsonapi_test_resource_type_aliasing/jsonapi_test_resource_type_aliasing.services.yml b/core/modules/jsonapi/tests/modules/jsonapi_test_resource_type_aliasing/jsonapi_test_resource_type_aliasing.services.yml deleted file mode 100644 index 9c02e50e34..0000000000 --- a/core/modules/jsonapi/tests/modules/jsonapi_test_resource_type_aliasing/jsonapi_test_resource_type_aliasing.services.yml +++ /dev/null @@ -1,5 +0,0 @@ -services: - Drupal\jsonapi_test_resource_type_aliasing\ResourceType\AliasingResourceTypeRepository: - decorates: jsonapi.resource_type.repository - parent: jsonapi.resource_type.repository - public: false diff --git a/core/modules/jsonapi/tests/modules/jsonapi_test_resource_type_aliasing/src/ResourceType/AliasedResourceType.php b/core/modules/jsonapi/tests/modules/jsonapi_test_resource_type_aliasing/src/ResourceType/AliasedResourceType.php deleted file mode 100644 index 761dc8ee59..0000000000 --- a/core/modules/jsonapi/tests/modules/jsonapi_test_resource_type_aliasing/src/ResourceType/AliasedResourceType.php +++ /dev/null @@ -1,33 +0,0 @@ -getEntityTypeId(), - $resource_type->getBundle(), - $resource_type->getDeserializationTargetClass(), - $resource_type->isInternal(), - $resource_type->isLocatable(), - $resource_type->isMutable(), - $resource_type->isVersionable(), - $resource_type->getFields() - ); - // Alias the resource type name with an alternative pattern. - $this->typeName = $alias; - } - -} diff --git a/core/modules/jsonapi/tests/modules/jsonapi_test_resource_type_aliasing/src/ResourceType/AliasingResourceTypeRepository.php b/core/modules/jsonapi/tests/modules/jsonapi_test_resource_type_aliasing/src/ResourceType/AliasingResourceTypeRepository.php deleted file mode 100644 index 6b8bc234db..0000000000 --- a/core/modules/jsonapi/tests/modules/jsonapi_test_resource_type_aliasing/src/ResourceType/AliasingResourceTypeRepository.php +++ /dev/null @@ -1,24 +0,0 @@ -id(), $bundle); - $base_resource_type = parent::createResourceType($entity_type, $bundle); - return new AliasedResourceType($base_resource_type, $alias); - } - -} diff --git a/core/modules/jsonapi/tests/modules/jsonapi_test_resource_type_building/src/EventSubscriber/ResourceTypeBuildEventSubscriber.php b/core/modules/jsonapi/tests/modules/jsonapi_test_resource_type_building/src/EventSubscriber/ResourceTypeBuildEventSubscriber.php index af4f1a267a..658ac0afc0 100644 --- a/core/modules/jsonapi/tests/modules/jsonapi_test_resource_type_building/src/EventSubscriber/ResourceTypeBuildEventSubscriber.php +++ b/core/modules/jsonapi/tests/modules/jsonapi_test_resource_type_building/src/EventSubscriber/ResourceTypeBuildEventSubscriber.php @@ -22,6 +22,7 @@ public static function getSubscribedEvents() { ['disableResourceType'], ['aliasResourceTypeFields'], ['disableResourceTypeFields'], + ['renameResourceType'], ], ]; } @@ -75,4 +76,18 @@ public function disableResourceTypeFields(ResourceTypeBuildEvent $event) { } } + /** + * Renames any resource types that have been renamed by a test. + * + * @param \Drupal\jsonapi\ResourceType\ResourceTypeBuildEvent $event + * The build event. + */ + public function renameResourceType(ResourceTypeBuildEvent $event) { + $names = \Drupal::state()->get('jsonapi_test_resource_type_builder.renamed_resource_types', []); + $resource_type_name = $event->getResourceTypeName(); + if (isset($names[$resource_type_name])) { + $event->setResourceTypeName($names[$resource_type_name]); + } + } + } diff --git a/core/modules/jsonapi/tests/src/Kernel/ResourceType/ResourceTypeNameAliasTest.php b/core/modules/jsonapi/tests/src/Kernel/ResourceType/ResourceTypeNameAliasTest.php deleted file mode 100644 index b2b506c449..0000000000 --- a/core/modules/jsonapi/tests/src/Kernel/ResourceType/ResourceTypeNameAliasTest.php +++ /dev/null @@ -1,87 +0,0 @@ -container - ->get('entity_type.manager') - ->getStorage('node_type') - ->create(['type' => 'page']) - ->save(); - } - - /** - * Ensures resource repository works with publicly renamed resource types. - * - * @covers ::get - * @covers ::all - * @covers ::getByTypeName - */ - public function testRepositoryResourceTypeNameAliasing() { - $repository = $this->container->get('jsonapi.resource_type.repository'); - - static::assertInstanceOf(ResourceType::class, $repository->get('user', 'user')); - static::assertNull($repository->getByTypeName('user--user')); - static::assertInstanceOf(ResourceType::class, $repository->getByTypeName('user==user')); - - static::assertInstanceOf(ResourceType::class, $repository->get('node', 'page')); - static::assertNull($repository->getByTypeName('node--page')); - static::assertInstanceOf(ResourceType::class, $repository->getByTypeName('node==page')); - - foreach ($repository->all() as $id => $resource_type) { - static::assertSame( - $resource_type->getTypeName(), - $id, - 'The key is always equal to the type name.' - ); - - static::assertNotSame( - sprintf('%s--%s', $resource_type->getEntityTypeId(), $resource_type->getBundle()), - $id, - 'The type name can be renamed so it differs from the internal.' - ); - } - } - -} diff --git a/core/modules/jsonapi/tests/src/Kernel/ResourceType/ResourceTypeRepositoryTest.php b/core/modules/jsonapi/tests/src/Kernel/ResourceType/ResourceTypeRepositoryTest.php index c8f2ab2aef..5503967191 100644 --- a/core/modules/jsonapi/tests/src/Kernel/ResourceType/ResourceTypeRepositoryTest.php +++ b/core/modules/jsonapi/tests/src/Kernel/ResourceType/ResourceTypeRepositoryTest.php @@ -187,16 +187,6 @@ public function testResourceTypeFieldAliasing() { $this->assertSame($this->resourceTypeRepository->getByTypeName('node--page')->getPublicName('uid'), 'owner'); } - /** - * Tests that resource type fields can be aliased per resource type. - */ - public function testResourceTypeNameAliasing() { - // When this test is implemented, ensure the the tested behaviors in - // ResourceTypeNameAliasTest have been covered and remove it. Then remove - // the jsonapi_test_resource_type_aliasing test module. - $this->markTestSkipped('Remove in https://www.drupal.org/project/drupal/issues/3105318'); - } - /** * Tests that resource type fields can be disabled per resource type. */ @@ -217,4 +207,19 @@ public function testResourceTypeFieldDisabling() { $this->assertTrue($this->resourceTypeRepository->getByTypeName('node--page')->isFieldEnabled('uid')); } + /** + * Tests that resource types can be renamed. + */ + public function testResourceTypeRenaming() { + \Drupal::state()->set('jsonapi_test_resource_type_builder.renamed_resource_types', [ + 'node--article' => 'articles', + 'node--page' => 'pages', + ]); + Cache::invalidateTags(['jsonapi_resource_types']); + $this->assertNull($this->resourceTypeRepository->getByTypeName('node--article')); + $this->assertInstanceOf(ResourceType::class, $this->resourceTypeRepository->getByTypeName('articles')); + $this->assertNull($this->resourceTypeRepository->getByTypeName('node--page')); + $this->assertInstanceOf(ResourceType::class, $this->resourceTypeRepository->getByTypeName('pages')); + } + }