If an entity has a bundle entity, but there are no bundle entities existing, EntityTypeBundleInfo::getAllBundleInfo() will not have a key in the return array for that entity type.

This means that for example doing this:

    $entity_types = $entity_type_manager->getDefinitions();
    $bundles = $entity_type_bundle_info->getAllBundleInfo();
    foreach ($entity_types as $entity_type_id => $entity_type) {
      foreach ($bundles[$entity_type_id] as $bundle_id => $bundle_info) {
        // ...

will throw notices, and requires a guard clause to check that $bundles has the entity type present.

The documentation for getAllBundleInfo() suggests that it returns data on all entity types:

> Get the bundle info of all entity types.

So for entity types that have no bundles, there should be an empty array.

EDIT:
Steps to recreate:
1) Create module with Drupal Console: drupal gm
2) Generate a controller: drupal gcon
3) Add the following code to the controller function:

public function getHelloEntities {
 $entity_types = $this->entityManager->getDefinitions();
    $bundles = $this->entityManager->getAllBundleInfo();
    $list = '';
    foreach ($entity_types as $entity_type_id => $entity_type) {
      foreach ($bundles[$entity_type_id] as $bundle_id => $bundle_info) {
        $list .= $bundle_id . '::' . implode($bundle_info) . '</ br>';
      }
    }
    return [
      '#type' => 'markup',
      '#markup' => $list
    ];
  }
}

4) Go to "/admin/structure/block/block-content/types" and delete the existing bundle type entity for entity block_content.
5) Go to the controller page "/modulename/getHelloEntities
6) This will now output notices on report page of "Notice: Undefined index: block_content"

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

joachim created an issue. See original summary.

roborew’s picture

Issue summary: View changes

Issue is related to the following bit of code:

          if ($bundle_entity_type = $entity_type->getBundleEntityType()) {
            foreach ($this->entityTypeManager->getStorage($bundle_entity_type)->loadMultiple() as $entity) {
              $this->bundleInfo[$type][$entity->id()]['label'] = $entity->label();
            }
          }
          // If entity type bundles are not supported and
          // hook_entity_bundle_info() has not already set up bundle
          // information, use the entity type name and label.
          elseif (!isset($this->bundleInfo[$type])) {
            $this->bundleInfo[$type][$type]['label'] = $entity_type->getLabel();
          }

When the entity is confirmed to have bundle types entities then the following foreach loop will load all bundle types entities for the parent entity, if none exist then nothing is added to the $bundleInfo array.

Proposed solution is to change the elseif to an if statement to catch all entities that have not been set and add the minimum info required.

roborew’s picture

Issue summary: View changes
joachim’s picture

That's not quite it -- that piece of code is for entity types which don't use bundles *at all*.

The problem this issue is about is for entity types that do use bundles, but happen not to have any right now:

  // First look for entity types that act as bundles for others, load them
  // and add them as bundles.
  if ($bundle_entity_type = $entity_type->getBundleEntityType()) {
    # We get here!
    foreach ($this->entityTypeManager->getStorage($bundle_entity_type)->loadMultiple() as $entity) {
      # but never in this loop, because there are *no* bundle entities to load.
      $this->bundleInfo[$type][$entity->id()]['label'] = $entity->label();
    }
  }
  // If entity type bundles are not supported and
  // hook_entity_bundle_info() has not already set up bundle
  // information, use the entity type name and label.
  elseif (!isset($this->bundleInfo[$type])) {
    $this->bundleInfo[$type][$type]['label'] = $entity_type->getLabel();
  }
}
# at this point, we've not set $this->bundleInfo[$type]

roborew’s picture

Here's a patch..

roborew’s picture

Status: Active » Needs review
Issue tags: +Vienna2017
joachim’s picture

Status: Needs review » Needs work

Thanks for the patch!

+++ b/core/lib/Drupal/Core/Entity/EntityTypeBundleInfo.php
@@ -101,9 +101,10 @@ public function getAllBundleInfo() {
-          elseif (!isset($this->bundleInfo[$type])) {
+          if (!isset($this->bundleInfo[$type])) {

That's not the right fix.

We don't want to come here in the situation described. We have no bundles, but this code would make it look like we DO have a bundle, with the same name as the entity type.

The right fix is to ensure that if the foreach loop is empty, we get an empty array set for the $type key.

roborew’s picture

FileSize
1.12 KB

Yep see what you mean, not ideal and it also fails the tests.

Here's a re-write, created an if statement before the foreach, if it fails it will return an empty array based on the $type.

joachim’s picture

+++ b/core/lib/Drupal/Core/Entity/EntityTypeBundleInfo.php
@@ -96,8 +96,12 @@ public function getAllBundleInfo() {
+            } else {

You've coddled the else...

Though this patch adds a level of nesting, which lowers readability.

What I would do is prepare the array just before the loop:

$this->bundleInfo[$type] = [];

Then it's always going to have the minimum needed.

joachim’s picture

Issue tags: +Needs tests

Could do with tests too... though I don't see *ANY* tests in core that cover the entity_type.bundle.info service!!!

At any rate, entity_test_with_bundle is the test entity type from entity_test module that a test should use. Test should install that, but not create any bundle entities.

roborew’s picture

FileSize
782 bytes

Here's the simplified patch. Had a look at the test failures and not entirely sure that this one won't also fail.. I don't have the entire test suite for BrowserTestBase setup.. will sort this and hopefully take a look at adding some tests. In the meantime ill let the test runner do its thing.

joachim’s picture

Nice!

This should be tested with a Kernel test, rather than BrowserTestBase.

roborew’s picture

Assigned: Unassigned » roborew

Assigning to me, tests coming ;-)

roborew’s picture

FileSize
1.51 KB

Dug into this a little further and now not sure if the approach above is best. Firstly found that the current method is covered by tests:

/**
 * @coversDefaultClass \Drupal\Core\Entity\EntityTypeBundleInfo
 * @group Entity
 */

These are still passing fine, so no problem here. The issue is that the content_translation tests fail in: "/core/modules/content_translation/tests/src/Functional/ContentTranslationEnableTest.php" as the module is calling:

    $bundles = $this->entityManager->getAllBundleInfo();

On the admin settings screen "admin/config/regional/content-language" entities that don't have bundles should not be displayed. Therefore for the patch above to work and pass the other content_translation tests we would need to add an array filter after the initial look up to remove the empty entity array:

    $bundles = $this->entityManager->getAllBundleInfo();
    $bundles = array_filter($bundles);

But i don't think we can presume this how other modules would want to use the method in the future and the change might lead to contrib modules displaying an entity when there are no entity bundle types created for it. In most cases I think the current implementation is probably correct as it does not require additional clean up for displaying the correct list of entity types. I've added the patch to illustrate the change that would be required for tests to pass currently. But i'd be interested to know what the best solution would be in this case, there's probably a better way todo this.

joachim’s picture

Looks like EntityTypeBundleInfoTest is missing some coverage then...

> In most cases I think the current implementation is probably correct as it does not require additional clean up for displaying the correct list of entity types

But the current implementation does not do what its docs say it does. At least one is wrong.

> On the admin settings screen "admin/config/regional/content-language" entities that don't have bundles should not be displayed.

I think what we say with this is that that code is relying on buggy implementation...

Andy_Read’s picture

I've been discussing this with @roborew and we think that we need to raise a separate issue against the Content Translation admin page.

  1. To correct the test, so that this patch passes.
  2. Instead to improve the UI and display the entity type but then show "No bundles available".
roborew’s picture

Status: Needs work » Needs review
FileSize
5.71 KB

@joachim found some time to dig a little deeper, and your right the test coverage was not scratching the critical part of the getAllBundleInfo() i.e:

          if ($bundle_entity_type = $entity_type->getBundleEntityType()) {
            foreach ($this->entityTypeManager->getStorage($bundle_entity_type)->loadMultiple() as $entity) {
              $this->bundleInfo[$type][$entity->id()]['label'] = $entity->label();
            }
          }

And therefore there was no way of testing that '$this->bundleinfo' array was created correctly based on whether bundles existed or not. I've patched 'EntityTypeBundleInfoTest' to provide the setup to mock these elements and to enforce the right implementation of the method.

This change has a knock on affect to the Content Language settings form 'admin/config/regional/content-language' and will cause test failures. I've included an amend in 'ContentLanguageSettingsForm' to account for this change. Wasn't sure whether to create a separate issue and patch for this, and risk an issue dependancy. Therefore, i've included it here for brevity and to make sure all tests are still passing.

roborew’s picture

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

joachim’s picture

Status: Needs review » Needs work
+++ b/core/modules/language/src/Form/ContentLanguageSettingsForm.php
@@ -56,6 +56,9 @@ public function buildForm(array $form, FormStateInterface $form_state) {
+    // Due to https://www.drupal.org/node/2910852 patch for getAllBundleInfo()
+    // necessitates array_filter() to remove empty array bundles to pass tests.
+    $bundles = array_filter($bundles);

The wording of the comment shouldn't refer to a patch.

But anyway, why not change it here instead:

    foreach ($entity_types as $entity_type_id => $entity_type) {
      if (!$entity_type instanceof ContentEntityTypeInterface || !$entity_type->hasKey('langcode') || !isset($bundles[$entity_type_id])) {

change the !isset($bundles[$entity_type_id]) to empty($bundles[$entity_type_id]), and that should preserve the same effect.

savkaviktor16@gmail.com’s picture

Status: Needs work » Needs review
FileSize
5.71 KB
1.13 KB

I took the liberty of modifying the patch according to @joachim's comment

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

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

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

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.

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.

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

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

joachim’s picture

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

Assigned: roborew » Unassigned
Status: Reviewed & tested by the community » Needs review
FileSize
5.7 KB
2.85 KB

Reroll patch against 9.4.x & also added reroll diff for reviewers

yogeshmpawar’s picture

Resolved CSpell errors.

joachim’s picture

Status: Needs review » Reviewed & tested by the community

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

alexpott’s picture

+++ b/core/modules/language/src/Form/ContentLanguageSettingsForm.php
@@ -79,7 +79,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
-      if (!$entity_type instanceof ContentEntityTypeInterface || !$entity_type->hasKey('langcode') || !isset($bundles[$entity_type_id])) {
+      if (!$entity_type instanceof ContentEntityTypeInterface || !$entity_type->hasKey('langcode') || empty($bundles[$entity_type_id])) {

For me the fact that we have to make this change means that I'm not sure the behaviour change is actually worth it. Contrib code could be doing exactly the same and would be broken by this change. I think we should close this as works as designed. Going to ask the other framework and entity subsystem maintainers.

catch’s picture

Yes I tend to agree with #34, seems like that could equally break things the other way.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: -Needs framework manager review

Discussed with @Berdir, @longwave and @catch. We all have concerns about making this change. @longwave suggested that rather than making the change we should update the docs to be clear about the behaviour. @catch's suggested change is:

-   * Get the bundle info of all entity types.
+   * Get the bundle info of all entity types with bundles.
pradhumanjain2311’s picture

Assigned: Unassigned » pradhumanjain2311
pradhumanjain2311’s picture

Assigned: pradhumanjain2311 » Unassigned
Status: Needs work » Needs review
FileSize
592 bytes

@alexpott i made changes as per your comment #36. Needs Review

mglaman’s picture

Contrib code could be doing exactly the same and would be broken by this change.

I'm not sure how contrib would be broken? It'd be nice to have an array that doesn't need additional isset and count checks so that we can use array manipulation functions more easily.

+++ b/core/modules/language/src/Form/ContentLanguageSettingsForm.php
@@ -79,7 +79,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
-      if (!$entity_type instanceof ContentEntityTypeInterface || !$entity_type->hasKey('langcode') || !isset($bundles[$entity_type_id])) {
+      if (!$entity_type instanceof ContentEntityTypeInterface || !$entity_type->hasKey('langcode') || empty($bundles[$entity_type_id])) {

Can we use count($bundles[$entity_type_id]) === 0?

alexpott’s picture

@mglaman in exactly the same way core is broken - see the change to core/modules/language/src/Form/ContentLanguageSettingsForm.php - anything in contrib or custom that does something similar will be broken.

mglaman’s picture

😬 oh duh, that wasn't done just for the fun of it. Sorry! Then yeah I guess that makes this a breaking change and something we'll need to live with forever?

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

timohuisman’s picture

I see #36, but unfortunately we still need this patch. I've rerolled #31 against 9.5.x and 10.1.x.

The patch from #38 was made for 9.5.x, but it also applies to 10.1.x.

alexpott’s picture

@timohuisman why do you need this patch?

timohuisman’s picture

@alexpott, I should have been clearer; we are using this patch, I'm not entirely sure if we still need it. I've run a few tests on our project and it seems to work fine without it, but I'm a bit hesitant to just remove the patch. I will discuss it with my team and report back as soon as I know more.

At least for now I've fixed the code style issues of #43.

The last submitted patch, 45: 2910852-45-9.5.x.patch, failed testing. View results

joachim’s picture

> Then yeah I guess that makes this a breaking change and something we'll need to live with forever?

It's doable in stages:

- 10.x: deprecate getAllBundleInfo(), add getREALLYAllBundleInfo() with new behaviour
- 11.x: deprecate getREALLYAllBundleInfo(), add back getAllBundleInfo with same behaviour as getREALLYAllBundleInfo()
- 12.x: remove getREALLYAllBundleInfo()

alexpott’s picture

There's a better way.

  1. In Drupal 10.x Introduce a flag as an argument on \Drupal\Core\Entity\EntityTypeBundleInfo::getAllBundleInfo() which is set by default to FALSE but triggers a deprecation when set to FALSE. If set to TRUE we have the new behaviour.
  2. In Drupal 11.x remove the flag and deprecate passing in an argument at all. (By this time we might rely on PHPStan for argument removal notifications so we might not have to add a deprecation for calling with an used argument.
  3. In Drupal 12.x remove the deprecation of passing in an argument (if necessary)

This way we have less impact on callers from contrib/custom - they only need to make two changes. I.e to add the TRUE to the call in D10 and to remove it in D11.

alexpott’s picture

Category: Bug report » Task

@timohuisman the only reason you'd need this patch is because you custom code or some contrib code relies on it. You can search that code for calls to this method and see. This patch does fix not an actual bug.

joachim’s picture

Category: Task » Bug report

Re #48 - very nice!

> This patch does fix not an actual bug

It's a bug because the docs say:

> * Get the bundle info of all entity types.

and the returned array does not have all entity types in it.

needs-review-queue-bot’s picture

Status: Needs review » Needs work
FileSize
2.33 KB

The Needs Review Queue Bot tested this issue. It either no longer applies to Drupal core, or fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".

Apart from a re-roll or rebase, this issue may need more work to address feedback in the issue or MR comments. To progress an issue, incorporate this feedback as part of the process of updating the issue. This helps other contributors to know what is outstanding.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.