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"
Comment | File | Size | Author |
---|---|---|---|
#51 | 2910852-nr-bot.txt | 2.33 KB | needs-review-queue-bot |
#45 | 2910852-45-10.1.x.patch | 5.64 KB | timohuisman |
#45 | 2910852-45-9.5.x.patch | 5.73 KB | timohuisman |
#43 | reroll_diff_31_43.txt | 1.86 KB | timohuisman |
#38 | 2910852-38.patch | 592 bytes | pradhumanjain2311 |
Comments
Comment #2
roborew CreditAttribution: roborew at Christian Aid commentedIssue is related to the following bit of code:
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.
Comment #3
roborew CreditAttribution: roborew at Christian Aid commentedComment #4
joachim CreditAttribution: joachim commentedThat'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:
Comment #5
roborew CreditAttribution: roborew at Christian Aid commentedHere's a patch..
Comment #6
roborew CreditAttribution: roborew at Christian Aid commentedComment #7
joachim CreditAttribution: joachim commentedThanks for the patch!
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.
Comment #8
roborew CreditAttribution: roborew at Christian Aid commentedYep 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.
Comment #9
joachim CreditAttribution: joachim commentedYou'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:
Then it's always going to have the minimum needed.
Comment #10
joachim CreditAttribution: joachim as a volunteer commentedCould 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.
Comment #11
roborew CreditAttribution: roborew at Christian Aid commentedHere'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.
Comment #12
joachim CreditAttribution: joachim as a volunteer commentedNice!
This should be tested with a Kernel test, rather than BrowserTestBase.
Comment #13
roborew CreditAttribution: roborew at Christian Aid commentedAssigning to me, tests coming ;-)
Comment #14
roborew CreditAttribution: roborew at Christian Aid commentedDug 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:
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:
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:
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.
Comment #15
joachim CreditAttribution: joachim as a volunteer commentedLooks 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...
Comment #16
Andy_Read CreditAttribution: Andy_Read at Azurite Media Ltd commentedI've been discussing this with @roborew and we think that we need to raise a separate issue against the Content Translation admin page.
Comment #17
roborew CreditAttribution: roborew at Christian Aid commented@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:
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.
Comment #18
roborew CreditAttribution: roborew at Christian Aid commentedI've created an issue #2913824: Use of getAllBundleInfo() in ContentLanguageSettingsForm based on incorrect/buggy implementation to review the amend to 'ContentLanguageSettingsForm' and to proposed an alternative solution.
Comment #20
joachim CreditAttribution: joachim as a volunteer commentedThe wording of the comment shouldn't refer to a patch.
But anyway, why not change it here instead:
change the !isset($bundles[$entity_type_id]) to empty($bundles[$entity_type_id]), and that should preserve the same effect.
Comment #21
savkaviktor16@gmail.com CreditAttribution: savkaviktor16@gmail.com at Skilld commentedI took the liberty of modifying the patch according to @joachim's comment
Comment #29
joachim CreditAttribution: joachim at Factorial GmbH commentedComment #30
yogeshmpawarReroll patch against 9.4.x & also added reroll diff for reviewers
Comment #31
yogeshmpawarResolved CSpell errors.
Comment #32
joachim CreditAttribution: joachim at Factorial GmbH commentedComment #34
alexpottFor 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.
Comment #35
catchYes I tend to agree with #34, seems like that could equally break things the other way.
Comment #36
alexpottDiscussed 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:
Comment #37
pradhumanjain2311 CreditAttribution: pradhumanjain2311 at OpenSense Labs for DrupalFit commentedComment #38
pradhumanjain2311 CreditAttribution: pradhumanjain2311 at OpenSense Labs for DrupalFit commented@alexpott i made changes as per your comment #36. Needs Review
Comment #39
mglamanI'm not sure how contrib would be broken? It'd be nice to have an array that doesn't need additional
isset
andcount
checks so that we can use array manipulation functions more easily.Can we use
count($bundles[$entity_type_id]) === 0
?Comment #40
alexpott@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.
Comment #41
mglaman😬 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?
Comment #43
timohuismanI 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.
Comment #44
alexpott@timohuisman why do you need this patch?
Comment #45
timohuisman@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.
Comment #47
joachim CreditAttribution: joachim as a volunteer commented> 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()
Comment #48
alexpottThere's a better way.
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.
Comment #49
alexpott@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.
Comment #50
joachim CreditAttribution: joachim as a volunteer commentedRe #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.
Comment #51
needs-review-queue-bot CreditAttribution: needs-review-queue-bot as a volunteer commentedThe 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.