Problem/Motivation

BlockContent::delete is not guaranteed to be called as its just a wrapper to Storage::delete

Steps to reproduce

Call \Drupal::entityTypeManager()->getStorage('block_content')->delete($some_entities) and notice that BlockContent::delete is not called.

Proposed resolution

Move the logic from BlockContent::delete to BlockContent::preDelete

Remaining tasks

Move the logic

User interface changes

API changes

Data model changes

Release notes snippet

entity_delete_multiple() calls $storage_controller->delete($entities) directly.

So any code present in the EntityClass::delete() method is never executed. Thus those two lines are not equivalent :

entity_delete_multiple('my_entity_type', array(1));
entity_load('my_entity_type', 1)->delete();

Problem : entity_delete_multiple() is marked deprecated, with the official recommendation being "use $storage_controller->delete($entities)", i.e we promote bypassing the $entity->delete() method, and deleting entities through the controller directly.

Right now, this means that entity-type-specific code for delete() should not be added in the entity type class, but in the storage controller.
However:
- nothing documents that, if that's how we want things to be, Entity::delete() should be final.
- there are definitely cases where the code would be storage-controller agnostic.

Note : The problem doesn't seem to arise for save, since we never added a generic entity_save() function.

Issue fork drupal-1937266

Command icon 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:

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

yched’s picture

Also, note that while no entity class in core currently overrides Entity::delete() (although we have a case in the "Field API / CMI" in-progress patch), there are a couple already that do override Entity::save().

So "overriding Entity::save() is fine but overriding Entity::delete() will lead to inconsistencies, put the code in StorageController::delete() instead" would not be great DX-wise :-/

msonnabaum’s picture

What if we moved preDelete and postDelete to EntityInterface as empty hook methods, so that any custom business logic can go there? Then there would never be a usecase to override EntityInterface::delete, it would simply delegate to the storage controller's delete method.

If EntityStorageControllerInterface::delete already has an array of entities, it can simply call those hook methods on each entity.

Berdir’s picture

Overriding Entity::save() is not possible either. $storage_controller->save($entity) will not call Entity:save(). That method shouldn't be overridden. It's just a shortcut for the latter.

Whether that is good or bad design is another question, but right now, doing it is wrong :) And making it final is problematic because that prevents us from mocking it.

Not sure about this being a bug. As said above, it's not an exception, it's by design right now, so changing the design would be a task?

yched’s picture

I think there are a couple config entities that override save right now. At least Field and FieldInstance do.

And yeah, putting every logic in the controller kind of sucks :-/.

fago’s picture

If EntityStorageControllerInterface::delete already has an array of entities, it can simply call those hook methods on each entity.

So are you suggesting to go with static methods on the entity class? If we end up passing on $entity_type and $entity_info I don't think this ends up as a good DX, but if we are not and use that for entity-type specific customisations I think it should work and I agree that it improves DX.

fago’s picture

Meanwhile we have the easy way to delete-customizations in the entity class via the static pre/post-delete methods. Thus any entity type customizing delete() does it wrong, and needs to be fixed.

fago’s picture

Title: entity_delete_multiple() bypasses Entity::delete() » CustomBlock, Field and FieldInstance entities do not use pre/postdelete methods

CustomBlock, Field and FieldInstance entities do so - i.e. we should fix them.

yched’s picture

Title: CustomBlock, Field and FieldInstance entities do not use pre/postdelete methods » CustomBlock entities do not use pre/postdelete methods
Component: entity system » block.module

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.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.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

tim.plunkett’s picture

Title: CustomBlock entities do not use pre/postdelete methods » BlockContent entities do not use pre/postdelete methods
Component: block.module » block_content.module
Issue summary: View changes

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.3.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.4.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.3.x-dev » 8.4.x-dev

Drupal 8.3.6 was released on August 2, 2017 and is the final full bugfix release for the Drupal 8.3.x series. Drupal 8.3.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.4.0 on October 4, 2017. (Drupal 8.4.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.4.x-dev branch from now on, and new development or disruptive changes should 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.

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

Drupal 8.4.4 was released on January 3, 2018 and is the final full bugfix release for the Drupal 8.4.x series. Drupal 8.4.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.5.0 on March 7, 2018. (Drupal 8.5.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.5.x-dev branch from now on, and new development or disruptive changes should 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.

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

Drupal 8.5.6 was released on August 1, 2018 and is the final bugfix release for the Drupal 8.5.x series. Drupal 8.5.x will not receive any further development aside from security fixes. Sites should prepare to update to 8.6.0 on September 5, 2018. (Drupal 8.6.0-rc1 is available for testing.)

Bug reports should be targeted against the 8.6.x-dev branch from now on, and new development or disruptive changes should 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.6.x-dev » 8.8.x-dev

Drupal 8.6.x will not receive any further development aside from security fixes. Bug reports should be targeted against the 8.8.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.9.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: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.7 was released on June 3, 2020 and is the final full bugfix release for the Drupal 8.8.x series. Drupal 8.8.x will not receive any further development aside from security fixes. Sites should prepare to update to Drupal 8.9.0 or Drupal 9.0.0 for ongoing support.

Bug reports should be targeted against the 8.9.x-dev branch from now on, and new development or disruptive changes should 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: 8.9.x-dev » 9.2.x-dev

Drupal 8 is end-of-life as of November 17, 2021. There will not be further changes made to Drupal 8. Bugfixes are now made to the 9.3.x and higher branches only. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.15 was released on June 1st, 2022 and is the final full bugfix release for the Drupal 9.3.x series. Drupal 9.3.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.4.x-dev branch from now on, and new development or disruptive changes should 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.

larowlan’s picture

Status: Active » Closed (outdated)
Issue tags: +Bug Smash Initiative
Berdir’s picture

Status: Closed (outdated) » Active

That's new code, there's still a delete method below

larowlan’s picture

So just to confirm, we need to move that logic into a custom storage controller and out of the entity?

I don't understand why that's an issue, is it in cases where someone changes the entity-class?

Berdir’s picture

No, just to pre/post Delete. delete is not guaranteed to be called, it's a wrapper for $storage->delete($entity)

larowlan’s picture

Title: BlockContent entities do not use pre/postdelete methods » BlockContent entities uses delete method instead of pre/postdelete
Issue summary: View changes
Issue tags: +Novice

Thanks, feels like a novice task then.

Updated issue summary

immaculatexavier made their first commit to this issue’s fork.

smustgrave’s picture

Status: Active » Needs review
FileSize
853 bytes

This along the lines of what you were thinking?

Berdir’s picture

It should probably be a a new preDelete() method as the current code runs before delete, but yeah, basically.

smustgrave’s picture

FileSize
1.11 KB

So this? Or do we not need that first loop?

Berdir’s picture

Yes, like this.

This code was added in [##1994146], the test there is functional, so we can't easily change that. We could maybe extend \Drupal\Tests\block_content\Kernel\BlockContentDeletionTest::testDeletingBlockContentShouldClearPluginCache() to also create a block config entity and verify that gets deleted also when using $storage->delete([$block_content]);

smustgrave’s picture

Not sure I follow. I see in that test a block entity does get deleted already.

Berdir’s picture

Yes, but only because the code calls $block_content->delete(). The bug here is that if you call $storage->delete([$block_content]) then that does not happen which is a valid thing to do and for example happens when using the views bulk delete action or directly through the API in custom code.

So we either need a kernel test calling it like that or I suppose we could also test through the bulk delete action, but bulk actions are by default not enabled on the block content view, so we'd need to do that. That's why I'd suggest a kernel test, but the views approach could be useful for example for a manual test.

smustgrave’s picture

FileSize
893 bytes
2.15 KB

So like this?

Berdir’s picture

Status: Needs review » Needs work

The existing kernel test is only testing block *plugins*, the code that we are changing is about block config entities/instances. To test it in the existing kernel test, you also need to create a block for the given plugin and then verify that it was deleted.

smustgrave’s picture

FileSize
1.32 KB
2.82 KB

This closer?

smustgrave’s picture

Status: Needs work » Needs review
andypost’s picture

Issue tags: -Novice

Not sure parent pre-delete should be executed before instance removal
Needs clarification

+++ b/core/modules/block_content/src/Entity/BlockContent.php
@@ -126,6 +126,20 @@ public function postSave(EntityStorageInterface $storage, $update = TRUE) {
+  public static function preDelete(EntityStorageInterface $storage, array $entities) {
+    parent::preDelete($storage, $entities);
...
+    /** @var \Drupal\block_content\BlockContentInterface $block */
+    foreach ($entities as $block) {
+      foreach ($block->getInstances() as $instance) {
+        $instance->delete();

@@ -162,16 +176,6 @@ public function preSaveRevision(EntityStorageInterface $storage, \stdClass $reco
-  public function delete() {
-    foreach ($this->getInstances() as $instance) {
-      $instance->delete();
-    }
-    parent::delete();

I wonder why parent now called before instances delete?

Maybe it does not have effect but looks weird, probably some code comment about why parent called first could help

smustgrave’s picture

@andypost Was following how I saw other preDeletes written.

Berdir’s picture

With delete, the order was relevant, because it essentially means preDelete or postDelete. with preDelete(), it does not because it's either we preDelete and it's IMHO perfectly fine to call the parent first and doesn't need an explanation.

Test looks fine to me, a failing test (a patch with just the test) would be helpful to prove that this indeed covers this bug.

smustgrave’s picture

The last submitted patch, 40: 1937266-40-tests-only.patch, failed testing. View results

andypost’s picture

Status: Needs review » Needs work
+++ b/core/modules/block_content/tests/src/Kernel/BlockContentDeletionTest.php
@@ -57,6 +60,24 @@ public function testDeletingBlockContentShouldClearPluginCache() {
+    $storage = $this->container->get('entity_type.manager')->getStorage('block_content');
...
+    $this->assertNull(\Drupal::entityTypeManager()->getStorage('block')->loadUnchanged($block->id()));

I wonder why 2 different approaches used to access entity type manager, please unify within this testing method to \Drupal::etm

ravi.shankar’s picture

Made changes as per comment #42.

The last submitted patch, 43: 1937266-43-test-only.patch, failed testing. View results

Berdir’s picture

Status: Needs review » Reviewed & tested by the community

I don't like using $this->container but there's an existing example in the test, so fine to stay consistent.

The last submitted patch, 43: 1937266-43-test-only.patch, failed testing. View results

larowlan credited kfitz.

larowlan credited pixelcab.

larowlan’s picture

The last submitted patch, 43: 1937266-43-test-only.patch, failed testing. View results

The last submitted patch, 43: 1937266-43-test-only.patch, failed testing. View results

The last submitted patch, 43: 1937266-43-test-only.patch, failed testing. View results

The last submitted patch, 43: 1937266-43-test-only.patch, failed testing. View results

The last submitted patch, 43: 1937266-43-test-only.patch, failed testing. View results

The last submitted patch, 43: 1937266-43-test-only.patch, failed testing. View results

The last submitted patch, 43: 1937266-43-test-only.patch, failed testing. View results

The last submitted patch, 43: 1937266-43-test-only.patch, failed testing. View results

The last submitted patch, 43: 1937266-43-test-only.patch, failed testing. View results

The last submitted patch, 43: 1937266-43-test-only.patch, failed testing. View results

alexpott’s picture

Version: 9.4.x-dev » 9.5.x-dev
Issue summary: View changes
Status: Reviewed & tested by the community » Fixed

Nice fix.

Committed and pushed 9e67c21100 to 10.1.x and 9c93ae1486 to 10.0.x and ba44f6430e to 9.5.x. Thanks!

  • alexpott committed 9e67c21 on 10.1.x
    Issue #1937266 by smustgrave, ravi.shankar, immaculatexavier, Berdir,...

  • alexpott committed 9c93ae1 on 10.0.x
    Issue #1937266 by smustgrave, ravi.shankar, immaculatexavier, Berdir,...

  • alexpott committed ba44f64 on 9.5.x
    Issue #1937266 by smustgrave, ravi.shankar, immaculatexavier, Berdir,...
alexpott’s picture

I backported this to 9.5.x as this is kinda like moving a hook implementation and has no API implications.

Status: Fixed » Closed (fixed)

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