Drupal 10:
This is a cool thread and I think I can add some value to it. The problem is, that you will have an issue with the
indexes, when you use the removeItem
method in a loop: The delta will change because you use removeItem
inside the
loop.
The solution
The solution to this problem is a recursive function which takes care about the changed index after removeItem
is
called. in the following code we assume that you have your entity reference field
( \Drupal\Core\Field\EntityReferenceFieldItemListInterface
) stored in $my_order_order_item_list
(the name is
"My Order" plus the ER field "Order Item") and a list of entity-ids which should be compared
(array $my_order_item_ids_all
) with it.
Delete orphans on success (not before)
Also we save the entities we want to remove in an array ( @param array $entitiesToRemove
)because we do a try-catch
block where we save our My Order and when this was successful we delete the entities which do not belong to this entity
anymore.
The method
/**
* Removes entities from an entity reference list that are not in the specified list of IDs.
*
* This function uses recursion to safely remove items from the entity reference list
* without causing index issues. It collects the entities to be removed in an array,
* which can then be processed outside the loop.
*
* @param \Drupal\Core\Field\EntityReferenceFieldItemListInterface $my_order_order_item_list
* The entity reference field item list from which entities should be removed.
* @param array $my_order_item_ids_all
* The array of IDs that should remain in the entity reference list.
* @param array $entitiesToRemove
* The array that will be populated with entities to be removed.
*
* @return array
* The array of entities that were removed from the reference list.
*/
private function removeEntitiesRecursively(&$my_order_order_item_list, $my_order_item_ids_all, &$entitiesToRemove) {
// Flag to check if any item was removed
$itemRemoved = false;
foreach ($my_order_order_item_list as $delta => $value) {
if (!in_array($value->target_id, $my_order_item_ids_all)) {
if ($entity = $value->entity) {
$entitiesToRemove[] = $entity;
}
$my_order_order_item_list->removeItem($delta);
$itemRemoved = true;
break; // Exit the loop to avoid index issues
}
}
// If an item was removed, call the function again
if ($itemRemoved) {
$this->removeEntitiesRecursively($my_order_order_item_list, $my_order_item_ids_all, $entitiesToRemove);
}
// Return the list of entities to remove once no items are left to remove
return $entitiesToRemove;
}
The method call
// now we delete all My Order Items which are not in the Request
$entitiesToRemove = $this->removeEntitiesRecursively($my_order_order_item_list, $my_order_item_ids_all, $entitiesToRemove);
Take care of orphans
After that we save our entity My Order and delete orphaned entities on success
try {
$my_order->save();
} catch (\Exception $e) {
// I used this in REST endpoint but you can throw whatever you want, it´s only an example :-)
throw new BadRequestHttpException('The My Order data could not be updated. ' . $e->getMessage());
}
// after the My Order is saved, we physically delete the My Order Items
foreach ($entitiesToRemove as $entityToRemove) {
$entityToRemove->delete();
}
Thoughts
This is the removeItem
method:
https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21TypedData%21Plugin%21DataType%21ItemList.php/function/ItemList%3A%3AremoveItem/10
The index problem is the use of rekey
inside removeItem
https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21TypedData%21Plugin%21DataType%21ItemList.php/function/ItemList%3A%3Arekey/10
In my opinion it´s not the best thing to rely on the index when you remove an item from an entity reference list and I
remember there are some open issues regarding removeItem
so take care about this, maybe something changes or become
deprecated in Drupal 11 or whatsoever, but if it changes to let´s say ID based it will be easy to change the code so: I
am using it (and the cool thing is, we do not have to save the parent entity every time we change the ER list)
The whole answer got a little more complex than requested but it covers problems that you might face when you are in need of manipulating an Entity Reference Field Item List.
Also I assumed that you want to delete the entities which were referenced by the list which is not always the case. The example is coming from eCommerce where you may have orders and order Items attached to it and as long those order Items does belong to a specific order, you want to delete those after you removed them from the list.
We can say that you need this approach in every case you would set the primary key of the referenced entity as foreign key to the parent entity.
What about added entities?
We talked about removing entities but what if we added (also) some entities?
It´s the same as before: whether removeItem
, nor appendItem
will make us save the parent entity each time we use it,
so you can expand your $my_order_order_item_list
before you call the method like so:
$my_order_order_item_list->appendItem($my_order_item_id);