diff --git a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php index 5986666..6bc33e8 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php @@ -224,25 +224,29 @@ protected function invokeHook($hook, EntityInterface $entity) { /** * Invokes a method on the Field objects within an entity. * - * @param string|callable $method - * The name of the method to be invoked or a callable to which processed - * items will be passed. + * Any argument passed will be forwarded to the invoked method. + * + * @param string $method + * The name of the method to be invoked. * @param \Drupal\Core\Entity\ContentEntityInterface $entity * The entity object. + * + * @return array + * A multidimensional associative array of results, keyed by entity + * translation language code and field name. */ protected function invokeFieldMethod($method, ContentEntityInterface $entity) { - $method_is_callable = is_callable($method); + $result = []; + $args = array_slice(func_get_args(), 2); foreach (array_keys($entity->getTranslationLanguages()) as $langcode) { $translation = $entity->getTranslation($langcode); foreach ($translation->getFields() as $name => $items) { - if ($method_is_callable) { - call_user_func($method, $items, $name, $langcode); - } - else { - $items->$method(); - } + // call_user_func_array is way slower than a direct call so we avoid + // using it if have no parameters. + $result[$langcode][$name] = $args ? call_user_func_array([$items, $method], $args) : $items->{$method}(); } } + return $result; } /** @@ -254,20 +258,12 @@ protected function invokeFieldMethod($method, ContentEntityInterface $entity) { * Specifies whether the entity is being updated or created. */ protected function invokeFieldPostSave(ContentEntityInterface $entity, $update) { - $fields = []; - - $this->invokeFieldMethod( - function (FieldItemListInterface $items, $name) use (&$fields, $update) { - if ($items->postSave($update)) { - $fields[$name] = $name; - } - }, - $entity - ); - - - if ($fields) { - $this->doSaveFieldItems($entity, $fields); + $resave = []; + foreach ($this->invokeFieldMethod('postSave', $entity, $update) as $langcode => $results) { + $resave = array_filter($results); + } + if ($resave) { + $this->doSaveFieldItems($entity, array_keys($resave)); } } diff --git a/core/lib/Drupal/Core/Field/FieldItemInterface.php b/core/lib/Drupal/Core/Field/FieldItemInterface.php index afd1371..c6b94f0 100644 --- a/core/lib/Drupal/Core/Field/FieldItemInterface.php +++ b/core/lib/Drupal/Core/Field/FieldItemInterface.php @@ -183,8 +183,13 @@ public function view($display_options = array()); /** * Defines custom presave behavior for field values. * - * This method is called before insert() and update() methods, and before - * values are written into storage. + * This method is called during the process of saving an entity, just before + * values are written into storage. When storing a new entity, its identifier + * will not be available yet. This should be used to massage item property + * values or perform any other operation that needs to happen before values + * are stored. For instance this is the proper phase to auto-create a new + * entity for an entity reference field item, because this way it will be + * possible to store the referenced entity identifier. */ public function preSave(); @@ -192,7 +197,16 @@ public function preSave(); * Defines custom post-save behavior for field values. * * This method is called during the process of saving an entity, just after - * values are written into storage. + * values are written into storage. This is useful mostly when the business + * logic to be implemented always requires the entity identifier, even when + * storing a new entity. For instance, when implementing circular entity + * references, the referenced entity will be created on pre-save with a dummy + * value for the referring entity identifier, which will be updated with the + * actual one on post-save. + * + * In the rare cases where item properties depend on the entity identifier, + * massaging logic will have to be implemented on post-save and returning TRUE + * will allow them to be rewritten to the storage with the updated values. * * @param bool $update * Specifies whether the entity is being updated or created. diff --git a/core/lib/Drupal/Core/Field/FieldItemList.php b/core/lib/Drupal/Core/Field/FieldItemList.php index a895d4f..5605f11 100644 --- a/core/lib/Drupal/Core/Field/FieldItemList.php +++ b/core/lib/Drupal/Core/Field/FieldItemList.php @@ -211,9 +211,7 @@ public function preSave() { * {@inheritdoc} */ public function postSave($update) { - $result = $this->delegateMethod(function(FieldItemInterface $item) use ($update) { - return $item->postSave($update); - }); + $result = $this->delegateMethod('postSave', $update); return (bool) array_filter($result); } @@ -234,18 +232,21 @@ public function deleteRevision() { /** * Calls a method on each FieldItem. * - * @param string|callable $method - * The name of the method to be invoked or a callable to which processed - * items will be passed. + * Any argument passed will be forwarded to the invoked method. + * + * @param string $method + * The name of the method to be invoked. * * @return array * An array of results keyed by delta. */ protected function delegateMethod($method) { $result = []; - $method_is_callable = is_callable($method); + $args = array_slice(func_get_args(), 1); foreach ($this->list as $delta => $item) { - $result[$delta] = $method_is_callable ? $method($item) : $item->{$method}(); + // call_user_func_array is way slower than a direct call so we avoid using + // it if have no parameters. + $result[$delta] = $args ? call_user_func_array([$item, $method], $args) : $item->{$method}(); } return $result; } diff --git a/core/lib/Drupal/Core/Field/FieldItemListInterface.php b/core/lib/Drupal/Core/Field/FieldItemListInterface.php index 18609cf..df595e5 100644 --- a/core/lib/Drupal/Core/Field/FieldItemListInterface.php +++ b/core/lib/Drupal/Core/Field/FieldItemListInterface.php @@ -130,8 +130,10 @@ public function __unset($property_name); /** * Defines custom presave behavior for field values. * - * This method is called before either insert() or update() methods, and - * before values are written into storage. + * This method is called during the process of saving an entity, just before + * item values are written into storage. + * + * @see \Drupal\Core\Field\FieldItemInterface::preSave() */ public function preSave(); @@ -139,7 +141,7 @@ public function preSave(); * Defines custom post-save behavior for field values. * * This method is called during the process of saving an entity, just after - * values are written into storage. + * item values are written into storage. * * @param bool $update * Specifies whether the entity is being updated or created. @@ -147,6 +149,8 @@ public function preSave(); * @return bool * Whether field items should be rewritten to the storage as a consequence * of the logic implemented by the custom behavior. + * + * @see \Drupal\Core\Field\FieldItemInterface::postSave() */ public function postSave($update); diff --git a/core/modules/file/src/Plugin/Field/FieldType/FileFieldItemList.php b/core/modules/file/src/Plugin/Field/FieldType/FileFieldItemList.php index 5626ee5..033a742 100644 --- a/core/modules/file/src/Plugin/Field/FieldType/FileFieldItemList.php +++ b/core/modules/file/src/Plugin/Field/FieldType/FileFieldItemList.php @@ -24,7 +24,6 @@ public function defaultValuesForm(array &$form, FormStateInterface $form_state) * {@inheritdoc} */ public function postSave($update) { - $resave = parent::postSave($update); $entity = $this->getEntity(); if (!$update) { @@ -75,7 +74,7 @@ public function postSave($update) { } } - return $resave; + return FALSE; } /** diff --git a/core/modules/path/src/Plugin/Field/FieldType/PathItem.php b/core/modules/path/src/Plugin/Field/FieldType/PathItem.php index e6cef58..9ad35ac 100644 --- a/core/modules/path/src/Plugin/Field/FieldType/PathItem.php +++ b/core/modules/path/src/Plugin/Field/FieldType/PathItem.php @@ -56,8 +56,6 @@ public function preSave() { * {@inheritdoc} */ public function postSave($update) { - $resave = parent::postSave($update); - if (!$update) { if ($this->alias) { $entity = $this->getEntity(); @@ -77,8 +75,7 @@ public function postSave($update) { \Drupal::service('path.alias_storage')->save($entity->urlInfo()->getInternalPath(), $this->alias, $this->getLangcode(), $this->pid); } } - - return $resave; + return FALSE; } /**