Problem/Motivation
The EntityTypeRepository checks that there is only one class per entity type. However, in contrib such as ECK, entity bundle classes are generated dynamically from configuration with the module not able to dynamically create classes per configured bundle. This means that the class itself must be the same as PHP does not support Generics. So creating two bundles derived from the same ECK entity currently throws an AmbiguousEntityClassException.
EntityTypeRepositoryInterface::getEntityTypeFromClass triggers this issue. The workaround to manually create a class for every ECK entity type renders the module unusable, but even if it were to be possible, it would still fail on this line (is_subclass_of($class_name, $entity_type->getOriginalClass())) because the original class is still Drupal\eck\Entity\EckEntity, regardless of any overrides.
The code in question was introduced in #2570593.
Steps to reproduce
- Install the eck module
- Create two eck entity types + entity type classes subclassing
EckEntity, and register them using hook_entity_type_alter - Create a bundle on one of those entity types + a class for that bundle, and register it using hook_entity_bundle_info_alter
- Call
EntityTypeRepository::getEntityTypeFromClass()with the bundle class name.
Proposed resolution
Require passing entity bundle info to the EntityTypeRepository to avoid ambiguous calls when there are two or more bundles for the same entity class.
| Comment | File | Size | Author |
|---|
Issue fork drupal-3257457
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #4
dieterholvoet commentedI created a MR with a proposed fix.
Comment #5
smustgrave commentedMoving back to NW. The MR is failing the test cases. If it's a bug it will need it's own test case also.
Would you say this is a duplicate of https://www.drupal.org/project/drupal/issues/3233143#comment-14631762 ?
Comment #8
dieterholvoet commentedI fixed the existing test and added a new test demonstrating the bug.
Comment #9
dieterholvoet commentedComment #10
dieterholvoet commentedThe test-only patch is failing, the MR with the fix is passing. Setting to Needs review.
Comment #11
smustgrave commentedCan the MR be updated in 11.x?
Also will need a trigger_error for the new parameter. To not break existing sites.
Comment #12
dieterholvoet commentedI created a draft change notice to mention in the deprecation message. Am I correct if I'm saying this will be deprecated in drupal:11.0.0 and required in drupal:12.0.0?
Comment #13
dieterholvoet commentedComment #14
smustgrave commentedNot deprecated. Would have to look up an example but required in D11 but added in 10.2
11.x is actually going to be 10.2
Think of it as a master branch now and releases will be off 11.x till gitlab
Comment #15
dieterholvoet commentedOkay, I updated the versions in code and in the change record.
Comment #16
smustgrave commentedFor the CC failure in the MR.
Comment #17
dieterholvoet commentedComment #18
smustgrave commentedThanks for taking care of that
Running the tests locally without the fix fails correctly
Drupal\Core\Entity\Exception\AmbiguousBundleClassException : Multiple entity types found for Multiple bundles are using the bundle class Drupal\Tests\Core\Entity\RoyalGala..
So even though the issue summary is talking eck tests show this is possible without a contrib too.
Comment #19
larowlanComing from the original issue, this was added for following use-case
Say you have a node-type called page with a bundle class \Drupal\mymodule\Entity\Bundle\Page
You should be able to do the following
So it would be good to confirm we have test coverage for that, and that this doesn't cause a regression.
If you can identify an existing test and/or confirm this still works - fine to self RTBC
Comment #20
smustgrave commentedSearching for a test that has a namespace for \Entity\Bundle\ I don't find anything so guess we don't have test coverage for that scenario.
Comment #22
scott_euser commentedAdded a line to BundleClassTest::testEntitySubclass() to verify that ->bundle() still returns the given bundle class. Not 100% sure though if that is what was meant in #19.
Comment #23
smustgrave commentedLeft some comments on MR.
Comment #25
ankithashettyMR updated as per the feedbacks shared on MR. Also rebased it.
Thanks!
Comment #26
smustgrave commentedStill failing.
Comment #27
scott_euser commentedProbably technically a problem with the coding standard itself as having a full stop to end the sentence after a version number probably should be allowed. Anyways I updated this to follow the sentence structure of similar deprecations.
Comment #28
smustgrave commentedTests are green,
I see there is already a change record
Issue summary appears complete.
Believe this is good to go.
Comment #29
dieterholvoet commentedIs there anything left that needs to be done here?
Comment #30
dwwBefore this is RTBC, I believe this needs a closer look by the entity system maintainers, and probably more of the folks involved in the original bundle subclass efforts. I'm not officially an entity subsys maintainer, so I can't sign-off on that side. I would need more time to carefully review this to be able to "sign-off" (such as it is) as one of the bundle subclass "sub-subsystem maintainers". 😅
Comment #31
berdirThe issue summary needs to be updated, it doesn't explain what the patch is trying to do.
> Leave out is_subclass_of($class_name, $entity_type->getOriginalClass())? I'm not sure why that condition exists.
That condition exists because if you change the bundle class of Article, then Node::load() must still work, so we also always need to check and support the original class, and we must not break existing code that might not even know that there is a bundle class.
I would expect that tests fail in this scenario and that we have tests for this.
Comment #32
dieterholvoet commentedOkay, that changes things. Drupal assumes that
EntityType->originalClasscontains the entity type class, but in ECK's case it contains a common entity class that can be used for different entity types. Who's in the wrong here? I would think ECK, because it definesDrupal\eck\Entity\EckEntityas the entity type class, which it isn't. But on the other hand, there's no other way for ECK to define a common entity class for all its entity types. Users would be required to manually create classes for every entity type, but that would kinda defeat the purpose of the module. Or we could consider automatic code generation, but that's a can of worms I'd rather keep closed.Comment #33
smustgrave commentedThanks @berdir for taking a look!
Comment #34
berdirOk, I need to clarify/extend #31 a bit, after a closer look at the changed code and the context. I don't think the problem is the original class, the MR anyway doesn't just remove that, so that question/suggestion in the issue summary is misleading and definitely needs an update.
Unless it's altered, orignalClass is identical to the current class and I'm pretty sure that is the case here, entityClass is also going to be the same for all of them. The part that I mentioned in #31 should still work, a practical use case for that is the file_entity module in contrib, and that's not changed.
I understand the proposed change a bit better now and it makes sense. I was wondering if we could drop the duplicate check entirely but it's technically still possible to register the same bundle class for two different entity types, especially with ECK, and then you can't do BundleClass::load().
Apart from the issue summary update, I would propose that we change the logic to be a positive check and not negative with continue, this seems to be optimized for fewest line changes in the MR, but the resulting code is harder to read IMHO and that's what people will look at in the future. Instead I would do this:
that means an indentation change for the same class check, but we also have an extra loop now, the check itself is closer to what we have now in HEAD.
Comment #35
scott_euser commentedAttempt at updating the issue summary. Removed subsystem maintainer review needed, as @Berdir has reviewed (or should it stay there, apologies if wrong to remove).
Comment #36
scott_euser commentedOkay updated the code as per #34 and best I could do with issue summary from my understanding of it. Tests are passing again (issue fork was missing 11.x branch), so I believe this is now ready for review again.
Edit: just thought it would be helpful to note I did also retest this using ECK creating multiple bundle classes and using eg BundleClassNameHere::create($properties) - which subsequently calls the problem code in question. 11.x branch it fails and on this branch I can successfully run the code.
Comment #37
smustgrave commentedChanges look good.
Left 2 small nitpicky suggestions.
Will leave in review for additional eyes.
Comment #38
scott_euser commentedThanks for checking! Fixed the nits and some coding standards issues, tests green again. Ready for review.
Comment #39
smustgrave commentedBeen about a week and don't want the issue to stale so will mark.
Comment #40
larowlanNice to see this close, couple of questions on the MR - thanks!
Comment #41
larowlanIssue credits
Comment #42
scott_euser commentedFixed the phpcs:ignore issue.
Hmmm maybe @Berdir or someone could point me in the right direction and I could work on implementation within the tests? For now all I can think of is creating a test module that effectively reproduces ECK as minimally as possible. Not sure if that is what you are after though @larowlan?
Comment #43
scott_euser commentedOkay, new test added that creates two bundle classes of the same entity class dynamically. Tests only triggers the ambiguous bundle class exception on it, with the change, problem solved. This is as minimal as I could manage to mimic what ECK is doing there.
Perhaps someone else might have a cleaner idea, but I think it resolves @larowlan's concern about the amount of mocking.
Some notes/further thoughts:
Comment #44
smustgrave commentedBelieve all feedback from @larowlan has been addressed.
Applied 2 very nitpicky return types.
Comment #45
alexpottPHPUnit tests look like they are failing.
Comment #46
smustgrave commentedRebased the MR.
Comment #47
alexpottI've left some comments on the MR - we just need to tidy up a few things I think.
Comment #48
smustgrave commented@scott_euser don't want to step on any toes. let me know if you want me to try and take a shot at it.
Comment #49
scott_euser commented@smustgrave all good if you want to give it a shot, I likely won't be able to for a couple of weeks. Thanks!
Comment #50
smustgrave commentedToday is a contribution day of mine so had time to take a look, let me know what you all think.
Comment #51
scott_euser commentedLooks good to me, thanks for taking care of it! Back to RTBC
Comment #53
alexpottIn #34 @Berdir asked for the subclass check to be kept around... but I think we already have that covered in \Drupal\Core\Entity\ContentEntityStorageBase::getEntityClass() and the \Drupal\Core\Entity\Exception\BundleClassInheritanceException it can throw.
Committed and pushed 644d8f2abd to 11.x and f342c259e9 to 10.3.x. Thanks!