Problem/Motivation
Prior to #2570593: Allow entities to be subclassed using "bundle classes" the entity-class was determined from the current entity-type definition.
Since that patch, it is now determined by the version in the last installed repository (The active version).
This breaks the file_entity_module.
Steps to reproduce
- Use
EntityTypeInterface::setClass()from ahook_entity_type_alter(). - Call
\Drupal::entityTypeManager()->getStorage($the_entity_type)->getEntityClass()and note it is the wrong class.
Proposed resolution
- In the constructor of
EntityStorageBase, set the private$baseEntityClassvariable based on the value set on the$entity_typepassed in as an argument. - Remove the comment on this property about 'remove in Drupal 10'.
When we remove the BC layer in #3244802: Remove BC layers in entity system, remove the __get and __set but leave the $baseEntityClass property behind.
Remaining tasks
Agree on the approachImplement proposed resolution.- Reviews / refinements.
- RTBC.
- Commit.
User interface changes
API changes
Fixes a regression introduced in #2570593: Allow entities to be subclassed using "bundle classes" where hook_entity_type_alter() could not be used to change the entity class for the given entity type.
Data model changes
Release notes snippet
TBD. Maybe not needed, if this lands before alpha1 is tagged. ;)
| Comment | File | Size | Author |
|---|---|---|---|
| #22 | 3246150-22.patch | 8.89 KB | dww |
| #14 | 3246150-13.fail_.patch | 3.31 KB | dww |
Comments
Comment #2
alexpottIMO we should not be storing the entity class in installed definitions and the class should always be determined by code and not state. The state should be about the schema and we should only be comparing schema when determining entity definition updates. An entity definition mismatch due to changing the cache class for example is really icky.
Comment #4
larowlanCrediting craigra who found this
Comment #5
andypostDoes it mean that static methods like load() and loadMultiple() could be broken?
Comment #6
berdirno, it means that existing custom entity classes will be broken if you've been altering them in hook_entity_type_alter().
Comment #7
dwwThanks for finding this now! Yay for alpha releases. 😉 We tried to think of such things in the parent, but this was a hidden gotcha.
@alexpott also posted #2 to a Slack thread about this bug, to which there was some further discussion, not captured here. Pasting now so folks just following the issue can see:
- - -
larowlan
so that would require merging the two definitions somehow?
alexpott
Yeah I dunno - I’ve been ostriching this issue since I realised what we did in 8.8
berdir
that's not an easy change to make
berdir
but yes, if it is dynamic, you shouldn't set it on entity type, installed or not, but override getEntityClass()
berdir
for the bc layer, I would suggest that we set the BC property that we already support
berdir
that bc layer can go away together with that property
berdir
for the recommended change, for now, you have to install that change, untangling that definition into an installable and declared part is a big change that we can't block this BC layer on
- - -
Not exactly sure how to proceed right now. I certainly don't want to ignore @alexpott's wishes / perspective, but it sounds like that proposal will be a much bigger, harder change. Perhaps @larowlan's original proposal is more pragmatic for the short term to fix 9.3.0 before beta1, and we open a follow-up to do the larger surgery to stop using state for this entirely and aim that for 9.4.x? Or I guess since it'd be a disruptive, deprecation-inducing change, we'd have to do it for 10.0.0 and remove it entirely in D11?
Comment #8
catchIf this works, it sounds reasonable, and we should do it ASAP.
Otherwise I think we need to roll this back in 9.3.x and address it in the original issue.
Comment #9
larowlanWe've hatched a plan here, I'll work on it today
Comment #10
larowlanComment #11
dwwFixes the whitespace bugs and a few docs nits.
There's still no new deprecation layer here, or deprecation tests, etc. I think this is just POC that the solution works. But not sure about NW for that, let's still get some more eyes on this.
Comment #12
larowlanAs discussed in slack, I think we can make a permanent fix here and not worry about a BC layer.
I think there's broad consensus that the entity-type class shouldn't be different between the active/installed versions - that should only apply to schema related changes.
As such, updating the issue summary with the proposed resolution.
Comment #13
larowlanComment #14
dwwWhoops. 😉 Lucky 13?
sh ./core/scripts/dev/commit-code-check.sh --branch 9.3.xis finally passing locally... Sorry for the noise. Also forgot interdiffs.If we're going to keep this approach, we'll want to update some @todo comments since the plan in #3244802: Remove BC layers in entity system was to remove baseEntityClass entirely. But if we want to keep it, that can just remove the __get() and __set() stuff and the tests, but leave baseEntityClass alone now that we're using it.
Comment #17
dwwWhoops, those fails in the pass patch makes sense. I was too specific updating those in #2570593: Allow entities to be subclassed using "bundle classes". I'll fix.
Comment #18
dwwThe fail test already failed as expected, since we hit that before the failures in the pass test. Not uploading another fail test. But this should now actually pass. 🤞
Comment #19
berdirI think that makes sense. The bug we have is specific to content entities, because only that storage overrides the entity type property with the installed version.
Only wondering if we do want to change the name of this property now that we're likely going to keep it, but not sure what else to call it. it's private, so we could always go back to just entityClass in D10 when we remove the __set()/__get() bc layer.
what about testing to actually create an entity and asserting the class of that, instead of just this method? not sure if it's really worth it.
Comment #20
alexpottI think we should change the active/installed definition to always get this from code and not store the value in state at all. This would allow us to discuss removing other things from the installed entity type definitions to make our lives easier. For example the
persistent_cacheand therender_cacheproperties.The current solution means we need to update the plan to remove \Drupal\Core\Entity\EntityStorageBase::$baseEntityClass
Comment #21
berdirI agree that we should do that, but not in this issue. It's a big mess, but it's an existing mess, while this specific issue is a pretty major regression and I don't think we can refactor the entire entity type definition handling in time for 9.3.0. This is an easy fix that keeps the status quo that so far was kinda implicit and just happened to continue work as $this->entityClass was set before we switched out the entity type definition.
Comment #22
dwwThanks for the reviews, folks.
Re: #19:
$baseEntityClassis still a pretty reasonable name for it, given how we're now using it. I think going back to just "entityClass" in D10 would perhaps be even more confusing. I think we can just leave it.Re: #20:
I also realized while fixing the comments for
$baseEntityClassthat we can already simplifyEntityStorageBase::getEntityClass(), but not how we originally imagined. So I removed that @todo/@see and fixed it now ($baseEntityClasswill always be set and we'll never call$this->entityType->getClass()).While I was at it, I added the @todo/@see #3244802: Remove BC layers in entity system comments to the tests for the BC layer (the
__get()/__set()ofEntityStorageBase::$entityClassso those aren't forgotten. Hope that's cool.Comment #23
larowlanThis looks ready to go.
I agree with #20 that we need to fix this, but also with #21 and #22 that 9.3.0 isn't the place
I'll open a follow-up
Comment #24
larowlanHere's a followup with a proposed approach #3246488: Use installed entity-type definitions for schema related information only
Comment #26
catchAgreed with going for the quick fix here - it'd either be that or rolling back bundle classes. I feel like we should already have an issue for taking various entity type definition things out of state, but I couldn't find it looking for a few minutes, so the follow-up is good until/if we do find the other one.
Committed cefb93c and pushed to 9.3.x. Thanks!
Comment #28
fgmThis change introduces appears to be causing a compatibility issue described at #3259941: getEntityClass() must be of the type string, null returned where the
EntityStorageBase::baseEntityClassis not set, and returns a badly typed result:That function returns a
NULLwhen thebaseEntityClassis not set and$this->entityType->getClass()returnsNULLtoo (the signature of that function allows an empty return), but the function signature does not allow forNULLresults.Rewriting it like this fixes the issue, but it may well be a symptom of a deeper problem : it does not seems correct that
entityType->getClass()should returnNULL.Comment #29
berdir> Rewriting it like this fixes the issue, but it may well be a symptom of a deeper problem : it does not seems correct that entityType->getClass() should return NULL.
On multiple levels. This is already a fallback.
Sounds like you have a custom storage class that does _not_ call the parent::__contruct() method() That's where that is set.
And yes, this is a fallback when the installed entity type definition is missing the class, in that case you should write an update function and apply that change. load the installed definition, set the class, and update it.
Comment #30
fgmJust double-checked in the specific case of #3259941: getEntityClass() must be of the type string, null returned:
Just to be sure:
NULLAs mentioned by the original poster on the other issue, this is a problem specific to batch situations : the same node loads work normally in other contexts. Maybe we should followup on the other issue ?
Comment #31
quietone commentedA follow-up was asked for in #22 and it was created in #24. Therefor I am removing the tag.