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

pfrenssen created an issue. See original summary.

pfrenssen’s picture

Issue summary: View changes
pfrenssen’s picture

Issue summary: View changes
mpdonadio’s picture

I 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.

pfrenssen’s picture

I 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.

Berdir’s picture

->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.

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

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

Sam152’s picture

What about a seperate repository service or factory instead of adding additional responsibilities to EntityInterface. Could look like something like the following:

/** @var EntityTemporaryDataRepositoryInterface $temp_data */
$temp_data = \Drupal::service('entity.temporary_data_repository');
$temp_data->get($entity, 'foo');
$temp_data->set($entity, 'foo', 'bar');

/** @var EntityTemporaryDataRepositoryInterface $temp_data */
$temp_data = \Drupal::service('entity.temporary_data_factory')->getRepsitory($entity);
$temp_data->get('foo');
$temp_data->set('foo', 'bar');

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.

Fabianx’s picture

I like:

/** @var EntityTemporaryDataRepositoryInterface $temp_data */
$temp_data = \Drupal::service('entity.temporary_data_factory')->getRepository($entity);
$temp_data->get('foo');
$temp_data->set('foo', 'bar');

a lot.

Especially as we could just potentially wrap it in a decorator when we need to access the additional properties:

$entity = new TemporaryMutableEntity($entity, \Drupal::service('entity.temporary_data_factory'));

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:

$modifier = new EntityModifier();
$modifier->set('_initialPublished', TRUE);
$entity->addModifier($modifier);

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:

  • a) Ensure that properties survive a reload of the entity (for certain cases, others are relying on the exact object instance).
  • b) Ensure that a diff of original entity vs. modified can still be retrieved somehow.

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:

$entity = new DynamicInterfaceEntity($entity);
$entity->addDynamicInterface('EntityPublishedInterface', $entity_published_interface_provider);

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:

$entity->hasDynamicInterface('EntityPublishedInterface') it would work for both.

Brainstorming end ;).

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.

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.

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.

andypost’s picture

Issue tags: +PHP 8.2
andypost’s picture

there'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

andypost’s picture

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.