Updated: Comment #49

Problem/Motivation

Too much business logic hiding in attachLoad(), we want to make storage controllers as focused on *storing* as possible and make it easy to provide different storage controllers that don't have to implement a trillion different entity-type specific things.

Proposed resolution

- Move as much as possible of that logic to a new postLoad() method on the entity class, similar to other post/pre methods.
- Remove $revision_id from attachLoad()/postLoad()
- Kill a few storage controllers that we don't need anymore.

Remaining tasks

Hope that it passes and RTBC it.

User interface changes

Niet.

API changes

attachLoad() renamed to postLoad(), new method on entity that entities should use instead.

#2130499: Move entity hook invocation from Storage to Entity classes - As discussed in Prague but decided to put of to a follow-up issue as it would make the patch more complex.

Original report by @chx

We already had two rounds of this (save/delete/etc and baseFieldDefinitions); now I am doing some of the load code and a little residual cleanup in Node.

CommentFileSizeAuthor
#84 storage-logic-2095283-84.patch56.69 KBBerdir
#79 storage-logic-2095283-79.patch56.73 KBBerdir
#74 storage-logic-2095283-74.patch60.05 KBamateescu
#70 storage-logic-2095283-70-interdiff.txt1.7 KBBerdir
#70 storage-logic-2095283-70.patch60 KBBerdir
#68 storage-logic-2095283-68-interdiff.txt3.93 KBBerdir
#68 storage-logic-2095283-68.patch60.01 KBBerdir
#66 storage-logic-2095283-66-interdiff.txt4.48 KBBerdir
#66 storage-logic-2095283-66.patch64.43 KBBerdir
#64 interdiff-62-64.txt2.24 KBvladan.me
#64 storage-logic-2095283-64.patch60.11 KBvladan.me
#62 storage-logic-2095283-62-interdiff.txt6.53 KBBerdir
#62 storage-logic-2095283-62.patch59.88 KBBerdir
#60 storage-logic-2095283-60-interdiff.txt3.18 KBBerdir
#60 storage-logic-2095283-60.patch58.81 KBBerdir
#55 storage-logic-2095283-55.patch56.41 KBBerdir
#53 storage-logic-2095283-53-interdiff.txt637 bytesBerdir
#53 storage-logic-2095283-53.patch56.41 KBBerdir
#51 storage-logic-2095283-51-interdiff.txt699 bytesBerdir
#51 storage-logic-2095283-51.patch56.43 KBBerdir
#49 storage-logic-2095283-49-interdiff.txt15.29 KBBerdir
#49 storage-logic-2095283-49.patch56.39 KBBerdir
#46 storage-logic-2095283-46-interdiff.txt7.71 KBBerdir
#46 storage-logic-2095283-46.patch52.87 KBBerdir
#44 storage-logic-2095283-44-interdiff.txt4.3 KBBerdir
#44 storage-logic-2095283-44.patch47.03 KBBerdir
#42 storage-logic-2095283-42-interdiff.txt4.57 KBBerdir
#42 storage-logic-2095283-42.patch42.73 KBBerdir
#39 storage-logic-2095283-39.patch43.31 KBBerdir
#39 storage-logic-2095283-39-interdiff.txt1.68 KBBerdir
#37 storage-logic-2095283-37.patch43.3 KBBerdir
#27 2095283_27.patch43.59 KBchx
#25 2095283_25.patch43.6 KBchx
#23 2095283_23.patch43.59 KBchx
#21 2095283_20.patch43.55 KBchx
#19 2095283_19.patch42.87 KBchx
#17 2095283_17.patch42.81 KBchx
#17 interdiff.txt20.7 KBchx
#15 2095283_13.patch29.81 KBchx
#12 2095283_12.patch29.79 KBchx
#10 2095283_10.patch22.09 KBchx
#9 2095283_9.patch22.18 KBchx
#7 2095283_7.patch18.22 KBchx
#5 2095283_5.patch18.21 KBchx
#3 2095283_3.patch15.85 KBchx
noesclogic.patch15.81 KBchx
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

chx’s picture

Assigned: Unassigned » chx
Status: Active » Needs review

(note for webchick: see, I didn't file this as critical. Do I get a brownie point?)

Status: Needs review » Needs work

The last submitted patch, noesclogic.patch, failed testing.

chx’s picture

Status: Needs work » Needs review
FileSize
15.85 KB

Status: Needs review » Needs work

The last submitted patch, 2095283_3.patch, failed testing.

chx’s picture

Status: Needs work » Needs review
FileSize
18.21 KB

Status: Needs review » Needs work

The last submitted patch, 2095283_5.patch, failed testing.

chx’s picture

Status: Needs work » Needs review
FileSize
18.22 KB
Berdir’s picture

Status: Needs review » Needs work
  1. +++ b/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php
    @@ -292,18 +292,8 @@ protected function buildQuery($ids, $revision_id = FALSE) {
    +    $class = $this->entityInfo['class'];
    +    $class::attachLoad($this, $queried_entities, $revision_id);
    
    +++ b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php
    @@ -354,19 +354,8 @@ protected function attachLoad(&$queried_entities, $load_revision = FALSE) {
    +    $class = isset($this->entityInfo['class']) ? $this->entityInfo['class']: $this->entityClass;
    +    $class::attachLoad($this, $queried_entities, $load_revision);
    

    We could probably move this into a parent class, not sure if we want to do it here, but I'm already adding a different implementation in a different issue, so this would help avoid code duplication. The weird class thing will go away there too.

  2. +++ b/core/modules/node/lib/Drupal/node/Entity/Node.php
    @@ -79,6 +79,27 @@ public function getRevisionId() {
    +
    +  /**
    +   * {@inheritdoc}
    +   */
    +  public static function preCreate(EntityStorageControllerInterface $storage_controller, array &$values) {
    +    // @todo Handle this through property defaults.
    +    if (empty($values['created'])) {
    +      $values['created'] = REQUEST_TIME;
    +    }
    +  }
    

    The @todo should actually be possible now. Can you try to add it to baseFieldDefinitions() ?

  3. +++ b/core/modules/node/lib/Drupal/node/NodeGrantDatabaseStorage.php
    @@ -256,4 +256,14 @@ public function count() {
    +  public function deleteNodeRecords(array $nids) {
    +    db_delete('node_access')
    +      ->condition('nid', $nids, 'IN')
    

    I think we can use $this->database here.

  4. The methods that you add should also allow us to kill EntityTestStorageController::create() as that needed $this->entityType.
chx’s picture

FileSize
22.18 KB

> We could probably move this into a parent class,

Moved.

> The @todo should actually be possible now. Can you try to add it to baseFieldDefinitions() ?

How so? EntityManager::getFieldDefinitions caches the definitions. And I do not know how to add a default.

> I think we can use $this->database here.

I think so too :)

> The methods that you add should also allow us to kill EntityTestStorageController::create() as that needed $this->entityType.

Killed. Also, if I would know how to add a default, I perhaps could kill the new EntityTest::preCreate() as well because $entity_type is handily passed in. 'value' => $entity_type?

chx’s picture

Status: Needs work » Needs review
FileSize
22.09 KB

larowlan and andypost pointed out how to add a default value. So trying that for entity test.

Status: Needs review » Needs work

The last submitted patch, 2095283_10.patch, failed testing.

chx’s picture

Status: Needs work » Needs review
FileSize
29.79 KB

I hit a phpstorm bug -- the recursive search-replace didn't actually work.

Status: Needs review » Needs work

The last submitted patch, 2095283_12.patch, failed testing.

Berdir’s picture

+++ b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTest.php
@@ -136,6 +137,9 @@ public static function baseFieldDefinitions($entity_type) {
+      'settings' => array(
+        'default_value' => $entity_type,
+      ),

As discussed, this doesn't work, as we need to bundle to get the fields.

chx’s picture

Status: Needs work » Needs review
FileSize
29.81 KB

Discussed with berdir and while this is the way to add a default it wont work because type is the bundle key.

Berdir’s picture

Status: Needs review » Needs work
+++ b/core/lib/Drupal/Core/Entity/EntityStorageControllerBase.php
@@ -272,4 +277,26 @@ protected function invokeTranslationHooks(EntityInterface $entity) {
+   * Attaches data to entities upon loading.
+   *
+   * This will attach fields, if the entity is fieldable. It calls
+   * hook_entity_load() for modules which need to add data to all entities.
+   * It also calls hook_TYPE_load() on the loaded entities. For example
+   * hook_node_load() or hook_user_load(). If your hook_TYPE_load()
+   * expects special parameters apart from the queried entities, you can set
+   * $this->hookLoadArguments prior to calling the method.
+   * See Drupal\node\NodeStorageController::attachLoad() for an example.
+   *

This needs to be updated. The base class doesn't attach fields anymore and the things below aren't correct anymore.

chx’s picture

Status: Needs work » Needs review
FileSize
20.7 KB
42.81 KB

This one moves some logic into ShortcutSet, renames attachLoad to postLoad because that just makes sense, look at the other methods in EntityInterface. Also, we added postLoad already for this purposes just was unused until now.

Status: Needs review » Needs work

The last submitted patch, 2095283_17.patch, failed testing.

chx’s picture

Status: Needs work » Needs review
FileSize
42.87 KB

Status: Needs review » Needs work

The last submitted patch, 2095283_19.patch, failed testing.

chx’s picture

Status: Needs work » Needs review
FileSize
43.55 KB

Status: Needs review » Needs work

The last submitted patch, 2095283_20.patch, failed testing.

chx’s picture

Status: Needs work » Needs review
FileSize
43.59 KB

Status: Needs review » Needs work

The last submitted patch, 2095283_23.patch, failed testing.

chx’s picture

Status: Needs work » Needs review
FileSize
43.6 KB

Status: Needs review » Needs work

The last submitted patch, 2095283_25.patch, failed testing.

chx’s picture

Status: Needs work » Needs review
FileSize
43.59 KB
Berdir’s picture

This seems to be the only case where we move a hook invocation to the entity classes. Why?

chx’s picture

We can move invokeHook to entity in the next patch. This is already a hodgepodge a bit. Also, invokeHook just recently slimmed down enough to be moveable.

fago’s picture

+++ b/core/lib/Drupal/Core/Entity/Entity.php
@@ -642,9 +642,36 @@ public static function postDelete(EntityStorageControllerInterface $storage_cont
-  public static function postLoad(EntityStorageControllerInterface $storage_controller, array $entities) {
+  public static function postLoad(EntityStorageControllerInterface $storage_controller, array $entities, $revision_id = FALSE) {

I don't think it makes sense to invoke the entity load from here. The regular pattern is the storage controller invoking the hook + the entity class method later on. Are there any reasons to not follow the same pattern here?

E.g. that's what we do for preSave():

      $entity->preSave($this);
      $this->invokeFieldMethod('preSave', $entity);
      $this->invokeHook('presave', $entity);
chx’s picture

> The regular pattern is the storage controller invoking the hook + the entity class method later on. Are there any reasons to not follow the same pattern here?

That's not a regular pattern, that's a stupid thing we did because invokeHook was dealing with fields. No matter the storage controller, you need to invoke hooks so why it's on the pluggable storage controller? Just count how many times the invoke code was duplicated (three times!) before.

Edit: 36 files changed, 221 insertions(+), 272 deletions(-)

fago’s picture

Avoiding code duplication is not a reason for moving it to the entity class, we can add a method invokeLoadHook() on the storage controller also.

That's not a regular pattern, that's a stupid thing we did because invokeHook was dealing with fields.

It's the pattern. We can argue on whether it's stupid or not, sure. But even if it's stupid, it should be fixed for all places in a consistent manner unless there are good reasons for being inconsistent. However, I do not see any?

chx’s picture

Status: Needs review » Closed (won't fix)
fago’s picture

Title: Remove more logic from the storage controllers » Remove storage logic from the storage controllers
Status: Closed (won't fix) » Active

Imo, the goal of this issue was to move entity type postLoad() specifications to the entity classes, as we already do it for e.g. save. Whether we want to move general, not entity-type-specific storage logic to the entity class is another issue and needs to be discussed first. If you are insisting to not separate that discussion to its own issue, then okok - let's have the discussion here.

So let's clarify what we are talking about. What lives in the storage controllers what is not storage engine specific? I find the following:
a) invokeHook(), invokeTranslationHooks(), maybe invokeHookLoad() or so also
b) invokeFieldMethod(), invokeFieldItemPrepareCache()
c) cacheGet(), cacheSet()

Anything else? Going with that for now.

So, when we remove that general logic from the storage we could move it to the entity class, but I dislike adding that much of general purpose code in people's entity classes. We should try to keep them as simple as possible. I know they aren't really simple but it would be way worse if we'd move all that stuff on entity class as well.

So, instead what about moving that general-purpose logic to suiting services we partly already do, e.g. for getting entity field definitions or instantiating fields. We could move a) to entity manager, b) to the entity field manager (= field type manager?) and c) could be moved to a EntityCacheController/Handler/Service which we have been discussing doing for entity cache. Then, we could go and call this services from the entity class and have the logic removed from storage controllers, while entity classes do not become heavier. Thoughts on that?

chx’s picture

Assigned: chx » Unassigned
Status: Active » Needs review
chx’s picture

*exasparated* There are constraints we need to work with but the size of the entity class is not one. On the other hand, the size of the storage controller class is one because it's pluggable and every. single. plugged. version needs to copypaste the code. Storage controlllers should contain code that wont need to be copypasted. Very simply. And yes, there's more to move. Moving the rest we can do in a followup. This is already byzantine and took some work to pass.

Berdir’s picture

Re-roll, let's see if this still works.

Status: Needs review » Needs work

The last submitted patch, storage-logic-2095283-37.patch, failed testing.

Berdir’s picture

Status: Needs work » Needs review
FileSize
1.68 KB
43.31 KB

Ups, this should fix hopefully the fails and the exceptions.

Berdir’s picture

Ok, so now I better understand what's going on here.

These are the options that we have to solve that load hook disagreement that we had in Prague:

1. Do what the patch does, move the hook invocation to the entity class. We can still do b) or c) additionally too.
2. Keep it in the storage controller, then we need to do one of a/b/c:

a) Make hookLoadArguments() on the entity class public so that we can call it from there.
b). Generalize the additional load hook arguments and pass along the bundles for all, including the generic hook_entity_load()
c). Drop the additional load hook arguments feature completely, unless there's a more useful use case than passing through the bundles. You need a loop and check every $entity *anyway*, as there could be multiple bundles and you only apply to one of those. The only thing you win is to be able to initially decide that no entities interest you, therefore saving a loop over them. The performance gain is tiny, and it's actually slower if you do apply to at least one entity, because you need to check twice. And you need more code, so it's hardly a DX improvement.

Thoughts? I actually kind of like c), although that would be an API change and I'd prefer 2 just because there's less conceptual change in this patch and we can open a follow-up to discuss and eventually move all hook invocations to the entity classes.

fago’s picture

  1. +++ b/core/lib/Drupal/Core/Entity/Entity.php
    @@ -340,9 +340,36 @@ public static function postDelete(EntityStorageControllerInterface $storage_cont
    +    // Call hook_TYPE_load(). The first argument for hook_TYPE_load() are
    

    This comment seems to stem from node-ages, just as the whole hook look arguments code. Do we still need this?
    After reading options of #40: I guess I like c)!

  2. +++ b/core/lib/Drupal/Core/Entity/EntityInterface.php
    @@ -207,14 +207,17 @@ public static function preDelete(EntityStorageControllerInterface $storage_contr
    -   * Acts on loaded entities before the load hook is invoked.
    +   * Calls the entity load hooks.
    

    Now it's inconsistent with other post-* crud hooks, as said - whatever we do it should stay consisent.

1. Do what the patch does, move the hook invocation to the entity class. We can still do b) or c) additionally too.
2. Keep it in the storage controller, then we need to do one of a/b/c:

As discussed in Prague, having the entity taking care of invoking it's own hooks actually makes sense and I have to agree with chx that the increased size of entity class isn't that problematic, plus it doesn't look like being a lot of code to add anyway. -> So let's do 1)!

However, we really should do that for all hooks - so things stay consistent. So let's move invokeHook to the entity class and use it from there?

+++ b/core/lib/Drupal/Core/Entity/Entity.php
@@ -340,9 +340,36 @@ public static function postDelete(EntityStorageControllerInterface $storage_cont
+    foreach (\Drupal::moduleHandler()->getImplementations($entity_type . '_load') as $module) {

Is there a way to avoid accessing the global module handler? Maybe, get it from the passed storage controller, or better have the entity manager providing a helper for invoking entity hooks + get that via the storage controller?

Berdir’s picture

- Re-rolled
- Hook load arguments dropped. Updated hook_node_load() documentation. The example might now look slightly more complicated, but the old one was kind of weird in that it checked but then still used all nids. Usually, you'd need that loop & check anyway IMHO.
- Tried to move invokeHook() but it's not that easy unless we make it a public hook. There are a few hooks that have no corresponding method on entities, e.g. delete revision (this should have one I think as we have save revision too) but not sure what to do about the translation hooks. Seems like they should also move to postSave(), but then we need to do the dataTable check there. Also not sure about the field invoke method helper method, should that move to Content entity base?

Where I'm getting at is that moving everything isn't trivial, so I'd rather not do it here. Either move on with this or keep load in the storage controller too for now. And open a follow-up issue to move all/the rest of it.

Status: Needs review » Needs work

The last submitted patch, 42: storage-logic-2095283-42.patch, failed testing.

Berdir’s picture

Status: Needs work » Needs review
FileSize
47.03 KB
4.3 KB

Attempt to fix those tests. The actual implementations show nicely that no real implementation even bothered to look at $types, some don't even define it (e.g. forum_node_load()) except of that node load test.

Status: Needs review » Needs work

The last submitted patch, 44: storage-logic-2095283-44.patch, failed testing.

Berdir’s picture

Status: Needs work » Needs review
FileSize
52.87 KB
7.71 KB

Re-roll. Removed the load hook tests for node and custom block completely. We have generic entity crud hook tests for various entity types and it's all unified now, no need for custom tests for that stuff now that $types is gone.

fago’s picture

Status: Needs review » Needs work

Either move on with this or keep load in the storage controller too for now. And open a follow-up issue to move all/the rest of it.

Yeah, let's go through that in a follow-up.

+  /**
+   * Attaches data to entities upon loading.
+   *
+   * @param $queried_entities
+   *   Associative array of query results, keyed on the entity ID.
+   * @param $revision_id_id
+   *   ID of the revision that was loaded, or FALSE if the most current revision
+   *   was loaded.

id_id

That's in there several times, best s/r to fix the s/r error.

+  /**
+   * {@inheritdoc}
+   */
+  public function deleteNodeRecords(array $nids) {
+    $this->database->delete('node_access')
+      ->condition('nid', $nids, 'IN')
+      ->execute();
+
+  }

Unneccessary empty line.

Else patch looks good.

amateescu’s picture

+++ b/core/lib/Drupal/Core/Entity/EntityInterface.php
@@ -207,14 +207,17 @@ public static function preDelete(EntityStorageControllerInterface $storage_contr
    * @param array $entities
    *   An array of entities.
+   * @param int|bool $revision_id
+   *   ID of the revision that was loaded, or FALSE if the most current revision
+   *   was loaded.
    */
-  public static function postLoad(EntityStorageControllerInterface $storage_controller, array $entities);
+  public static function postLoad(EntityStorageControllerInterface $storage_controller, array $entities, $revision_id = FALSE);

The revision_id of which of the loaded entities? :)

Berdir’s picture

Issue summary: View changes
Status: Needs work » Needs review
FileSize
56.39 KB
15.29 KB

This kills the weird $revision_id argument completely :)

It's used for exactly one thing, to figure out if we should load the fields specific revision or not. Which is something that we can now calculate automatically. We need to refactor the field stuff further to not require loaded entity objects and might need to re-introduce it there again, but that won't go through postLoad() anymore.

This has the additional benefit of removing the concept of a $revision_id/flag from EntityInterface and the storage controller base class, which don't know anything about revisions.

Also found some more postLoad() methods that I could kill. The only one that survived is comment, because that needs to happen *before* we map the data to entities. We'll get to that later...

Status: Needs review » Needs work

The last submitted patch, 49: storage-logic-2095283-49.patch, failed testing.

Berdir’s picture

Status: Needs work » Needs review
FileSize
56.43 KB
699 bytes

I will never learn to at least do a simple syntax check before I upload broken patches...

Status: Needs review » Needs work

The last submitted patch, 51: storage-logic-2095283-51.patch, failed testing.

Berdir’s picture

Status: Needs work » Needs review
FileSize
56.41 KB
637 bytes

Wow. that means we invoke the load hooks for config entities every time they're accessed, because we don't have a static cache. That's... crazy, need to do some profiling on how costly config loading is.

This should fix that, let's see if there are additional errors.

Status: Needs review » Needs work

The last submitted patch, 53: storage-logic-2095283-53.patch, failed testing.

Berdir’s picture

Status: Needs work » Needs review
FileSize
56.41 KB

Would be too embarrassing to post an interdiff.

Status: Needs review » Needs work

The last submitted patch, 55: storage-logic-2095283-55.patch, failed testing.

yched’s picture

This look like an awesome move overall.

I'm just a bit worried about the amount of stuff that's dumped into the postLoad() step, either in the controller or the entity class. It makes it difficult to figure out what happens in which order, you need to unwind the class hierarchy and the order of the parent:: calls.
Also, having field values loaded in a step called postLoad() seems a bit weird/counterintuitive ?

I didn't really follow the issue, so feel free to discard this...

Berdir’s picture

You are perfectly right, loading field values should not be in there nor converting things into entities, and it will have to move around so that we can load the field values before we create the entity objects. The way it works right now is a left-over of when we had the NG storage controller, that had to pick what it changed to not having to duplicate too much code.

I need to create an issue, but there's already a bullet point for this in the meta issue.

yched’s picture

Thanks @Berdir, makes sense.
Not fully clear to me what's for this issue and what's for followups / side issues then ?

Berdir’s picture

Status: Needs work » Needs review
FileSize
58.81 KB
3.18 KB

Ah, now I found what @chx was referencing to. There was one piece where the actual revision id was used. And it switched back and forth between a $load_revision and $revision_id, weird stuff. And it had a fall back in place anyway. So I just removed all that code and it looks like the entity tests are still passing.

It is still a bit weird, because we conditionally convert $entities from arrays to objects if there is *no* data table, and the other code only runs if there is a revision data table, so it's fairly sure that it doesn't run when $entities is an array of entity objects, but to be sure, I still added an is_object() check there.

@yched: Not sure, I think we shouldn't do more here other than fixing the tests, unless you see something. This basically just involves moving things from entity storage to entity classes, anything that's internal refactoring should imho be done in a follow-up issue, unless directly necessary for this issue.

Status: Needs review » Needs work

The last submitted patch, 60: storage-logic-2095283-60.patch, failed testing.

Berdir’s picture

Status: Needs work » Needs review
FileSize
59.88 KB
6.53 KB

So we need to pass $entities around by reference.

IMHO it's wrong that the store returns differently sorted entities, but that's a different topic.

Berdir’s picture

Status: Needs review » Needs work
Issue tags: +Needs reroll, +Novice

Some nitpicks that I noticed, will re-roll myself tonight or so unless someone else has time first.

  1. +++ b/core/lib/Drupal/Core/Entity/EntityInterface.php
    @@ -207,14 +207,14 @@ public static function preDelete(EntityStorageControllerInterface $storage_contr
     
       /**
    -   * Acts on loaded entities before the load hook is invoked.
    +   * Calls the entity load hooks.
        *
    

    Not sure about this change, should probably just have the before load hooks invoked part removed. The important part is what it can be used to do and when it's called, not what the default implementation does.

  2. +++ b/core/lib/Drupal/Core/Entity/EntityStorageControllerBase.php
    @@ -152,4 +157,15 @@ protected function invokeHook($hook, EntityInterface $entity) {
    +   *
    +   * @param $queried_entities
    +   *   Associative array of query results, keyed on the entity ID.
    

    Missing an array

  3. +++ b/core/lib/Drupal/Core/Entity/EntityStorageControllerInterface.php
    @@ -153,4 +153,18 @@ public function save(EntityInterface $entity);
    +   * Returns the current entity type.
    +   *
    +   * @return string
    +   */
    +  public function entityType();
    +
    +  /**
    +   * Returns the current entity info.
    +   *
    +   * @return string
    +   */
    +  public function entityInfo();
    

    Missing descriptions of the @return.

vladan.me’s picture

Status: Needs work » Needs review
Issue tags: -Needs reroll, -Novice
FileSize
60.11 KB
2.24 KB

Documentation updated, patch still applies

fago’s picture

Status: Needs review » Needs work

Looks like this becomes a great clean-up :-) Here a review:

  1. I'm not sure whether this has been discussed already, but I'm baffled by the inconsistency of postLoad() invoking entity hooks and others like postSave() don't. Why not invoke entity-load hooks in the storage controller also?
    Also, if it would stay in the entity class the interface docs should probably define that so implementers know.
  2. +++ b/core/lib/Drupal/Core/Entity/EntityStorageControllerInterface.php
    @@ -150,7 +150,24 @@ public function save(EntityInterface $entity);
    +   * Returns the current entity type.
    

    I'm confused by "current" here. What does this mean or refer to?

  3. +++ b/core/lib/Drupal/Core/Entity/EntityStorageControllerInterface.php
    @@ -150,7 +150,24 @@ public function save(EntityInterface $entity);
    +  /**
    +   * Returns the current entity info.
    +   *
    

    Same here.

  4. +++ b/core/modules/node/lib/Drupal/node/Entity/Node.php
    @@ -77,6 +77,16 @@ public function getRevisionId() {
    +  public static function preCreate(EntityStorageControllerInterface $storage_controller, array &$values) {
    +    // @todo Handle this through property defaults.
    

    This does not call parents while others do. Even if it's empty, it might not stay empty?

  5. +++ b/core/modules/node/lib/Drupal/node/Entity/Node.php
    @@ -149,6 +159,13 @@ public static function preDelete(EntityStorageControllerInterface $storage_contr
    +  public static function postDelete(EntityStorageControllerInterface $storage_controller, array $nodes) {
    ...
    +  }
    

    Same here.

  6. +++ b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTest.php
    @@ -95,6 +96,15 @@ protected function init() {
    +  public static function preCreate(EntityStorageControllerInterface $storage_controller, array &$values) {
    +    if (empty($values['type'])) {
    ...
    +    }
    +  }
    

    again, call the parent?

Berdir’s picture

Status: Needs work » Needs review
FileSize
64.43 KB
4.48 KB

Ok, moved the hooks over, calling parents and removed "current". Let's see if this works.

fago’s picture

Status: Needs review » Needs work

dreditor just ate my review for breakfast ;-/ anyway, here the short version:

+  public static function postLoad(EntityStorageControllerInterface $storage_controller, array &$entities) {
+    $storage_controller->loadCategories($entities);
+    parent::postload($storage_controller, $entities);
+  }

All postLoad() overrides should use default ordering now as there is no reason left not to, i.e. call parent first.

-   * Overrides Drupal\Core\Entity\DatabaseStorageController::attachLoad().
+   * Overrides Drupal\Core\Entity\DatabaseStorageController::postLoad().
    */
-  function attachLoad(&$queried_users, $load_revision = FALSE) {
+  function postLoad(array &$queried_users, $revision_id = FALSE) {

$revision_id should be gone here, not ?

-      $this->attachLoad($queried_entities, $revision_id);
+      $this->postLoad($queried_entities, $revision_id);

Again.

+   *
+   * @todo Don't call parent::postLoad() at all because we want to be able to
+   * control the entity load hooks.

deprecated comment

Berdir’s picture

Status: Needs work » Needs review
Issue tags: +Entity Field API, +sprint, +API change
FileSize
60.01 KB
3.93 KB

Ok, cleaned that up too. Checked with @amateescu, he said it's ok to remove the @todo If I understood correctly :)

fago’s picture

Status: Needs review » Needs work

Found two more - else this is RTBC imo.

+++ b/core/lib/Drupal/Core/Entity/FieldableDatabaseStorageController.php
@@ -372,11 +363,11 @@ public function loadRevision($revision_id) {
-      $this->attachLoad($queried_entities, $revision_id);
+      $this->postLoad($queried_entities, $revision_id);

Another $revision_id left over.

  1. +++ b/core/modules/menu_link/lib/Drupal/menu_link/Entity/MenuLink.php
    @@ -576,6 +576,42 @@ public function postSave(EntityStorageControllerInterface $storage_controller, $
    +    }
    +    parent::postLoad($storage_controller, $entities);
    

    Should move up also.

Berdir’s picture

Status: Needs work » Needs review
FileSize
60 KB
1.7 KB

Ok, cleaned that up as well.

fago’s picture

Status: Needs review » Reviewed & tested by the community

Thanks!

Berdir’s picture

Berdir’s picture

70: storage-logic-2095283-70.patch queued for re-testing.

chx’s picture

Title: Remove storage logic from the storage controllers » Remove non-storage logic from the storage controllers

The title was misleading.

effulgentsia’s picture

Issue tags: +beta target

Probably moot, since this is already RTBC, but just in case, tagging as a beta target, since it touches on data storage issues.

Berdir’s picture

74: storage-logic-2095283-74.patch queued for re-testing.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 74: storage-logic-2095283-74.patch, failed testing.

Berdir’s picture

Status: Needs work » Reviewed & tested by the community
FileSize
56.73 KB

Re-roll after the aggregator categories were removed.

Berdir’s picture

Status: Reviewed & tested by the community » Needs review

RTBC no longer triggers testbot?

Berdir’s picture

Status: Needs review » Reviewed & tested by the community

Passed, back to RTBC.

fago’s picture

79: storage-logic-2095283-79.patch queued for re-testing.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 79: storage-logic-2095283-79.patch, failed testing.

Berdir’s picture

Status: Needs work » Needs review
FileSize
56.69 KB
alexpott’s picture

Priority: Normal » Major
Issue tags: +Approved API change

Tagging and setting correct priority after discussion with @catch

Berdir’s picture

Status: Needs review » Reviewed & tested by the community

That was just a re-roll, so setting back to RTBC.

alexpott’s picture

Title: Remove non-storage logic from the storage controllers » Change notice: Remove non-storage logic from the storage controllers
Status: Reviewed & tested by the community » Active
Issue tags: +Needs change record

Committed 65be82a and pushed to 8.x. Thanks!

sun’s picture

Amazing work, all! :)

There's just one detail I wondered about: Some postLoad() overrides are enhancing the queried entities with entity specific data, and they call into parent::postLoad() to attach fields and invoke module hooks.

However, the position of when parent::postLoad() is called and in turn, the point in time when module hooks are invoked, is not consistent for all entity types. Most of them are calling parent::postLoad() as the first operation, before any additional operations are performed. As a consequence, module hooks will not be able to act on additional data supplied by the entity class, or am I missing something?

It looks like parent::attachLoad() was always invoked as a last step previously, so that module hook implementations always had the full data available?

Is that something we're going to polish via #2137807: Move entity storage related hooks to pre/Post methods on entity classes, or should we open a separate issue for that?

Berdir’s picture

@sun: Just discussed this with Xano as well in IRC too. postLoad() right now is still weird, because FieldableDatabaseStorageController still relies on something that dates back to a hack introduced in the NG storage controller to avoid duplicating the load method. Therefore, that specific implementation receives stdClass objects and converts them to entities within that method, before calling the parent.

I will work on this in #2137801: Refactor entity storage to load field values before instantiating entity objects.

Berdir’s picture

Title: Change notice: Remove non-storage logic from the storage controllers » Remove non-storage logic from the storage controllers
Status: Active » Fixed
Issue tags: -sprint, -Needs change record

Created a change notice about the removed $types argument/additional load arguments support: https://drupal.org/node/2166881

There is afaik no specific change notice about the other moved methods, only a short note in https://drupal.org/node/1400186, added this issue as a reference in that as well (because there aren't enough yet :)). I intend to write this down in the documentation when we're done with the changes.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.