If someone loads an entity, deletes (i.e unsets) one (of the many) values of it's field_collection field, and then saves that entity, that value of that field is deleted but behind-the-scenes the field_collection_item entity that corresponded to that value is never deleted and stays around forever.
This would appear to be a perfectly valid way of deleting the value of a field in Drupal, and so field_collection needs to make sure to detect this situation and also delete everything else associated with that value - namely the associated field_collection_item entity.
This is exactly the sort of behavior core file.module does to ensure that it can delete files when they are no longer referenced by any entities (see modules/file/file.field.inc: file_field_update())
I came across this issue when importing field_collection_items with feeds, since before an 'update' import, feeds simply unsets all current values of a field in preparation for them to be replaced by the import. We have found that our field_collection_item table was getting out of hand.
Patch to follow..
| Comment | File | Size | Author |
|---|---|---|---|
| #17 | field_collection-1186826-17.patch | 2.24 KB | jamsilver |
| #8 | field_collection-check_for_original.1186826.8.patch | 2.54 KB | jamsilver |
| #2 | field_collection.1186826.patch | 2.24 KB | jamsilver |
| #2 | field_collection.1186826.drushmake.patch | 1.84 KB | jamsilver |
| #1 | field_collection.1186826.patch | 2.18 KB | jamsilver |
Comments
Comment #1
jamsilver commentedThis patch is based on that from http://api.drupal.org/api/drupal/modules--file--file.field.inc/function/....
Comment #2
jamsilver commentedAdding a not empty check before calling entity_delete_multiple.
Comment #3
fagoGood catch.
When one uses the delete callback of the formatter, the delete is probably going to be issues twice. But as the delete_multiple issues an entity_load on the ids, I guess that shouldn't harm.
Why not just use $entity->original?
Comment #4
tim.plunkettComment #5
sachbearbeiter commentedsub
Comment #6
jamsilver commentedTo be honest, I based this code on core file module's
file_field_update()which is why I've done it this way.However, having looked at it just now, it would appear that we cannot absolutely guarantee that an entity that has a field collection field on it will have this 'original' property set. Sure any entity created using entity.module will have it set (see
EntityApiController::save()) and sure any core entity will have it set (e.g. seenode_save), but it remains perfectly valid atm in Drupal for someone to create their own entity type without using entity.module - and so not have an $entity->original property available. Which fundamentally is certainly why core file module does not expect to be able to use it either.I suppose, for efficiency, we could make the code check to see if there is an 'original' property available and if so use that, and if not fallback to the current behavior?
Comment #7
jamsilver commentedOK - I've attached a re-roll of the patch that first checks for the existence of the 'original' property and uses that if available.
However, having made it, I'm not convinced it makes sense to do even this. Just because - logically - if it is valid for someone to come along and make a custom entity_type without using entity.module and so without an
$entity->originalproperty, then presumably it is equally valid for them to come along and make a custom entity_type without using entity.module and then use the$entity->originalproperty for something custom that has nothing to do with the unchanged value of the entity?To be fair, maybe such a person should be forced to get an error in that very rare situation! And start using entity.module =p
Your call which patch you want use - the one in #2 still applies on the latest dev.
Comment #8
jamsilver commentedForgot the patch..
Comment #9
liquidcms commentedIs it safe to say that this patch will not help with any pre-existing corrupted nodes? I applied tha ptch and i do not seem to (so far) be able to get the issue to return. However, the nodes which i could not edit before i still can not edit.
Comment #10
liquidcms commenteda bit more editing for my node and i have still been able to corrupt it and again get:
not sure what i did that broke the node; will keep testing
Comment #11
liquidcms commentedi think i was able to still corrupt the node when i cloned the node (#1304214: Doesn't clone properly) and then removed items
Comment #12
rp7 commentedsubscribe
Comment #13
interx commentedI'm getting still getting this issue after the patch. When deleting a FieldCollectionItemEntity directly, the reference in the node's collection field remains and triggers an EntityMalformedException.
I thought I would be able to just use
$field_collection_item->delete();or
But because of the stale references in the collection field that triggers :
"EntityMalformedException: Missing bundle property on entity of type field_collection_item. in entity_extract_ids() (line 7389..."I can avoid this by manually removing the collectionfield item from the collection field like
But shouldn't that be done automatically?
Comment #14
daniel wentsch commentedSubscribing... wasn't yet able to reproduce the error yet.
Comment #15
Lerain commentedI've got this problem as well and made the following obervation - maybe someone else is able to work his magic with this hint:
So I've got a node I cannot edit anymore, receiving the error:
Now the node id is 8034 and the field_collection_item for that content type is field_global_video. So I checked the database-field: field_data_field_global_video, looked up the line of the entity_id 8034 and found the value 192 in the field: field_global_video_value.
So with the 192 in mind I went over to the field_collection_item table but couldn't find any item_id 192. Since I know that this should be a field_global_video in my case, I just inserted the line manually via PHPMyAdmin.
INSERT INTO `tablename`.`field_collection_item` (`item_id`, `field_name`) VALUES ('192', 'field_global_video');And voila... the node is editable again. Now this is certainly not a solution, but maybe a hint.
Comment #16
jamsilver commentedCorrect me if I'm wrong, but all comments from #9 - #15 are about a different issue.
This issue is about how old, unreferenced field_collection_item entities are not removed when the field that referenced them on the 'host' entity is deleted programmatically. It is essentially a 'housekeeping' problem and has not yet been demonstrated to cause errors (e.g. corrupted nodes). The only problem with it is that the field_collection_item table can get very very big.
Issues #9 - #15 seem to be about the complimentary/reverse situation whereby when the field_collection_item entity itself is deleted programmatically, the 'host' entity that was referencing it now becomes somehow unloadable / corrupted.
I am setting this issue to 'needs review' because my patches in #2 and #8 still stand.
I am making another issue to handle the reverse problem: #1331764: Delete host entity reference when a field_collection_item entity is deleted programatically.
Comment #17
jamsilver commentedRe-rolling against latest.
Comment #18
jamsilver commentedYay! This has finally been fixed - #1776424: Removed field-collections are never deleted
Comment #19
TD44 commentedHi, i have many orphaned field collections items.
Does this patch remove all previous orphaned items ? Or does this patch works only when removing field collection items after installing this patch?
Comment #20
commonpike commentedSame question as @TD44 . Does this patch remove all previous orphaned items ? Or does this patch works only when removing field collection items after installing this patch?
Comment #21
mastoll commentedI have orphaned field collection items that I can't figure out how to find/see, nor how to delete. Would love to have the answer to @TD44's question! Anyone?
Comment #22
remyyyyyTo start, you can use a query like this one to see the orphaned field collection items :
Then, you can export the result to a comma separated list like this :
And you can delete all the orphaned field collection items with a call to function entity_delete_multiple() like this :
Comment #23
jvieille commentedDeleted