Problem/Motivation

JSON:API has \Drupal\jsonapi\ResourceType\ResourceTypeBuildEvent to allow fields to be aliased, fields to be disabled and resource types to be disabled. Those were the three first public PHP APIs for the jsonapi module.

A logical next one would be to allow resource types to be aliased.

Related: @bojanz in #3032787-21: [META] Start creating the public PHP API of the JSON:API module.

Proposed resolution

  1. Allow entity types to declare a JSON:API resource type name using a jsonapi_resource_type_name entity type annotation key.
  2. Add a new method to \Drupal\jsonapi\ResourceType\ResourceTypeBuildEvent.

(Option 2 is preferred by @e0ipso, @gabesullice and @Wim Leers because it solved more problems: it allows any module to override it. So: more power, more consistency, smaller API surface.)

Remaining tasks

User interface changes

None.

API changes

A new method on \Drupal\jsonapi\ResourceType\ResourceTypeBuildEvent.

Data model changes

None.

Release notes snippet

TBD

CommentFileSizeAuthor
#85 3105318-85.patch18.88 KBbbrala
#83 3105318-83.patch19.74 KBbbrala
#83 interdiff-82-83.txt779 bytesbbrala
#82 interdiff_77-82.txt1.41 KBravi.shankar
#82 3105318-82.patch18.83 KBravi.shankar
#78 interdiff-75-77.txt836 bytesbbrala
#77 interdiff-3105318-75-77.txt1.72 KBbbrala
#77 add_a_public_api_for-3105318-77.patch18.79 KBbbrala
#75 add_a_public_api_for-3105318-75.patch18.88 KBbbrala
#75 interdiff-74-75.txt2.33 KBbbrala
#74 interdiff-3105318-70-72.txt2.46 KBbbrala
#74 add_a_public_api_for-3105318-72.patch18.93 KBbbrala
#70 3105318-70.patch18.88 KBbbrala
#70 interdiff-3105318-61-70.txt5.14 KBbbrala
#60 3105318-60.patch17.16 KBbbrala
#60 interdiff-56-60.txt1.56 KBbbrala
#56 interdiff-52-56.txt13.68 KBbbrala
#56 3105318-56.patch19.51 KBbbrala
#53 3105318-52.patch26.62 KBbbrala
#53 interdiff-47-52.txt3.21 KBbbrala
#51 interdiff-47-51.txt1.19 KBbbrala
#51 3105318-51.patch28.94 KBbbrala
#50 interdiff-47-50.txt1.16 KBbbrala
#50 3105318-50.patch28.97 KBbbrala
#49 interdiff-45-47.txt984 bytesbbrala
#49 3105318-47.patch29.16 KBbbrala
#46 interdiff-44-45.txt6.48 KBbbrala
#45 3105318-45.patch28.01 KBbbrala
#44 interdiff-43-44.txt924 bytesbbrala
#44 3105318-44.patch20.43 KBbbrala
#43 interdiff-37-43.txt7.35 KBbbrala
#43 3105318-43.patch20.28 KBbbrala
#37 add_a_public_api_for-3105318-37.patch20.93 KBmglaman
#37 interdiff-3105318-33-37.txt3.47 KBmglaman
#33 add_a_public_api_for-3105318-33.patch18.34 KBmglaman
#33 interdiff-3105318-31-33.txt4.26 KBmglaman
#31 add_a_public_api_for-3105318-31.patch16.8 KBmglaman
#31 interdiff-3105318-29-31.txt8.02 KBmglaman
#29 add_a_public_api_for-3105318-29.patch10.92 KBmglaman
#29 interdiff-3105318-22-29.txt4.3 KBmglaman
#22 3105318-22.patch11.08 KBmglaman
#22 interdiff-3105318-16-22.txt1.72 KBmglaman
#16 3105318-16.patch9.68 KBmglaman
#14 3105318-14.patch10.04 KBmglaman
#11 3105318-11.patch2.02 KBmglaman
#3 3105318-3.patch909 bytesWim Leers
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Wim Leers created an issue. See original summary.

Wim Leers’s picture

Issue summary: View changes
Wim Leers’s picture

Status: Active » Needs review
FileSize
909 bytes

Something like this? I'm not sure if the "public" vs "internal" distinction that we have for fields makes sense here too. For now, going with it.

(For fields, we have to have a mapping to the internal aka actual/storage field name. For resource types, we may not need this, although much if not all of the infrastructure does assume the entity type and bundle can be parsed from the JSON:API resource type name. So perhaps we need it there too.)

Wim Leers credited bojanz.

Wim Leers credited e0ipso.

Wim Leers’s picture

Crediting the people who participated in the Slack discussion that led to this.

gabesullice’s picture

Status: Needs review » Needs work

I'm not sure if the "public" vs "internal" distinction that we have for fields makes sense here too. For now, going with it... although much if not all of the infrastructure does assume the entity type and bundle can be parsed from the JSON:API resource type name

We do not need it. The infrastructure does not assume that, at least not on purpose. We have ResourceType::getEntityTypeId() and ResourceType::getBundle() for that. That's because JSON:API Extras has always allowed resource types to be renamed. Parsing the resource type name would be a bug IMO.

Wim Leers’s picture

Issue tags: +Needs tests

👍 Perfect. That's what I thought at first, but upon cursory scan I wasn't so sure anymore. Thanks for confirming!

So, then shall we go with just setResourceTypeName()?

This will also need test coverage, it can be added to \Drupal\Tests\jsonapi\Kernel\ResourceType\ResourceTypeRepositoryTest.

gabesullice’s picture

So, then shall we go with just setResourceTypeName()?

Works for me!

mglaman’s picture

Status: Needs work » Needs review
Issue tags: -Needs tests
FileSize
2.02 KB

Renamed the method per #9 and #10. Added a test method to ResourceTypeRepositoryTest.

mglaman’s picture

Status: Needs review » Needs work

This doesn't work. \Drupal\jsonapi\ResourceType\ResourceType::__construct still builds the typeName based off of the entity type ID and bundle. We'd need to modify the constructor to accept the resource type name. Or a method for overriding it.

    $this->typeName = $this->bundle === '?'
      ? 'unknown'
      : sprintf('%s--%s', $this->entityTypeId, $this->bundle);
Wim Leers’s picture

#12: that's what I feared in #3 and #9.

mglaman’s picture

Status: Needs work » Needs review
FileSize
10.04 KB

Here's a stab at changing the constructor and test changes. We'll see what we do.

mglaman’s picture

I just realized this doesn't entirely solve our problem for the Commerce API.

  public function getPath() {
    return sprintf('/%s/%s', $this->getEntityTypeId(), $this->getBundle());
  }

The resource path is not built off of the resource type name. So you're going to have a product--default type available at /commerce_product/default.

mglaman’s picture

I don't know if this is scope creep, but I feel like the path should reflect the resource type name as it currently does. So I've changed the path output to

  public function getPath() {
    return '/' . implode('/', explode('--', $this->typeName));
  }

Also:

+++ b/core/lib/Drupal/Core/Security/PharExtensionInterceptor.php
@@ -31,7 +31,7 @@ class PharExtensionInterceptor implements Assertable {
-  public function assert(string $path, string $command): bool {
+  public function assert($path, $command) {

Whoops. I couldn't run tests locally without modifying this. Didn't mean to patch.

Status: Needs review » Needs work

The last submitted patch, 16: 3105318-16.patch, failed testing. View results

Wim Leers’s picture

I don't know if this is scope creep, but I feel like the path should reflect the resource type name as it currently does

Agreed.

mglaman’s picture

So I ran into a side effect here. There is no way to retrieve an aliased resource type for an entity type. Given an entity type ID you have to know how it was aliased so you can still run `\Drupal\jsonapi\ResourceType\ResourceTypeRepository::get`.

`get()` does the following:

return $this->getByTypeName("$entity_type_id--$bundle");

Currently, as a workaround, I am using something like this:

  private function getResourceTypesForEntityType(string $entity_type_id): array {
    if (!isset($this->entityTypeResourceTypes[$entity_type_id])) {
      $this->entityTypeResourceTypes[$entity_type_id] = array_filter($this->resourceTypeRepository->all(), static function (ResourceType $resource_type) use ($entity_type_id) {
        return $resource_type->getEntityTypeId() === $entity_type_id;
      });
    }
    return $this->entityTypeResourceTypes[$entity_type_id];
  }
mglaman’s picture

Oh, this totally breaks everything, like related fields

\Drupal\jsonapi\ResourceType\ResourceTypeRepository::getRelatableResourceTypesFromFieldDefinition

    return array_map(function ($target_bundle) use ($entity_type_id, $resource_types) {
      $type_name = "$entity_type_id--$target_bundle";
      return isset($resource_types[$type_name]) ? $resource_types[$type_name] : NULL;
    }, $target_bundles);

I think aliasing resource type names is going to be near impossible. For Commerce, all we want is to change the paths. We want to control `type` and paths :/

mglaman’s picture

It looks like some of the blockers are due to reverts in resource-type matching logic from #3018287: ResourceTypeRepository computes ResourceType value objects on *every request*

mglaman’s picture

This reverts changes in ResourceTypeRepository caused by #3018287 that break renamed resource types. It should cause this patch to pass. It also means JSON:API Extras is probably broken in 8.8.x as well if you rename resource types.

Status: Needs review » Needs work

The last submitted patch, 22: 3105318-22.patch, failed testing. View results

mglaman’s picture

Edit: not a problem, I messed up my assertion code. 🤦‍♂️

gabesullice’s picture

  1. +++ b/core/modules/jsonapi/src/ResourceType/ResourceType.php
    @@ -339,7 +341,7 @@ public function isVersionable() {
    -  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, $type_name = NULL, $internal = FALSE, $is_locatable = TRUE, $is_mutable = TRUE, $is_versionable = FALSE, array $fields = []) {
    

    If you move $type_name to the end, we can skip the whole BC song and dance. However, if we do want to do that song and dance, let's go all-in. Make it the first argument so that $entity_type_id and $bundle can more easily be made optional in the future.

    ... Actually, thinking about it more. Let's do that. Then we can move all the $this->typeName logic out of the constructor into the repository where you have $type_name = NULL;.

  2. +++ b/core/modules/jsonapi/src/ResourceType/ResourceType.php
    @@ -425,7 +430,7 @@ public function getRelatableResourceTypesByField($field_name) {
       public function getPath() {
    -    return sprintf('/%s/%s', $this->getEntityTypeId(), $this->getBundle());
    +    return '/' . implode('/', explode('--', $this->typeName));
       }
    

    I like this reformulation. It gets us further away from entities.

  3. +++ b/core/modules/jsonapi/src/ResourceType/ResourceTypeRepository.php
    @@ -433,8 +441,12 @@ protected function getRelatableResourceTypesFromFieldDefinition(FieldDefinitionI
         return array_map(function ($target_bundle) use ($entity_type_id, $resource_types) {
    -      $type_name = "$entity_type_id--$target_bundle";
    -      return isset($resource_types[$type_name]) ? $resource_types[$type_name] : NULL;
    +      foreach ($resource_types as $resource_type) {
    +        if ($resource_type->getEntityTypeId() === $entity_type_id && $resource_type->getBundle() === $target_bundle) {
    +          return $resource_type;
    +        }
    +      }
    +      return NULL;
         }, $target_bundles);
    

    Let's convert this to an array_reduce and simply drop the NULL values. This is a bug waiting to happen (it's probably already there).

  4. +++ b/core/modules/jsonapi/tests/modules/jsonapi_test_resource_type_building/src/EventSubscriber/ResourceTypeBuildEventSubscriber.php
    @@ -22,6 +22,7 @@ public static function getSubscribedEvents() {
    +        ['renamingResourceTypes'],
    
    @@ -75,4 +76,18 @@ public function disableResourceTypeFields(ResourceTypeBuildEvent $event) {
    +  public function renamingResourceTypes(ResourceTypeBuildEvent $event) {
    

    Nit: s/renamingResourceTypes/renameResourceType

gabesullice’s picture

Let's convert this to an array_reduce and simply drop the NULL values. This is a bug waiting to happen (it's probably already there).

I was pretty sure that this was probably a bug and I had a vague feeling that I had realized it before. I tracked that feeling down to this comment #3034786-37: ResourceIdentifier::getVirtualOrMissingResourceIdentifier() ignores field aliases; causes $relatable_resource_types field to be empty and results in an error.

I think we can/should just fix it here for efficiency's sake.


Edit: Actually, it's entirely appropriate to fix that bug here. The bug could only have been encountered if one were using JSON:API Extras. JSON:API core has no official way to alias resource types in JSON:API so, in theory, the bug doesn't exist and there was no good way to write a test for it. (JSON:API Extras does some tricky things to alias resource types and it wasn't ever "officially" supported)

Since this issue is all about creating a public, official API to alias resource types, it makes complete sense that the bug should be prevented in this issue.

xjm’s picture

Version: 9.0.x-dev » 9.1.x-dev
mglaman’s picture

Picking this back up

mglaman’s picture

  • #25.1: done, made it a mandatory parameter and the first one. The resource type repository is now what defines the default type name to be {$entity_type_id}--{$bundle}
  • #25.3 wouldn't that be a performance waste? array_reduce would go through every resource type, even after we have determined the proper one. Or I'm not following the suggestion.

#26 (follow up to 25.3) so we should do an array_filter to remove any keys from getRelatableResourceTypesFromFieldDefinition which may be null? I don't see the gains in array_reduce, as we know it should exit once we find the first (and only) match.

Status: Needs review » Needs work

The last submitted patch, 29: add_a_public_api_for-3105318-29.patch, failed testing. View results

mglaman’s picture

Status: Needs work » Needs review
FileSize
8.02 KB
16.8 KB

This should address the test failures.

Status: Needs review » Needs work

The last submitted patch, 31: add_a_public_api_for-3105318-31.patch, failed testing. View results

mglaman’s picture

Status: Needs work » Needs review
FileSize
4.26 KB
18.34 KB

This should fix the remaining tests – forgot to persist "unknown" as the type name for dangling references. Also the decorated ResourceTypeRepository in jsonapi_test_collection_count needed to be updated.

Status: Needs review » Needs work

The last submitted patch, 33: add_a_public_api_for-3105318-33.patch, failed testing. View results

mglaman’s picture

Status: Needs work » Needs review

Failures were completely unrelated, queued the tests again and it passed.

bbrala’s picture

Awesome patch, would love to see this in core. I went through the code and I have one concern. As mentioned in our discussion in slack about this patch.

+++ b/core/modules/jsonapi/src/ResourceType/ResourceTypeRepository.php
@@ -179,7 +182,12 @@ public function get($entity_type_id, $bundle) {
+    foreach ($this->all() as $resource) {
+      if ($resource->getEntityTypeId() === $entity_type_id && $resource->getBundle() === $bundle) {
+        return $resource;
+      }
+    }
+    return NULL;

When looking at this code i'm worried about the performance implications if removing the lookup table for finding the correct resources.

This would mean when serving a request with a set of resources the repository will loop through all resources for every access check for the set. This would possibly be quite a lot and probably even includes the related resources.

Perhaps we do need a static lookup table in some form again. Not sure how right now though.

mglaman’s picture

#36 has a great point. This patch adds a static mapping, which prevents duplicated loops to find a resource type. It was modified in #3018287: ResourceTypeRepository computes ResourceType value objects on *every request* to improve performance but broke the aliasing of resource types.

It leverages the memory cache so that its caches can be invalidated.


Edit:

+++ b/core/modules/jsonapi/src/ResourceType/ResourceTypeRepository.php
@@ -104,12 +111,13 @@ class ResourceTypeRepository implements ResourceTypeRepositoryInterface {
    * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher
    *   The event dispatcher.
    */
-  public function __construct(EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $entity_bundle_info, EntityFieldManagerInterface $entity_field_manager, CacheBackendInterface $cache, EventDispatcherInterface $dispatcher) {
+  public function __construct(EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $entity_bundle_info, EntityFieldManagerInterface $entity_field_manager, CacheBackendInterface $cache, EventDispatcherInterface $dispatcher, CacheBackendInterface $resource_type_mapping) {

Forgot to update a doc block.

jibran’s picture

Do we need a change notice here or need to amend an existing one?

bbrala’s picture

+++ b/core/modules/jsonapi/jsonapi.services.yml
@@ -82,7 +82,7 @@ services:
+    arguments: ['@entity_type.manager', '@entity_type.bundle.info', '@entity_field.manager', '@cache.jsonapi_resource_types', '@event_dispatcher', '@cache.jsonapi_memory']

Why not use cache.jsonapi_resource_types which is a chained cache with memory first and then the default cache which has been introduced for the ResourceRepository and is already included?

This cache could do the same, but with some persistence?

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

gabesullice’s picture

The patch in #2996114-167: Argument 2 passed to Drupal\jsonapi\Routing\Routes::Drupal\jsonapi\Routing\{closure}() must be an instance of Drupal\jsonapi\ResourceType\ResourceType, NULL given references this issue as a follow up since it is introducing a test related to resource type aliasing.

Before this patch lands, we will need to clean up the @todos that it introduced (assuming it lands).

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

bbrala’s picture

I was working on extra's and using build events there and came across issue again. I think it is time to get this moving again since #2996114: Argument 2 passed to Drupal\jsonapi\Routing\Routes::Drupal\jsonapi\Routing\{closure}() must be an instance of Drupal\jsonapi\ResourceType\ResourceType, NULL given has landed.

I've resolled the patch for 9.3 as a start.

bbrala’s picture

Missing docblock for ResourceTypeRepository contructor.

bbrala’s picture

bbrala’s picture

FileSize
6.48 KB

The last submitted patch, 44: 3105318-44.patch, failed testing. View results

Status: Needs review » Needs work

The last submitted patch, 45: 3105318-45.patch, failed testing. View results

bbrala’s picture

Status: Needs work » Needs review
FileSize
29.16 KB
984 bytes

Fixed Kernel/Normalizer/LinkCollectionNormalizerTest since it instantiates a ResourceType and was missing an argument.

bbrala’s picture

Invalidated.

bbrala’s picture

FileSize
28.94 KB
1.19 KB

Invalidated.

bbrala’s picture

bbrala’s picture

(I give up on commit style patches)

Cleaned up the code to lookup resources since we added a lookupResourceType method in a related issue. Made cache lookups consistent with the code in the all() method.

Also removed the extra cache from the service and class. We should just use the cache already present in the repository, no need to change the service. The CID can't overlap so it is fine to save it in permanent cache.

gabesullice’s picture

Status: Needs review » Needs work
Issue tags: +Needs change record
  1. +++ b/core/modules/jsonapi/src/ResourceType/ResourceType.php
    @@ -339,7 +341,7 @@ public function isVersionable() {
    -  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($type_name, $entity_type_id, $bundle, $deserialization_target_class, $internal = FALSE, $is_locatable = TRUE, $is_mutable = TRUE, $is_versionable = FALSE, array $fields = []) {
    

    I think this is the correct thing to do, but I suspect committers will ask us to put $type_name as a final, optional argument to maintain BC on this internal API on a "best effort" basis.

    In another issue, we could consider adding a static ResourceType::create method in order to have a more elegant method signature.

  2. +++ b/core/modules/jsonapi/src/ResourceType/ResourceType.php
    @@ -425,7 +424,7 @@ public function getRelatableResourceTypesByField($field_name) {
    -    return sprintf('/%s/%s', $this->getEntityTypeId(), $this->getBundle());
    +    return '/' . implode('/', explode('--', $this->typeName));
    

    This looks like a bugfix? E.g. if $this->bundle === '?' then the path would contain an illegal character in the path segment of the URL.

  3. +++ b/core/modules/jsonapi/src/ResourceType/ResourceTypeRepository.php
    @@ -153,13 +153,16 @@ protected function createResourceType(EntityTypeInterface $entity_type, $bundle)
    +    $type_name = $bundle === '?' ? 'unknown' : sprintf('%s--%s', $entity_type->id(), $bundle);
         if (!$internalize_resource_type) {
           $event = ResourceTypeBuildEvent::createFromEntityTypeAndBundle($entity_type, $bundle, $fields);
           $this->eventDispatcher->dispatch($event, ResourceTypeBuildEvents::BUILD);
           $internalize_resource_type = $event->resourceTypeShouldBeDisabled();
           $fields = $event->getFields();
    +      $type_name = $event->getResourceTypeName();
         }
         return new ResourceType(
    +      $type_name,
    

    Yeah, I think the way to do the BC dance is to keep the fallback inside the ResourceType class and make $type_name and optional argument with NULL as the default.

  4. +++ b/core/modules/jsonapi/src/ResourceType/ResourceTypeRepository.php
    @@ -180,7 +183,14 @@ public function get($entity_type_id, $bundle) {
    -    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 === FALSE) {
    +      $resource_type = static::lookupResourceType($this->all(), $entity_type_id, $bundle);
    +      $this->cache->set($map_id, $resource_type, Cache::PERMANENT, $this->cacheTags);
    +    }
    +
    +    return $cached ? $cached->data : $resource_type;
    

    Could we add this caching in a separate issue?

  5. +++ b/core/modules/jsonapi/src/ResourceType/ResourceTypeRepository.php
    @@ -188,7 +198,7 @@ public function get($entity_type_id, $bundle) {
    -    return isset($resource_types[$type_name]) ? $resource_types[$type_name] : NULL;
    +    return $resource_types[$type_name] ?? NULL;
    

    Fine, but could be a Task issue.

  6. +++ b/core/modules/jsonapi/tests/modules/jsonapi_test_collection_count/src/ResourceType/CountableResourceTypeRepository.php
    @@ -14,16 +14,17 @@ class CountableResourceTypeRepository extends ResourceTypeRepository {
    -      $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->getTypeName(),
    +      $resource_type->getEntityTypeId(),
    +      $resource_type->getBundle(),
    +      $resource_type->getDeserializationTargetClass(),
    +      $resource_type->isInternal(),
    +      $resource_type->isLocatable(),
    +      $resource_type->isMutable(),
    +      $resource_type->isVersionable(),
    +      $resource_type->getFields()
    

    😍

  7. +++ /dev/null
    @@ -1,33 +0,0 @@
    - * @todo remove this class in https://www.drupal.org/project/drupal/issues/3105318
    

    👏👏👏

  8. +++ b/core/modules/jsonapi/tests/src/Kernel/Controller/EntityResourceTest.php
    @@ -218,7 +218,7 @@ public function testGetEmptyCollection() {
    -    $resource_type = new ResourceType('node', 'article', NULL);
    +    $resource_type = new ResourceType('node--article', 'node', 'article', NULL);
    
    +++ b/core/modules/jsonapi/tests/src/Kernel/Normalizer/LinkCollectionNormalizerTest.php
    @@ -76,7 +76,7 @@ protected function setUp(): void {
    -    $link_context = new ResourceObject(new CacheableMetadata(), new ResourceType('n/a', 'n/a', 'n/a'), 'n/a', NULL, [], new LinkCollection([]));
    +    $link_context = new ResourceObject(new CacheableMetadata(), new ResourceType('n/a', 'n/a', 'n/a', 'n/a'), 'n/a', NULL, [], new LinkCollection([]));
    
    +++ b/core/modules/jsonapi/tests/src/Kernel/Query/FilterTest.php
    @@ -311,7 +311,7 @@ protected function savePaintings($paintings) {
    -    $resource_type = new ResourceType('foo', 'bar', NULL);
    +    $resource_type = new ResourceType('foo--bar', 'foo', 'bar', NULL);
    
    @@ -378,7 +378,7 @@ public function testCreateFromQueryParameterNested() {
    -    $resource_type = new ResourceType('foo', 'bar', NULL);
    +    $resource_type = new ResourceType('foo--bar', 'foo', 'bar', NULL);
    

    These changes could be avoided by making $type_name optional.

  9. +++ /dev/null
    @@ -1,87 +0,0 @@
    - * Despite JSON:API having a limited public PHP API, contrib and custom
    - * modules have found ways to rename resource types (e.g. "user--user" to
    - * "user") which causes JSON:API to malfunction. This results in confusion and
    - * bug reports. Since aliasing resource type names is a feature that JSON:API
    - * will eventually support, this test will help prevent future regressions and
    - * lower the present support burden.
    

    This is basically covering JSON:API Extras. Which, happily, you maintain! I'm not worried about removing this test, but I do think we should have a change record that describes the upgrade path from an aliasing repository to an event subscriber.

bbrala’s picture

Thanks for the review, at first glance they all make perfect sense. I'll get this cleaned up and perhaps split off some parts to keep it as small as possible.

bbrala’s picture

  1. Ok, i'll go with your defensive approach. Perhaps a create method somewhere is something for the future, but i wont make this patch even bigger right now at least.
  2. See #16 and #25. Mostly to keep things consistent really.
  3. Yeah, seems like the way to go. I'll move and change default for the ResourceTypeBuildEvent.
  4. I think if we do not do this we get possible performance issues. This code will be run a LOT as noted comment #36 (by me ;))
  5. Yeah might as well, ill create a new task and revert this
  6. :)
  7. :)
  8. Yeah i've gone through and checked all calls, lot of less changes now. While doing that i noticed a place where it wasnt changed also $type_3 = new ResourceType('entity_type_3', '123', EntityInterface::class, NULL, TRUE);. Guess that call was forgotten.
  9. Yeah change record should be present.
bbrala’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 56: 3105318-56.patch, failed testing. View results

bbrala’s picture

bbrala’s picture

Reverted accidental remove of jsonapi_test_field_aliasing module.

bbrala’s picture

Status: Needs work » Needs review
gabesullice’s picture

Status: Needs review » Reviewed & tested by the community

Looks good! My only reservation after #60 was still the cache. I was worried that going over the network to a database, even one like Redis, would be slower than a loop in PHP. However, I confirmed that the cache back end is an in-memory one, which mitigates my concern.

mglaman’s picture

Seeing this RTBC brings tears of joy to my eyes 🥲

bbrala’s picture

Draft change record available here.

gabesullice’s picture

Looks good! Thank you.

larowlan’s picture

Status: Reviewed & tested by the community » Needs work

This is looking great, just a couple of suggestions

  1. +++ b/core/modules/jsonapi/src/ResourceType/ResourceType.php
    @@ -338,8 +338,10 @@ public function isVersionable() {
    +   * @param null|string $type_name
    +   *   The resource type name.
    

    I think we should document that two hyphens will be used to construct the path, e.g -- becomes /

  2. +++ b/core/modules/jsonapi/src/ResourceType/ResourceTypeRepository.php
    @@ -153,11 +153,13 @@ protected function createResourceType(EntityTypeInterface $entity_type, $bundle)
    +    $type_name = $bundle === '?' ? 'unknown' : sprintf('%s--%s', $entity_type->id(), $bundle);
    
    @@ -167,7 +169,8 @@ protected function createResourceType(EntityTypeInterface $entity_type, $bundle)
    +      $type_name
    

    I don't think we need to initialise this - as passing null to the constructor will take care of that

  3. +++ b/core/modules/jsonapi/src/ResourceType/ResourceTypeRepository.php
    @@ -180,7 +183,14 @@ public function get($entity_type_id, $bundle) {
    +    if ($cached === FALSE) {
    +      $resource_type = static::lookupResourceType($this->all(), $entity_type_id, $bundle);
    +      $this->cache->set($map_id, $resource_type, Cache::PERMANENT, $this->cacheTags);
    ...
    +    return $cached ? $cached->data : $resource_type;
    

    this would read nicer if you flipped the logic, you'd avoid the second ternary

    if ($cached) {
      return $cached->data;
    }
    
    ...
    
bbrala’s picture

  1. #66.1 You are right. This is should also be included in the change record.
  2. #66.2 Good point! But in my opinion it should be initialized as NULL to be explicit about it.
  3. #66. OK, that is better, i agree. Will open a follow-up task then since the RecourseTypeRepository::all method uses exactly this logic. I chose consistency over readability here.

New patch incoming after I eat ;)

bbrala’s picture

I've been thinking and #66.2 does mean some extra breaks in contrib since this would mean anyone who now uses double dashes in a renamed resource would end up with a new path so i think something like

  public function getPath() {
    $defaultPath = sprintf('/%s/%s', $this->getEntityTypeId(), $this->getBundle());
    if($this->typeName === sprintf('%s--%s', $this->entityTypeId, $this->bundle)){
        return '/' . implode('/', explode('--', $this->typeName));
    }
    return '/' . $this->typeName;
  }

This would mean less breakage through contrib, but would mean it is impossible to rename a bundle only.

Pros:
- Less breakage in contrib with names containing double dashes.

Cons:
- Less flexible renaming of resources since you cannot rename something like node/page to node/content-page.

Another option is adding an option to enable/disable adding path separators. I'm looking for some extra opinions here I think.

mglaman’s picture

On mobile, so not s thorough review of recent updates. But we should not consider contrib here, in my opinion. This was never a public API and Amy contrib was hacks.

The only two contrib to worry about is JSONAPI Extras and Commerce API (which maintains it's kwk version of this code)

I like `--` becoming a standard constant to identify a split (like `:` for plugin derivatives)

bbrala’s picture

Status: Needs work » Reviewed & tested by the community
FileSize
5.14 KB
18.88 KB

After a discussion on Slack with @larowan, @mglaman and @e0ipso i decided to go through with this patch.

  1. I created #3225034: Simplify ResourceTypeRepository control flow for returning cached data to simplify some control flows in the ResourceTypeRepository.
  2. I updated the changerecord to include the new constant Drupal\jsonapi\ResourceType::TYPE_NAME_URI_PATH_SEPARATOR that defines how resource names are split to uri paths.
  3. As requested, the $typeName variable is now initilized as NULL in ResourceTypeRepository::createResourceType

Putting this back to RTBC as discussed with @larowan on Slack.

BR0kEN’s picture

  1. +++ b/core/modules/jsonapi/src/ResourceType/ResourceType.php
    @@ -355,7 +362,7 @@ public function __construct($entity_type_id, $bundle, $deserialization_target_cl
    +        : sprintf('%s' . self::TYPE_NAME_URI_PATH_SEPARATOR . '%s', $this->entityTypeId, $this->bundle);
    

    Suggestion: $this->entityTypeId . self::TYPE_NAME_URI_PATH_SEPARATOR . $this->bundle.

  2. +++ b/core/modules/jsonapi/src/ResourceType/ResourceType.php
    @@ -425,12 +432,13 @@ public function getRelatableResourceTypesByField($field_name) {
    +   *   The path to access this resource type. This function replaces "--" with
    +   *   a "/" in the uri path. Example: "node--article" -> "node/article"
    

    Suggestion: The path to access this resource type. This function replaces {@see TYPE_NAME_URI_PATH_SEPARATOR} with a "/" in the URI path. Example: "node--article" -> "node/article".

  3. +++ b/core/modules/jsonapi/src/ResourceType/ResourceTypeBuildEvent.php
    @@ -67,7 +67,7 @@ protected function __construct($resource_type_name, array $fields) {
    +    return new static(sprintf('%s' . ResourceType::TYPE_NAME_URI_PATH_SEPARATOR . '%s', $entity_type->id(), $bundle), $fields);
    

    Suggestion: return new static($entity_type->id() . ResourceType::TYPE_NAME_URI_PATH_SEPARATOR . $bundle, $fields);

  4. +++ b/core/modules/jsonapi/src/ResourceType/ResourceTypeBuildEvent.php
    @@ -84,7 +84,8 @@ public function getResourceTypeName() {
    +   *   The resource type name. The resource replaces "--" with
    +   *   a "/" in the uri path. Example: "node--article" -> "node/article"
    

    Suggestion: The resource type name. The {@see TYPE_NAME_URI_PATH_SEPARATOR} is replaced by a "/". Example: "node--article" -> "node/article".

bbrala’s picture

Status: Reviewed & tested by the community » Needs work
bbrala’s picture

I'll have a look, thanks.

bbrala’s picture

Status: Needs work » Needs review
FileSize
18.93 KB
2.46 KB

#72.1: Ok, the current contruction is indeed weird. But i think i rather sprintf('%s%s%s', ...args) then.
#72.2: Good point.
#72.3: See 1.
#72.4: See 2.

bbrala’s picture

Updated docblocks a bit more to be more readable. Also replaces sprinf with normal string concatting.

BR0kEN’s picture

  1. +++ b/core/modules/jsonapi/src/ResourceType/ResourceType.php
    @@ -349,9 +358,12 @@ public function __construct($entity_type_id, $bundle, $deserialization_target_cl
    +    $this->typeName = $type_name;
    +    if ($type_name === NULL) {
    +      $this->typeName = $this->bundle === '?'
    +        ? 'unknown'
    +        : $this->entityTypeId . self::TYPE_NAME_URI_PATH_SEPARATOR . $this->bundle;
    +    }
    

    Assigning a value and then reassign it in case it's NULL seem useless. Can we do the following?

    if ($type_name === NULL) {
      $this->typeName = $this->bundle === '?'
        ? 'unknown'
        : $this->entityTypeId . self::TYPE_NAME_URI_PATH_SEPARATOR . $this->bundle;
    }
    else {
      $this->typeName = $type_name;
    }
    
  2. +++ b/core/modules/jsonapi/src/ResourceType/ResourceTypeBuildEvent.php
    @@ -17,9 +17,9 @@ class ResourceTypeBuildEvent extends Event {
    +  protected $resourceTypeName = NULL;
    

    It's NULL by default, no need to explicitly declare that.

  3. +++ b/core/modules/jsonapi/src/ResourceType/ResourceTypeBuildEvent.php
    @@ -80,6 +80,17 @@ public function getResourceTypeName() {
    +  public function setResourceTypeName(string $resource_type_name) {
    

    Since this is the new public method, can we explicitly declare it should have no return statement by adding the void return type annotation?

bbrala’s picture

1. I think the current way is more readable in my opinion.
2. This is true technically, sure.
3. Yes we actually can, and should.

bbrala’s picture

FileSize
836 bytes

The cli tool seems to have rendered a broken interdiff.

BR0kEN’s picture

Status: Needs review » Reviewed & tested by the community
gabesullice’s picture

😍 loving the progress here.

larowlan’s picture

Status: Reviewed & tested by the community » Needs work

Just a couple of minor things here.
It might be worth asking in #documentation for some clarification on the best way to handle these @see references, but it seems like they're not compliant with the standard

  1. +++ b/core/modules/jsonapi/src/ResourceType/ResourceType.php
    @@ -18,6 +18,13 @@
    +   * @see getPath()
    

    According to the docs, this needs to be fully qualified

    I don't claim to be an expert, but the docs seems to indicate that's the case

  2. +++ b/core/modules/jsonapi/src/ResourceType/ResourceType.php
    @@ -420,12 +432,14 @@ public function getRelatableResourceTypesByField($field_name) {
    +   *   {@see TYPE_NAME_URI_PATH_SEPARATOR} with a "/" in the URI path.
    
    +++ b/core/modules/jsonapi/src/ResourceType/ResourceTypeBuildEvent.php
    @@ -80,6 +80,17 @@ public function getResourceTypeName() {
    +   *   {@see \Drupal\jsonapi\ResourceType\ResourceType::getPath()}.
    

    According to the standard, @see needs to be on it's own line, with a space

    https://www.drupal.org/docs/develop/standards/api-documentation-and-comm...

ravi.shankar’s picture

Status: Needs work » Needs review
FileSize
18.83 KB
1.41 KB

Made changes as per comment #81, please review.

bbrala’s picture

Status: Needs review » Reviewed & tested by the community
FileSize
779 bytes
19.74 KB

That is exactly what i wouldv'e done. I've updated the remaining docblock with the reference to the constant.

Setting to rtbc again since the changes are so minimal.

larowlan’s picture

Status: Reviewed & tested by the community » Needs work
+++ b/core/modules/jsonapi/tests/src/Kernel/ResourceType/ResourceTypeRepositoryTest.php
@@ -217,4 +207,19 @@ public function testResourceTypeFieldDisabling() {
diff --git a/core/scripts/dev/commit-code-check.sh b/core/scripts/dev/commit-code-check.sh

diff --git a/core/scripts/dev/commit-code-check.sh b/core/scripts/dev/commit-code-check.sh
old mode 100755
new mode 100644
diff --git a/core/scripts/drupal.sh b/core/scripts/drupal.sh

diff --git a/core/scripts/drupal.sh b/core/scripts/drupal.sh
old mode 100755
new mode 100644
diff --git a/core/scripts/password-hash.sh b/core/scripts/password-hash.sh

diff --git a/core/scripts/password-hash.sh b/core/scripts/password-hash.sh
old mode 100755
new mode 100644
diff --git a/core/scripts/rebuild_token_calculator.sh b/core/scripts/rebuild_token_calculator.sh

diff --git a/core/scripts/rebuild_token_calculator.sh b/core/scripts/rebuild_token_calculator.sh
old mode 100755
new mode 100644
diff --git a/core/scripts/run-tests.sh b/core/scripts/run-tests.sh

diff --git a/core/scripts/run-tests.sh b/core/scripts/run-tests.sh
old mode 100755
new mode 100644
diff --git a/core/scripts/update-countries.sh b/core/scripts/update-countries.sh

diff --git a/core/scripts/update-countries.sh b/core/scripts/update-countries.sh
old mode 100755
new mode 100644
diff --git a/core/tests/Drupal/Tests/Composer/Plugin/Scaffold/fixtures/scripts/disable-git-bin/git b/core/tests/Drupal/Tests/Composer/Plugin/Scaffold/fixtures/scripts/disable-git-bin/git

diff --git a/core/tests/Drupal/Tests/Composer/Plugin/Scaffold/fixtures/scripts/disable-git-bin/git b/core/tests/Drupal/Tests/Composer/Plugin/Scaffold/fixtures/scripts/disable-git-bin/git
old mode 100755
new mode 100644

all these file permissions changes are unintended, can you please revert these

bbrala’s picture

Status: Needs work » Reviewed & tested by the community
FileSize
18.88 KB

Sorry, forgot to add the local branch while generating the diff. Interdiff is empty for some reason, but when manualy comaring the patch had just the last set of lines removed.

  • larowlan committed fc51b4b on 9.3.x
    Issue #3105318 by bbrala, mglaman, ravi.shankar, Wim Leers, gabesullice...
larowlan’s picture

Status: Reviewed & tested by the community » Fixed

Committed fc51b4b and pushed to 9.3.x. Thanks!

Published the change record.

Nice work folks.

bbrala’s picture

Awesome! Thanks everyone!

Wim Leers’s picture

with 92 additions and 183 deletions

👏👏👏

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.