diff --git a/core/modules/jsonapi/src/ResourceType/ResourceType.php b/core/modules/jsonapi/src/ResourceType/ResourceType.php index 955feff354..549999d899 100644 --- a/core/modules/jsonapi/src/ResourceType/ResourceType.php +++ b/core/modules/jsonapi/src/ResourceType/ResourceType.php @@ -37,12 +37,25 @@ class ResourceType { protected $bundle; /** - * The type name. + * The type name (mutable). + * + * This value can be overridden by JSON:API Extras or any other + * custom solution that alters the "createResourceType()" method + * of "jsonapi_extras.resource_type.repository" service. * * @var string */ protected $typeName; + /** + * The type name (immutable). + * + * The name of a resource in the "ENTITY_TYPE_ID--ENTITY_BUNDLE" format. + * + * @var string + */ + private $typeNameInternal; + /** * The class to which a payload converts to. * @@ -112,7 +125,7 @@ public function getEntityTypeId() { } /** - * Gets the type name. + * Gets the type name (mutable). * * @return string * The type name. @@ -121,6 +134,16 @@ public function getTypeName() { return $this->typeName; } + /** + * Gets the type name (immutable). + * + * @return string + * The type name. + */ + public function getTypeNameInternal() { + return $this->typeNameInternal; + } + /** * Gets the bundle. * @@ -356,7 +379,7 @@ public function __construct($entity_type_id, $bundle, $deserialization_target_cl $this->isVersionable = $is_versionable; $this->fields = $fields; - $this->typeName = $this->bundle === '?' + $this->typeNameInternal = $this->typeName = $this->bundle === '?' ? 'unknown' : sprintf('%s--%s', $this->entityTypeId, $this->bundle); @@ -397,11 +420,12 @@ public function setRelatableResourceTypes(array $relatable_resource_types) { */ public function getRelatableResourceTypes() { if (!isset($this->relatableResourceTypesByField)) { - $this->relatableResourceTypesByField = array_reduce(array_map(function (ResourceTypeRelationship $field) { - return [$field->getPublicName() => $field->getRelatableResourceTypes()]; - }, array_filter($this->fields, function (ResourceTypeField $field) { - return $field instanceof ResourceTypeRelationship; - })), 'array_merge', []); + $this->relatableResourceTypesByField = []; + foreach ($this->fields as $field) { + if ($field instanceof ResourceTypeRelationship) { + $this->relatableResourceTypesByField[$field->getPublicName()] = $field->getRelatableResourceTypes(); + } + } } return $this->relatableResourceTypesByField; } diff --git a/core/modules/jsonapi/src/ResourceType/ResourceTypeRelationship.php b/core/modules/jsonapi/src/ResourceType/ResourceTypeRelationship.php index 8e782a527c..b1a9b4fc98 100644 --- a/core/modules/jsonapi/src/ResourceType/ResourceTypeRelationship.php +++ b/core/modules/jsonapi/src/ResourceType/ResourceTypeRelationship.php @@ -2,6 +2,8 @@ namespace Drupal\jsonapi\ResourceType; +use Drupal\Component\Assertion\Inspector; + /** * Specialization of a ResourceTypeField to represent a resource relationship. * @@ -32,6 +34,7 @@ class ResourceTypeRelationship extends ResourceTypeField { * A new instance of the field with the given relatable resource types. */ public function withRelatableResourceTypes(array $resource_types) { + assert(Inspector::assertAllObjects($resource_types, ResourceType::class)); $relationship = new static($this->internalName, $this->publicName, $this->enabled, $this->hasOne); $relationship->relatableResourceTypes = $resource_types; return $relationship; diff --git a/core/modules/jsonapi/src/ResourceType/ResourceTypeRepository.php b/core/modules/jsonapi/src/ResourceType/ResourceTypeRepository.php index 724264732f..8824362fb1 100644 --- a/core/modules/jsonapi/src/ResourceType/ResourceTypeRepository.php +++ b/core/modules/jsonapi/src/ResourceType/ResourceTypeRepository.php @@ -124,7 +124,7 @@ public function all() { $resource_types = array_reduce($bundles, function ($resource_types, $bundle) use ($entity_type) { $resource_type = $this->createResourceType($entity_type, (string) $bundle); return array_merge($resource_types, [ - $resource_type->getTypeName() => $resource_type, + $resource_type->getTypeNameInternal() => $resource_type, ]); }, $resource_types); } diff --git a/core/modules/jsonapi/tests/modules/jsonapi_test_resource_typename_hack/jsonapi_test_resource_typename_hack.info.yml b/core/modules/jsonapi/tests/modules/jsonapi_test_resource_typename_hack/jsonapi_test_resource_typename_hack.info.yml new file mode 100644 index 0000000000..1323700898 --- /dev/null +++ b/core/modules/jsonapi/tests/modules/jsonapi_test_resource_typename_hack/jsonapi_test_resource_typename_hack.info.yml @@ -0,0 +1,4 @@ +name: 'JSON:API test resource typeName override' +type: module +package: Testing +core: 8.x diff --git a/core/modules/jsonapi/tests/modules/jsonapi_test_resource_typename_hack/jsonapi_test_resource_typename_hack.services.yml b/core/modules/jsonapi/tests/modules/jsonapi_test_resource_typename_hack/jsonapi_test_resource_typename_hack.services.yml new file mode 100644 index 0000000000..2238b13a6d --- /dev/null +++ b/core/modules/jsonapi/tests/modules/jsonapi_test_resource_typename_hack/jsonapi_test_resource_typename_hack.services.yml @@ -0,0 +1,5 @@ +services: + Drupal\jsonapi_test_resource_typename_hack\ResourceType\ResourceTypeRepository: + decorates: jsonapi.resource_type.repository + parent: jsonapi.resource_type.repository + public: false diff --git a/core/modules/jsonapi/tests/modules/jsonapi_test_resource_typename_hack/src/ResourceType/ResourceType.php b/core/modules/jsonapi/tests/modules/jsonapi_test_resource_typename_hack/src/ResourceType/ResourceType.php new file mode 100644 index 0000000000..1d68da3fe5 --- /dev/null +++ b/core/modules/jsonapi/tests/modules/jsonapi_test_resource_typename_hack/src/ResourceType/ResourceType.php @@ -0,0 +1,21 @@ +typeName = str_replace('--', '==', $this->typeName); + } + +} diff --git a/core/modules/jsonapi/tests/modules/jsonapi_test_resource_typename_hack/src/ResourceType/ResourceTypeRepository.php b/core/modules/jsonapi/tests/modules/jsonapi_test_resource_typename_hack/src/ResourceType/ResourceTypeRepository.php new file mode 100644 index 0000000000..2da8d7b326 --- /dev/null +++ b/core/modules/jsonapi/tests/modules/jsonapi_test_resource_typename_hack/src/ResourceType/ResourceTypeRepository.php @@ -0,0 +1,29 @@ +id(), + $bundle, + $entity_type->getClass(), + $entity_type->isInternal(), + static::isLocatableResourceType($entity_type, $bundle), + static::isMutableResourceType($entity_type, $bundle), + static::isVersionableResourceType($entity_type), + static::getFields($this->getAllFieldNames($entity_type, $bundle), $entity_type, $bundle) + ); + } + +} diff --git a/core/modules/jsonapi/tests/src/Kernel/ResourceType/ResourceTypeRepositoryTypeNameHackTest.php b/core/modules/jsonapi/tests/src/Kernel/ResourceType/ResourceTypeRepositoryTypeNameHackTest.php new file mode 100644 index 0000000000..6bd77a84b3 --- /dev/null +++ b/core/modules/jsonapi/tests/src/Kernel/ResourceType/ResourceTypeRepositoryTypeNameHackTest.php @@ -0,0 +1,71 @@ +container + ->get('entity_type.manager') + ->getStorage('node_type') + ->create(['type' => 'page']) + ->save(); + } + + /** + * Ensures resource repository forms the listing using internal names. + * + * JSON:API Extras is used widely and it's hard to imagine how big its + * coverage at existing projects. The project allows renaming resource + * types (e.g. "user---user" to "user") and this negatively affects the + * operability of JSON:API itself. + * + * @link https://www.drupal.org/project/drupal/issues/2996114 + */ + public function test() { + $repository = $this->container->get('jsonapi.resource_type.repository'); + + static::assertInstanceOf(ResourceType::class, $repository->get('user', 'user')); + static::assertInstanceOf(ResourceType::class, $repository->getByTypeName('node--page')); + + foreach ($repository->all() as $id => $resource_type) { + static::assertSame( + sprintf('%s--%s', $resource_type->getEntityTypeId(), $resource_type->getBundle()), + $id, + 'The key must always be in the "ENTITY_TYPE_ID--ENTITY_BUNDLE" format.' + ); + + static::assertNotSame( + $resource_type->getTypeName(), + $id, + 'The type name can be changed and not always is the same as the internal type name.' + ); + } + } + +}