Change record status: 
Project: 
Introduced in branch: 
8.0.x
Description: 

Unlike the corresponding entity hooks, FieldItemInterface::insert() and FieldItemInterface::update() were invoked before entity insert/update. The rationale behind this behavior, is that field items may need to act before field values are written to perform some final massaging, but they may also need to know the entity identifier. However supporting this use case is no longer possible in D8, because with the default SQL storage we store base field values while allocating the entity ID, during the initial base table write. Previously we had an inconsistent behavior between shared table fields and dedicated table fields: field methods were run after write for the former, and before write for the latter. This was a critical flaw and the only actual difference between them and FieldItemInterface::preSave(), which made sense only for the default SQL storage.

These methods have been replaced with the FieldItemInterface::postSave() method, that acts after field values are stored, which implies having the entity identifier always available, even when saving the entity for the first time.

Before:

class PathItem {

  public function insert() {
    if ($this->alias) {
      $entity = $this->getEntity();

      if ($path = \Drupal::service('path.alias_storage')->save($entity->urlInfo()->getInternalPath(), $this->alias, $this->getLangcode())) {
        $this->pid = $path['pid'];
      }
    }
  }

  public function update() {
    // Delete old alias if user erased it.
    if ($this->pid && !$this->alias) {
      \Drupal::service('path.alias_storage')->delete(array('pid' => $this->pid));
    }
    // Only save a non-empty alias.
    elseif ($this->alias) {
      $entity = $this->getEntity();
      \Drupal::service('path.alias_storage')->save($entity->urlInfo()->getInternalPath(), $this->alias, $this->getLangcode(), $this->pid);
    }
  }
}

After:

class PathItem {

  public function postSave($update) {
    if (!$update) {
      if ($this->alias) {
        $entity = $this->getEntity();
        if ($path = \Drupal::service('path.alias_storage')->save($entity->urlInfo()->getInternalPath(), $this->alias, $this->getLangcode())) {
          $this->pid = $path['pid'];
        }
      }
    }
    else {
      // Delete old alias if user erased it.
      if ($this->pid && !$this->alias) {
        \Drupal::service('path.alias_storage')->delete(array('pid' => $this->pid));
      }
      // Only save a non-empty alias.
      elseif ($this->alias) {
        $entity = $this->getEntity();
        \Drupal::service('path.alias_storage')->save($entity->urlInfo()->getInternalPath(), $this->alias, $this->getLangcode(), $this->pid);
      }
    }
  }
}

In the exceptional cases where the field item values depend on the entity identifier, it is possible to return TRUE, in which case the field values will be rewritten to the storage.

class FooItem {

  public function postSave($update) {
    $resave = FALSE;
    if (!$update) {
      $this->setValue('bar:' . $this->getEntity()->id());
      $resave = TRUE;
    }
    return $resave;
  }
}

The same changes apply to FieldItemListInterface.

Impacts: 
Module developers