Problem/Motivation

While working on JSON:API Schema, which iterates over data types to derive a schema, this error occurs when comment module is installed:

[error] The "entity:comment:comment" plugin does not exist. Valid plugin IDs for Drupal\Core\TypedData\TypedDataManager are: {long list of data types...}

After some digging, it appears it's because EntityDeriver.php has this guard:

if ($bundle !== $entity_type_id) {
  $this->derivatives[$entity_type_id . ':' . $bundle] = [ ...

This is a problem because bundles can have the same ID as their associated entity type ID. This happens in core when the comment module is installed because of the "Default comment" bundle which has a machine name of comment.

Proposed resolution

Change the guard to check that the entity type does not have a bundle entity type and also to check that a bundle has not been defined by hook_entity_bundle_info(_alter) rather than the simple check that the bundle ID does not equal the entity type ID.

Remaining tasks

Supply a patch.
Write a test.
Review

User interface changes

None.

API changes

None.

Data model changes

None.

Release notes snippet

None.

CommentFileSizeAuthor
#3 3061610-2-test-only.patch2.5 KBgabesullice
#3 3061610-2.patch3.33 KBgabesullice
#7 interdiff.txt853 bytesgabesullice
#7 3061610-2-test-only.patch2.5 KBgabesullice
#7 3061610-7.patch3.32 KBgabesullice
#9 interdiff.txt917 bytesgabesullice
#9 3061610-9-test-only.patch2.59 KBgabesullice
#9 3061610-9.patch3.42 KBgabesullice
#11 interdiff-9-11.txt984 bytesgabesullice
#11 3061610-11-test-only.patch2.59 KBgabesullice
#11 3061610-11.patch3.9 KBgabesullice
#12 3061610-12.patch3.55 KBgabesullice
#20 interdiff-12-20.txt849 bytesgabesullice
#20 3061610-20.patch4.38 KBgabesullice
#25 interdiff.txt798 bytesWim Leers
#25 3061610-25.patch4.38 KBWim Leers
#26 interdiff-test.txt1.6 KBWim Leers
#26 interdiff-fix.txt1.05 KBWim Leers
#26 interdiff.txt2.59 KBWim Leers
#26 3061610-26.patch5.02 KBWim Leers
#27 interdiff.txt1.32 KBWim Leers
#27 3061610-27.patch4.85 KBWim Leers
#29 interdiff.txt1.86 KBgabesullice
#29 3061610-29.patch3.55 KBgabesullice
#31 3061610-29--correct.patch4.98 KBgabesullice
#42 interdiff-31-42.txt678 bytesgabesullice
#42 3061610-42.patch4.98 KBgabesullice
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

gabesullice created an issue. See original summary.

gabesullice’s picture

Assigned: Unassigned » gabesullice
gabesullice’s picture

Assigned: gabesullice » Unassigned
Status: Active » Needs review
FileSize
2.5 KB
3.33 KB
gabesullice’s picture

Issue summary: View changes

The last submitted patch, 3: 3061610-2-test-only.patch, failed testing. View results

Status: Needs review » Needs work

The last submitted patch, 3: 3061610-2.patch, failed testing. View results

gabesullice’s picture

Status: Needs work » Needs review
FileSize
853 bytes
2.5 KB
3.32 KB

I couldn't figure out why those two unrelated tests were failing and I spoke to @tedbow. He reminded me that bundles don't have to be entities, which made me realize that $entity_type->getKey('bundle') would work better than $entity_type->getBundleEntityType() for this. Hopefully he can be given issue credit for that help :)

Re-uploading the test only patch for readability.

The last submitted patch, 7: 3061610-2-test-only.patch, failed testing. View results

gabesullice’s picture

tedbow’s picture

+++ b/core/lib/Drupal/Core/Entity/Plugin/DataType/Deriver/EntityDeriver.php
@@ -99,7 +99,7 @@ public function getDerivativeDefinitions($base_plugin_definition) {
       foreach ($this->bundleInfoService->getBundleInfo($entity_type_id) as $bundle => $bundle_info) {
-        if ($bundle !== $entity_type_id) {
+        if ($entity_type->getKey('bundle')) {

Seems like this check should now be outside of the loop. No need to call `$this->bundleInfoService->getBundleInfo($entity_type_id)` because we can evaluate `$entity_type->getKey('bundle')` before the loop since it is not going to change.

Otherwise looks good. It should be same either way but it is small performance and clarity fix

gabesullice’s picture

Forest, trees... meet Gabe.

Good call @tedbow!

gabesullice’s picture

+++ b/core/lib/Drupal/Core/Entity/Plugin/DataType/Deriver/EntityDeriver.php
@@ -98,13 +98,13 @@ public function getDerivativeDefinitions($base_plugin_definition) {
-            'class' => $class,
-            'label' => $bundle_info['label'],
-            'constraints' => $this->derivatives[$entity_type_id]['constraints'],
-          ] + $base_plugin_definition;
+              'class' => $class,
+              'label' => $bundle_info['label'],
+              'constraints' => $this->derivatives[$entity_type_id]['constraints'],
+            ] + $base_plugin_definition;

The #11 patch had this indentation change in it, even though it wasn't in the interdiff. Fixed.

tedbow’s picture

Status: Needs review » Reviewed & tested by the community

Ok looks good. Nice catch with this issue!

RTBC assuming #12 passes and #11 test only fails.

The last submitted patch, 9: 3061610-9-test-only.patch, failed testing. View results

The last submitted patch, 11: 3061610-11.patch, failed testing. View results

The last submitted patch, 11: 3061610-11-test-only.patch, failed testing. View results

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 12: 3061610-12.patch, failed testing. View results

zrpnr’s picture

Confirmed that this patch in #12 applies cleanly to the current 8.7.x, and fixes the "entity:comment:comment" error in jsonapi_schema, exciting to see that module working!
Not marking RTBC yet though since all those patches just failed automated testing.

Wim Leers’s picture

Kernel tests containing knowledge of internal implementation details is once again getting in the way.

gabesullice’s picture

One line, easy fix.

gabesullice’s picture

Status: Needs work » Needs review

Whoops, back to NR

tedbow’s picture

Status: Needs review » Reviewed & tested by the community

Yep looks good!

larowlan’s picture

larowlan’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs tests
+++ b/core/lib/Drupal/Core/Entity/Plugin/DataType/Deriver/EntityDeriver.php
@@ -98,8 +98,8 @@ public function getDerivativeDefinitions($base_plugin_definition) {
+      if ($entity_type->getKey('bundle')) {
+        foreach ($this->bundleInfoService->getBundleInfo($entity_type_id) as $bundle => $bundle_info) {

I think this is still the wrong approach. Bundles can be provided by something other than a config entity, e.g. hook_entity_bundle_info and hook_entity_bundle_info_alter.

In my opinion, the correct fix also needs to check if the count of bundleInfoService->getBundleInfo() is greater than 1.

So something like:

$bundle_info = $this->bundleInfoService->getBundleInfo($entity_type_id);
if (count($bundle_info) > 1 || $entity->getKey('bundle'))

Also, needs a test for that where we have a hook that implements entity_bundle_info - entity_test module implements this, so you should be able to leverage that

Wim Leers’s picture

Version: 8.7.x-dev » 8.8.x-dev
FileSize
798 bytes
4.38 KB

Fixed a deprecation error that went unnoticed because this was rolled against 8.7.

Wim Leers’s picture

Status: Needs work » Needs review
Issue tags: -Needs tests +Needs issue summary update
FileSize
1.6 KB
1.05 KB
2.59 KB
5.02 KB

Addressed #24.

Thanks to @larowlan's excellent remark (🙏), we now know that the proposed resolution in the issue summary is also inaccurate: it's too simplistic. 😲

Wim Leers’s picture

  1. +++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityDeriverTest.php
    @@ -0,0 +1,94 @@
    +  /**
    +   * Modules to enable.
    +   *
    +   * @var array
    +   */
    +  public static $modules = ['system', 'field', 'user', 'node', 'comment', 'entity_test'];
    

    🤓 Formatting nits here.

  2. +++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityDeriverTest.php
    @@ -0,0 +1,94 @@
    +    $this->installEntitySchema('user');
    +    $this->installEntitySchema('node');
    +    $this->installEntitySchema('node_type');
    +    $this->installEntitySchema('comment');
    +    $this->installEntitySchema('comment_type');
    

    🤓 We don't need most of these entity schemas to be installed because we're not saving such entities.

Fixed.

Wim Leers’s picture

+++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityDeriverTest.php
@@ -0,0 +1,95 @@
+    entity_test_create_bundle('foo', NULL, 'entity_test_no_bundle');
+    entity_test_create_bundle('entity_test_no_bundle', NULL, 'entity_test_no_bundle');

FYI entity_test_no_bundle was specifically chosen because it does not list a bundle entity key! This is what @larowlan was getting at in #24. Took me some time to grok 😇

gabesullice’s picture

Issue summary: View changes
Status: Needs review » Reviewed & tested by the community
Issue tags: -Needs issue summary update
FileSize
1.86 KB
3.55 KB

Thanks @Wim Leers!

Just fixing two nits...

  1. +++ b/core/lib/Drupal/Core/Entity/Plugin/DataType/Deriver/EntityDeriver.php
    @@ -105,8 +105,9 @@ public function getDerivativeDefinitions($base_plugin_definition) {
    +      $bundle_info = $this->bundleInfoService->getBundleInfo($entity_type_id);
    +      if (count($bundle_info) > 1 || $entity_type->getKey('bundle')) {
    +        foreach ($this->bundleInfoService->getBundleInfo($entity_type_id) as $bundle => $bundle_info) {
    

    Nit: Let's not call ->getBundleInfo() twice.

  2. +++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityDeriverTest.php
    @@ -0,0 +1,95 @@
    +      'unbundleable entity type with entity_test_entity_bundle_info()-generated bundle type' => [
    ...
    +      'unbundleable entity type with entity_test_entity_bundle_info-generated bundle type with matching name' => [
    

    Nit: One has () and the other does not.

larowlan’s picture

Status: Reviewed & tested by the community » Needs work

Something went wrong with that patch @gabesullice, patch size dropped dramatically and it no-longer applies

error: patch failed: core/lib/Drupal/Core/Entity/Plugin/DataType/Deriver/EntityDeriver.php:105
error: core/lib/Drupal/Core/Entity/Plugin/DataType/Deriver/EntityDeriver.php: patch does not apply
error: core/tests/Drupal/KernelTests/Core/Entity/EntityDeriverTest.php: does not exist in index
make: *** [patch88] Error 1
gabesullice’s picture

Wim Leers’s picture

Status: Needs work » Reviewed & tested by the community

  • larowlan committed 6d3b05c on 8.8.x
    Issue #3061610 by gabesullice, Wim Leers, tedbow, larowlan: Typed Data's...
larowlan’s picture

Status: Reviewed & tested by the community » Fixed

Committed 6d3b05c and pushed to 8.8.x. Thanks!

🎧 this commit was brought to you by henchlock by thee oh sees

gabesullice’s picture

Version: 8.8.x-dev » 8.7.x-dev
Issue tags: +Contributed project blocker

Any chance of this getting backported since it's a bug and it's blocking the JSON:API Schema module when the comment module is enabled?

gabesullice’s picture

Status: Fixed » Needs review
Wim Leers’s picture

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

Status: Reviewed & tested by the community » Fixed

C/p as 8adf90654d and pushed to 8.7.x

  • larowlan committed 8adf906 on 8.7.x
    Issue #3061610 by gabesullice, Wim Leers, tedbow, larowlan: Typed Data's...

  • larowlan committed d04a668 on 8.7.x
    Revert "Issue #3061610 by gabesullice, Wim Leers, tedbow, larowlan:...
larowlan’s picture

Status: Fixed » Needs work

Rolled this back on 8.7.x as it broke php 5.5 testing, expectException does not exist in the version of phpunit we use on php 5.

See https://www.drupal.org/pift-ci-job/1381558

gabesullice’s picture

Status: Needs work » Needs review
FileSize
678 bytes
4.98 KB
Wim Leers’s picture

Status: Needs review » Reviewed & tested by the community

#42's fix looks good and it passed PHP 5 testing so 👍

larowlan’s picture

Status: Reviewed & tested by the community » Fixed

Committed e731e88 and pushed to 8.7.x. Thanks!

  • larowlan committed e731e88 on 8.7.x
    Issue #3061610 by gabesullice, Wim Leers, larowlan, tedbow: Typed Data's...
claudiu.cristea’s picture

Status: Fixed » Closed (fixed)

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

Luke.Leber’s picture

I have a question about this topic regarding the documentation on \Drupal\Core\Entity\EntityAccessControlHandler::checkCreateAccess and friends.

The documentation is as follows (trimmed for brevity):

* @param string|null $entity_bundle
* (optional) The bundle of the entity. Required if the entity supports
* bundles, defaults to NULL otherwise.
*
* @return \Drupal\Core\Access\AccessResultInterface
* The access result.
*/
protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {

It appears that after this has landed, the result of \Drupal\Core\Entity\EntityTypeInterface::getBundleEntityType is no longer respected to determine whether or not an entity has bundles or not.

As a result, instead of NULL being passed to the access control handler, it's passing a default bundle that's the same as the entity type id.

Is this a BC concern? We've had one minor breakage from what appears to have resulted from this.

If not, should we update the documentation within the EntityAccessControlHandler?

Thanks :)

gabesullice’s picture

Hm, @Luke.Leber, it seems really bizarre to me that this issue would have caused the regression you're describing. Do you mind describing how you determined/confirmed that this patch caused the regression and/or a little more detail/explanation about the code that was impacted?

diff --git a/core/lib/Drupal/Core/Entity/Plugin/DataType/Deriver/EntityDeriver.php b/core/lib/Drupal/Core/Entity/Plugin/DataType/Deriver/EntityDeriver.php
index 38ac960cc1..e5051b9318 100644
--- a/core/lib/Drupal/Core/Entity/Plugin/DataType/Deriver/EntityDeriver.php
+++ b/core/lib/Drupal/Core/Entity/Plugin/DataType/Deriver/EntityDeriver.php
@@ -98,11 +98,12 @@ public function getDerivativeDefinitions($base_plugin_definition) {
       ] + $base_plugin_definition;
 
       // Incorporate the bundles as entity:$entity_type:$bundle, if any.
-      foreach ($this->bundleInfoService->getBundleInfo($entity_type_id) as $bundle => $bundle_info) {
-        if ($bundle !== $entity_type_id) {
+      $bundle_info = $this->bundleInfoService->getBundleInfo($entity_type_id);
+      if (count($bundle_info) > 1 || $entity_type->getKey('bundle')) {
+        foreach ($bundle_info as $bundle => $info) {
           $this->derivatives[$entity_type_id . ':' . $bundle] = [
             'class' => $class,
-            'label' => $bundle_info['label'],
+            'label' => $info['label'],
             'constraints' => $this->derivatives[$entity_type_id]['constraints'],
           ] + $base_plugin_definition;
         }

This ^ is the entirety of the patch, excluding tests. It's really difficult for me to see how that would cause this:

It appears that after this has landed, the result of \Drupal\Core\Entity\EntityTypeInterface::getBundleEntityType is no longer respected to determine whether or not an entity has bundles or not.

Maybe you have some extra insight into how it reverberated through the codebase to cause your issue?

Luke.Leber’s picture

Good evening,

I'll try to provide exact reproduction details tonight without any of our custom code in place on a fresh 8.7 installation.

If this doesn't happen with the core entity types (I'll be targeting the User entity) then I'll just chalk it up to a quirk somewhere else in our codebase. In either case I'll let you know.

Thanks

gabesullice’s picture

(I'll be targeting the User entity)

I would also try with the Comment entity, since that was the entity type which this issue intended to fix.

Luke.Leber’s picture

Hello,

After tracing things out more thoroughly, it actually seems that https://www.drupal.org/node/3038254 may be a more probable avenue to explain the change that I am seeing.

Here are the steps that I took to reproduce:

1) Install Drupal 8.7.7
2) Enable the media library module
3) Create an image media type
4) Add an image.
5) Create an entity reference field on the user entity to a media item
6) Go to the user add form and open the media library widget and select a media item, but do not insert it yet.
7) Set a breakpoint at line 284 in EntityAccessControlHandler.php
8) Click insert to pause at the breakpoint in the XHR request.
9) Observe that $context['entity_type_id'] is 'user' and that $bundle is also 'user'.

I completely missed that change record earlier. Sorry about any confusion that I may have caused here.

I'll follow up with the media team.

Thanks again

Wim Leers’s picture