I just ran across this while writing a patch for D8 profile2 module. The problem was that the helper function I used to load the profile objects for a user returned them keyed by their profile type. During bulk deletion in hook_user_delete() I just passed that on to entity_get_controller()->delete() which failed because the wrong entity id's where used. This happened because the storage controllers currently assume that the array keys are always the entity ids. While this is probably expected in most cases it is a somewhat fragile behavior - And it's not even documented by the interface:

  /**
   * Deletes permanently saved entities.
   *
   * @param array $entities
   *   An array of entity objects to delete.
   *
   * @throws Drupal\Core\Entity\EntityStorageException
   *   In case of failures, an exception is thrown.
   */
  public function delete(array $entities);

It's very easy (and most likely doesn't have any performance impact) to just use array_map() to retrieve the id's from the entity objects.

It's not _really_ a bug considering that it works everywhere in Core currently but considering that it's not documented anywhere and somewhat inconsistent with the interface I am marking it as a bug report anyhow.

Fixing this is as easy as this:

// We shouldn't assume that the array keys are the entity ids.
$ids = array_map(function ($entity) {
  return $entity->id();
}, $entities);
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

fubhy’s picture

Component: plugin system » entity system
Status: Active » Needs review
sun’s picture

You're right that it is not enforced or validated in any way currently, but right now, we expect all callers of delete() to pass a proper array that we expect.

This was recently changed/improved in #1822150: EntityStorageControllerInterface::delete() should accept an array of entities, not IDs

fubhy’s picture

I see, so there are two things we can do then:

a) Enforce ids as keys in the ->delete() by extracting them from the entities array as shown here:

// We shouldn't assume that the array keys are the entity ids.
$ids = array_map(function ($entity) {
  return $entity->id();
}, $entities);

or b) Document it in the interface and leave the code as it is (always expect a properly formatted array of entities)

fubhy’s picture

Issue summary: View changes

Adding example of how to fix this.

alansaviolobo queued entity-delete.patch for re-testing.

Status: Needs review » Needs work

The last submitted patch, entity-delete.patch, failed testing.

Berdir’s picture

Issue tags: +Novice, +Needs reroll

An up to date fix for this is currently part of #1885830: Enable static caching for config entities, tagging as Novice and re-roll, instead of trying to re-roll the patch here, just create a new on that contains the code from over there in EntityStorageBase::delete().

dermario’s picture

Assigned: Unassigned » dermario
dermario’s picture

Status: Needs work » Needs review
FileSize
1.36 KB

I tried to fix that issues with the comment of Berdir in #8. Rerolled the patch and took over the changes in EntityStorageBase::delete(). I also replaced the $entities variable with the $keyed_entities variable - otherwise the patch would not make sense since $keyed_entities would not be used.

seanB’s picture

I will review this!

dermario’s picture

Issue tags: +#amsterdam2014
seanB’s picture

Issue tags: -#amsterdam2014

First thing that I noticed:

+++ b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
@@ -339,20 +339,27 @@ public function delete(array $entities) {
+
+

One of these blank lines need to be removed.

I just tested the following:

  • User delete from the user overview
  • User delete from the user edit page
  • Manually calling the delete function with an array of user entities keyed by username

This all seemed to work (without the patch the manual delete didn't actually delete the items. No errors though!).

I will now do the following:

  • Update the patch to remove the extra newline
  • Write a test to make sure the delete function will handle deletes with arrays not keyed by ids.
seanB’s picture

After checking some more, the patch actually doesn't feel like the right way to go.

The actual delete is done in the doDelete() method. In the implementation of the doDelete() method for the SqlContentEntityStorage class, we should get the IDs from the entities, not from the array keys. Basically just like the KeyValueEntityStorage class already does in it's doDelete() implementation.

Attached is a patch. Not sure if we need extra tests for this.

Berdir’s picture

Sure, we can fix it differently, but the thing is that the common expectation when getting a list of entities is that they are keyed by the entity ID. that is the case when you do an entity load.

If we do it like #8, we could simplify KeyValueEntityStorage instead of having to duplicate this logic in all doDelete().

seanB’s picture

Ok, that actually does sound easier.

Attached is an updated patch with the following changes:
- Removed extra newline from #8
- Simplified the doDelete() for the KeyValueEntityStorage

Status: Needs review » Needs work

The last submitted patch, 14: entity_delete_1854912-14.patch, failed testing.

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 14: entity_delete_1854912-14.patch, failed testing.

seanB’s picture

Testbot is failing 'Too many connections'. I'll try again later.

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 14: entity_delete_1854912-14.patch, failed testing.

Status: Needs work » Needs review
dermario’s picture

Assigned: dermario » Unassigned

Unassigning myself from that issue.

chx’s picture

Status: Needs review » Reviewed & tested by the community

It's delete so a) the overhead is miniscule anyways especially on such a super slow operation as a database delete b) making sure the right things get deleted is kinda important :)

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: -Needs reroll +Needs tests

This looks completely sensible - nice code hardening. It would be fantastic to have a test so we don't unexpected revert this. I guess the right place to do this will be Drupal\system\Tests\Entity\EntityApiTest

seanB’s picture

FileSize
3.91 KB

I don't have much experience writing tests, but here is a first attempt to do this. Please let me know if there is stuff I could improve for this patch.

seanB’s picture

Status: Needs work » Needs review
seanB’s picture

Sorry, I changed a comment since the test is not just for nodes. Here's a new patch.

The last submitted patch, 25: entity_delete_1854912-25.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 27: entity_delete_1854912-27.patch, failed testing.

seanB’s picture

FileSize
3.95 KB

Argh, off course I need to check that the delete works for the storage controller. The entity_delete_multiple() does a loadMultiple() that fails when we pass an array not keyed by the IDs.

New patch is attached...

seanB’s picture

Status: Needs work » Needs review
star-szr’s picture

@seanB, can you please post a test-only patch to ensure the test would catch regressions? Thanks!

jhedstrom’s picture

Here's the test only part of #30. Don't attribute credit for this please.

Status: Needs review » Needs work

The last submitted patch, 33: entity-delete-1854912-33-TEST-ONLY.patch, failed testing.

jhedstrom’s picture

Status: Needs work » Needs review

Fail was expected in #33.

bojanz’s picture

Opened a duplicate in #2560433: Entity storage assumes entities passed for deletion are keyed by id.
Patch still applies, though with offsets. Here's a fresh one.
I also took the opportunity to swap the added 'array()' with '[]' in EntityStorageBase::delete().

RTBC if green :)

bojanz’s picture

Title: Don't assume that the array keys are entity id's in DatabaseStorageController::delete() » Don't assume that the array keys are entity id's in EntityStorageBase::delete()

Retitling.

bojanz’s picture

Status: Needs review » Reviewed & tested by the community

We're good to go.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 37: entity_delete_1854912-37.patch, failed testing.

Status: Needs work » Needs review
bojanz’s picture

Status: Needs review » Reviewed & tested by the community

Green again.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed 40ad8ee and pushed to 8.0.x. Thanks!

This change makes lots of sense and does not introduce any disruption.

  • alexpott committed 40ad8ee on 8.0.x
    Issue #1854912 by seanB, bojanz, dermario, fubhy, jhedstrom: Don't...

Status: Fixed » Closed (fixed)

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