Problem/Motivation
In Drupal 7 it was very common to temporarily put data on an entity by using an arbitrary property to hold the data. This was abusing the fact that entities were simple objects:
$entity->myproperty = 'my data';
This was intended to keep track of some temporary state until the end of the page request.
Unfortunately this practice is completely unreliable; whenever static caches are cleared, or an unchanged entity is loaded from the database, a new entity will be instantiated and the temporary data is lost.
I see this practice continuing in the wild in D8 code, even though we can now solve keeping track of temporary state by writing a custom service to hold the data. Writing custom services might be overkill though in many cases, and with the lack of an officially supported alternative we will see the continued abuse of properties on the entity.
Proposed resolution
Provide new methods:
EntityInterface::setTemporaryData($key, $value, $persist_on_reload = TRUE)
EntityInterface::getTemporaryData($key)
EntityInterface::clearTemporaryData($key)
Alongside this we could maybe deprecate the setting of arbitrary properties on entities, so that in D9 or beyond we can disable this completely. This could be as simple as a few lines of documentation on the EntityInterface, or even a deprecated warning being emitted whenever an unsupported property is accessed.
Remaining tasks
Discuss potential pitfalls.
User interface changes
None.
API changes
New methods added.
Data model changes
None.
Comments
Comment #2
pfrenssenComment #3
pfrenssenComment #4
mpdonadioI think we could do a property_exists() in the magic __set() method and then throw deprecated warning. Checking in the __get() may not catch it b/c.
Comment #5
pfrenssenI think we can come up with a list. We have the fields that can be directly accessed, as well as some internal properties such as
$entity->original
.The
__get()
is maybe not so important, if we have a deprecation warning on__set()
it would be more than enough to educate people about it. Where there's a get there's a set :)Anyway, the deprecation thing is a "nice to have", I can imagine people objecting to it because it is so heavily used in legacy code. This might be Drupal 10 territory.
Comment #6
Berdir->original has its own issue to be deprecated in favor of setters/getters.
I think we should add the API, then open a follow-up to add a deprecation warning just to see what happens and then as part of that open specific issues to convert usages in core to that.
I'm also open to discussing to deprecate $content_entity->$field_name in favor of supporting only get()/set(). I'm not sure yet about field properties.
See #1977266: Fix ContentEntityBase::__get() to not return by reference for a special case of __get() that we can likely not do anymore on its own but we might want to focus on the cases that require it to be by reference here first as they are probably the weird ones.
Comment #8
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedWhat about a seperate repository service or factory instead of adding additional responsibilities to
EntityInterface
. Could look like something like the following:Comment #10
Fabianx CreditAttribution: Fabianx at Tag1 Consulting commentedI like:
a lot.
Especially as we could just potentially wrap it in a decorator when we need to access the additional properties:
However the disadvantage of keeping it separate from the entity itself is while it ensures that lazy builders could use "pack()" the entity back to array('entity_type', 'entity_id'), the problem is that when code actually relies on that there is no way anymore to see if the entity was modified.
e.g. what I used in a proof-of-concept while developing BigPipe was to serialize() the entity and serialize() a freshly loaded entity and compared those to see if it was modified in some way.
We could also allow to add a list of EntityModifiers - instead.
While that is still arbitrary, it is at least consistent.
e.g.
What about:
A get on an unused property would then ask if any modifier has the property and return it if it is there.
Though that does still not solve the problem of when an entity is reloaded, though e.g. the _initialPublished should not even persist for longer than the entity's lifetime.
---
So we have two things to take into account here:
Still brainstorming here ...
I think the problem we have is that the entity has a fixed interface and we want / need to enhance that interface during the runtime. [e.g. for the a theoretical EntityWorkspaceStatusInterface, which has isInitialPublished() / setInitialPublished() methods].
Also some things must persist in entity cache, while others are just temporary to the object.
Which is kinda what I though of with my modifiers:
with the big problem that an instanceof won't work unless it is a true dynamic child entity, but if that code was changed to check for:
Brainstorming end ;).
Comment #20
andypostComment #21
andypostthere's existing API for that purpose
- user_data service, associates and removes additions to user entity
- third-party-settings for config entities
and less known
- key value entity storage - #2208617: Add key value entity storage
Comment #22
andypost