From 15c7c6bc6b2cb55d43b0f0f3f84292446ccfac3b Mon Sep 17 00:00:00 2001
From: Lee Rowlands <lee.rowlands@previousnext.com.au>
Date: Fri, 16 Aug 2019 07:56:44 +1000
Subject: [PATCH] Issue #3056816 by amateescu, larowlan, hchonov, plach,
 jibran, Berdir, catch, Sam152: Installing a new field storage definition
 during a fieldable entity type update is not possible

---
 .../Drupal/Core/Entity/EntityTypeListener.php |  28 +++-
 .../Field/FieldStorageDefinitionListener.php  |  12 +-
 .../entity_test/entity_test.services.yml      |   2 +-
 .../src/EntityTestDefinitionSubscriber.php    | 138 +++++++++++++++++-
 .../entity_test_update.services.yml           |   6 +
 .../EntitySchemaSubscriber.php                |  74 ++++++++++
 .../Entity/EntityDefinitionUpdateTest.php     |  66 ++++++++-
 .../FieldableEntityDefinitionUpdateTest.php   |  20 ++-
 8 files changed, 322 insertions(+), 24 deletions(-)
 create mode 100644 core/modules/system/tests/modules/entity_test_update/entity_test_update.services.yml
 create mode 100644 core/modules/system/tests/modules/entity_test_update/src/EventSubscriber/EntitySchemaSubscriber.php

diff --git a/core/lib/Drupal/Core/Entity/EntityTypeListener.php b/core/lib/Drupal/Core/Entity/EntityTypeListener.php
index 0adcecfb6286..9ab4fab633f5 100644
--- a/core/lib/Drupal/Core/Entity/EntityTypeListener.php
+++ b/core/lib/Drupal/Core/Entity/EntityTypeListener.php
@@ -71,12 +71,13 @@ public function onEntityTypeCreate(EntityTypeInterface $entity_type) {
       $storage->onEntityTypeCreate($entity_type);
     }
 
-    $this->eventDispatcher->dispatch(EntityTypeEvents::CREATE, new EntityTypeEvent($entity_type));
-
     $this->entityLastInstalledSchemaRepository->setLastInstalledDefinition($entity_type);
     if ($entity_type->entityClassImplements(FieldableEntityInterface::class)) {
       $this->entityLastInstalledSchemaRepository->setLastInstalledFieldStorageDefinitions($entity_type_id, $this->entityFieldManager->getFieldStorageDefinitions($entity_type_id));
     }
+
+    $this->eventDispatcher->dispatch(EntityTypeEvents::CREATE, new EntityTypeEvent($entity_type));
+    $this->clearCachedDefinitions();
   }
 
   /**
@@ -94,9 +95,10 @@ public function onEntityTypeUpdate(EntityTypeInterface $entity_type, EntityTypeI
       $storage->onEntityTypeUpdate($entity_type, $original);
     }
 
-    $this->eventDispatcher->dispatch(EntityTypeEvents::UPDATE, new EntityTypeEvent($entity_type, $original));
-
     $this->entityLastInstalledSchemaRepository->setLastInstalledDefinition($entity_type);
+
+    $this->eventDispatcher->dispatch(EntityTypeEvents::UPDATE, new EntityTypeEvent($entity_type, $original));
+    $this->clearCachedDefinitions();
   }
 
   /**
@@ -116,9 +118,10 @@ public function onEntityTypeDelete(EntityTypeInterface $entity_type) {
       $storage->onEntityTypeDelete($entity_type);
     }
 
-    $this->eventDispatcher->dispatch(EntityTypeEvents::DELETE, new EntityTypeEvent($entity_type));
-
     $this->entityLastInstalledSchemaRepository->deleteLastInstalledDefinition($entity_type_id);
+
+    $this->eventDispatcher->dispatch(EntityTypeEvents::DELETE, new EntityTypeEvent($entity_type));
+    $this->clearCachedDefinitions();
   }
 
   /**
@@ -135,13 +138,22 @@ public function onFieldableEntityTypeUpdate(EntityTypeInterface $entity_type, En
     }
 
     if ($sandbox === NULL || (isset($sandbox['#finished']) && $sandbox['#finished'] == 1)) {
-      $this->eventDispatcher->dispatch(EntityTypeEvents::UPDATE, new EntityTypeEvent($entity_type, $original));
-
       $this->entityLastInstalledSchemaRepository->setLastInstalledDefinition($entity_type);
       if ($entity_type->entityClassImplements(FieldableEntityInterface::class)) {
         $this->entityLastInstalledSchemaRepository->setLastInstalledFieldStorageDefinitions($entity_type_id, $field_storage_definitions);
       }
+
+      $this->eventDispatcher->dispatch(EntityTypeEvents::UPDATE, new EntityTypeEvent($entity_type, $original));
+      $this->clearCachedDefinitions();
     }
   }
 
+  /**
+   * Clears necessary caches to apply entity/field definition updates.
+   */
+  protected function clearCachedDefinitions() {
+    $this->entityTypeManager->clearCachedDefinitions();
+    $this->entityFieldManager->clearCachedFieldDefinitions();
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Field/FieldStorageDefinitionListener.php b/core/lib/Drupal/Core/Field/FieldStorageDefinitionListener.php
index fda621b92915..b73f3411e5a2 100644
--- a/core/lib/Drupal/Core/Field/FieldStorageDefinitionListener.php
+++ b/core/lib/Drupal/Core/Field/FieldStorageDefinitionListener.php
@@ -85,9 +85,9 @@ public function onFieldStorageDefinitionCreate(FieldStorageDefinitionInterface $
       $storage->onFieldStorageDefinitionCreate($storage_definition);
     }
 
-    $this->eventDispatcher->dispatch(FieldStorageDefinitionEvents::CREATE, new FieldStorageDefinitionEvent($storage_definition));
-
     $this->entityLastInstalledSchemaRepository->setLastInstalledFieldStorageDefinition($storage_definition);
+
+    $this->eventDispatcher->dispatch(FieldStorageDefinitionEvents::CREATE, new FieldStorageDefinitionEvent($storage_definition));
     $this->entityFieldManager->clearCachedFieldDefinitions();
   }
 
@@ -104,9 +104,9 @@ public function onFieldStorageDefinitionUpdate(FieldStorageDefinitionInterface $
       $storage->onFieldStorageDefinitionUpdate($storage_definition, $original);
     }
 
-    $this->eventDispatcher->dispatch(FieldStorageDefinitionEvents::UPDATE, new FieldStorageDefinitionEvent($storage_definition, $original));
-
     $this->entityLastInstalledSchemaRepository->setLastInstalledFieldStorageDefinition($storage_definition);
+
+    $this->eventDispatcher->dispatch(FieldStorageDefinitionEvents::UPDATE, new FieldStorageDefinitionEvent($storage_definition, $original));
     $this->entityFieldManager->clearCachedFieldDefinitions();
   }
 
@@ -133,9 +133,9 @@ public function onFieldStorageDefinitionDelete(FieldStorageDefinitionInterface $
       $storage->onFieldStorageDefinitionDelete($storage_definition);
     }
 
-    $this->eventDispatcher->dispatch(FieldStorageDefinitionEvents::DELETE, new FieldStorageDefinitionEvent($storage_definition));
-
     $this->entityLastInstalledSchemaRepository->deleteLastInstalledFieldStorageDefinition($storage_definition);
+
+    $this->eventDispatcher->dispatch(FieldStorageDefinitionEvents::DELETE, new FieldStorageDefinitionEvent($storage_definition));
     $this->entityFieldManager->clearCachedFieldDefinitions();
   }
 
diff --git a/core/modules/system/tests/modules/entity_test/entity_test.services.yml b/core/modules/system/tests/modules/entity_test/entity_test.services.yml
index 75e1bf33ce68..212de69c186f 100644
--- a/core/modules/system/tests/modules/entity_test/entity_test.services.yml
+++ b/core/modules/system/tests/modules/entity_test/entity_test.services.yml
@@ -1,7 +1,7 @@
 services:
   entity_test.definition.subscriber:
     class: Drupal\entity_test\EntityTestDefinitionSubscriber
-    arguments: ['@state']
+    arguments: ['@state', '@entity.last_installed_schema.repository', '@entity_type.manager', '@entity_field.manager']
     tags:
       - { name: event_subscriber }
   cache_context.entity_test_view_grants:
diff --git a/core/modules/system/tests/modules/entity_test/src/EntityTestDefinitionSubscriber.php b/core/modules/system/tests/modules/entity_test/src/EntityTestDefinitionSubscriber.php
index 6cdceb6d9cc7..e96ebd406601 100644
--- a/core/modules/system/tests/modules/entity_test/src/EntityTestDefinitionSubscriber.php
+++ b/core/modules/system/tests/modules/entity_test/src/EntityTestDefinitionSubscriber.php
@@ -2,10 +2,13 @@
 
 namespace Drupal\entity_test;
 
+use Drupal\Core\Entity\EntityFieldManagerInterface;
+use Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface;
 use Drupal\Core\Entity\EntityTypeEvents;
 use Drupal\Core\Entity\EntityTypeEventSubscriberTrait;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Entity\EntityTypeListenerInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Field\FieldStorageDefinitionEvents;
 use Drupal\Core\Field\FieldStorageDefinitionEventSubscriberTrait;
 use Drupal\Core\Field\FieldStorageDefinitionInterface;
@@ -28,6 +31,27 @@ class EntityTestDefinitionSubscriber implements EventSubscriberInterface, Entity
    */
   protected $state;
 
+  /**
+   * The last installed schema repository.
+   *
+   * @var \Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface
+   */
+  protected $entityLastInstalledSchemaRepository;
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * The entity field manager.
+   *
+   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
+   */
+  protected $entityFieldManager;
+
   /**
    * Flag determining whether events should be tracked.
    *
@@ -35,11 +59,21 @@ class EntityTestDefinitionSubscriber implements EventSubscriberInterface, Entity
    */
   protected $trackEvents = FALSE;
 
+  /**
+   * Determines whether the live definitions should be updated.
+   *
+   * @var bool
+   */
+  protected $updateLiveDefinitions = FALSE;
+
   /**
    * {@inheritdoc}
    */
-  public function __construct(StateInterface $state) {
+  public function __construct(StateInterface $state, EntityLastInstalledSchemaRepositoryInterface $entity_last_installed_schema_repository, EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager) {
     $this->state = $state;
+    $this->entityLastInstalledSchemaRepository = $entity_last_installed_schema_repository;
+    $this->entityTypeManager = $entity_type_manager;
+    $this->entityFieldManager = $entity_field_manager;
   }
 
   /**
@@ -53,14 +87,38 @@ public static function getSubscribedEvents() {
    * {@inheritdoc}
    */
   public function onEntityTypeCreate(EntityTypeInterface $entity_type) {
+    if ($this->entityLastInstalledSchemaRepository->getLastInstalledDefinition($entity_type->id())) {
+      $this->storeDefinitionUpdate(EntityTypeEvents::CREATE);
+    }
     $this->storeEvent(EntityTypeEvents::CREATE);
+
+    // Retrieve the live entity type definition in order to warm the static
+    // cache and then insert the new entity type definition, so we can test that
+    // the cache doesn't get stale after the event has fired.
+    if ($this->updateLiveDefinitions) {
+      $this->entityTypeManager->getDefinition($entity_type->id());
+      $this->state->set('entity_test_rev.entity_type', $entity_type);
+    }
   }
 
   /**
    * {@inheritdoc}
    */
   public function onEntityTypeUpdate(EntityTypeInterface $entity_type, EntityTypeInterface $original) {
+    $last_installed_definition = $this->entityLastInstalledSchemaRepository->getLastInstalledDefinition($entity_type->id());
+    if ((string) $last_installed_definition->getLabel() === 'Updated entity test rev') {
+      $this->storeDefinitionUpdate(EntityTypeEvents::UPDATE);
+    }
+
     $this->storeEvent(EntityTypeEvents::UPDATE);
+
+    // Retrieve the live entity type definition in order to warm the static
+    // cache and then insert the new entity type definition, so we can test that
+    // the cache doesn't get stale after the event has fired.
+    if ($this->updateLiveDefinitions) {
+      $this->entityTypeManager->getDefinition($entity_type->id());
+      $this->state->set('entity_test_rev.entity_type', $entity_type);
+    }
   }
 
   /**
@@ -74,28 +132,73 @@ public function onFieldableEntityTypeUpdate(EntityTypeInterface $entity_type, En
    * {@inheritdoc}
    */
   public function onEntityTypeDelete(EntityTypeInterface $entity_type) {
+    if (!$this->entityLastInstalledSchemaRepository->getLastInstalledDefinition($entity_type->id())) {
+      $this->storeDefinitionUpdate(EntityTypeEvents::DELETE);
+    }
     $this->storeEvent(EntityTypeEvents::DELETE);
+
+    // Retrieve the live entity type definition in order to warm the static
+    // cache and then delete the new entity type definition, so we can test that
+    // the cache doesn't get stale after the event has fired.
+    if ($this->updateLiveDefinitions) {
+      $this->entityTypeManager->getDefinition($entity_type->id());
+      $this->state->set('entity_test_rev.entity_type', '');
+    }
   }
 
   /**
    * {@inheritdoc}
    */
   public function onFieldStorageDefinitionCreate(FieldStorageDefinitionInterface $storage_definition) {
+    if (isset($this->entityLastInstalledSchemaRepository->getLastInstalledFieldStorageDefinitions($storage_definition->getTargetEntityTypeId())[$storage_definition->getName()])) {
+      $this->storeDefinitionUpdate(FieldStorageDefinitionEvents::CREATE);
+    }
     $this->storeEvent(FieldStorageDefinitionEvents::CREATE);
+
+    // Retrieve the live field storage definitions in order to warm the static
+    // cache and then insert the new storage definition, so we can test that the
+    // cache doesn't get stale after the event has fired.
+    if ($this->updateLiveDefinitions) {
+      $this->entityFieldManager->getFieldStorageDefinitions($storage_definition->getTargetEntityTypeId());
+      $this->state->set('entity_test_rev.additional_base_field_definitions', [$storage_definition->getName() => $storage_definition]);
+    }
   }
 
   /**
    * {@inheritdoc}
    */
   public function onFieldStorageDefinitionUpdate(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) {
+    $last_installed_definition = $this->entityLastInstalledSchemaRepository->getLastInstalledFieldStorageDefinitions($storage_definition->getTargetEntityTypeId())[$storage_definition->getName()];
+    if ((string) $last_installed_definition->getLabel() === 'Updated field storage test') {
+      $this->storeDefinitionUpdate(FieldStorageDefinitionEvents::UPDATE);
+    }
     $this->storeEvent(FieldStorageDefinitionEvents::UPDATE);
+
+    // Retrieve the live field storage definitions in order to warm the static
+    // cache and then insert the new storage definition, so we can test that the
+    // cache doesn't get stale after the event has fired.
+    if ($this->updateLiveDefinitions) {
+      $this->entityFieldManager->getFieldStorageDefinitions($storage_definition->getTargetEntityTypeId());
+      $this->state->set('entity_test_rev.additional_base_field_definitions', [$storage_definition->getName() => $storage_definition]);
+    }
   }
 
   /**
    * {@inheritdoc}
    */
   public function onFieldStorageDefinitionDelete(FieldStorageDefinitionInterface $storage_definition) {
+    if (!isset($this->entityLastInstalledSchemaRepository->getLastInstalledFieldStorageDefinitions($storage_definition->getTargetEntityTypeId())[$storage_definition->getName()])) {
+      $this->storeDefinitionUpdate(FieldStorageDefinitionEvents::DELETE);
+    }
     $this->storeEvent(FieldStorageDefinitionEvents::DELETE);
+
+    // Retrieve the live field storage definitions in order to warm the static
+    // cache and then remove the new storage definition, so we can test that the
+    // cache doesn't get stale after the event has fired.
+    if ($this->updateLiveDefinitions) {
+      $this->entityFieldManager->getFieldStorageDefinitions($storage_definition->getTargetEntityTypeId());
+      $this->state->set('entity_test_rev.additional_base_field_definitions', []);
+    }
   }
 
   /**
@@ -105,6 +208,13 @@ public function enableEventTracking() {
     $this->trackEvents = TRUE;
   }
 
+  /**
+   * Enables live definition updates.
+   */
+  public function enableLiveDefinitionUpdates() {
+    $this->updateLiveDefinitions = TRUE;
+  }
+
   /**
    * Checks whether an event has been dispatched.
    *
@@ -130,4 +240,30 @@ protected function storeEvent($event_name) {
     }
   }
 
+  /**
+   * Checks whether the installed definitions were updated before the event.
+   *
+   * @param string $event_name
+   *   The event name.
+   *
+   * @return bool
+   *   TRUE if the last installed entity type of field storage definitions have
+   *   been updated before the event was fired, FALSE otherwise.
+   */
+  public function hasDefinitionBeenUpdated($event_name) {
+    return (bool) $this->state->get($event_name . '_updated_definition');
+  }
+
+  /**
+   * Stores the installed definition state for the specified event.
+   *
+   * @param string $event_name
+   *   The event name.
+   */
+  protected function storeDefinitionUpdate($event_name) {
+    if ($this->trackEvents) {
+      $this->state->set($event_name . '_updated_definition', TRUE);
+    }
+  }
+
 }
diff --git a/core/modules/system/tests/modules/entity_test_update/entity_test_update.services.yml b/core/modules/system/tests/modules/entity_test_update/entity_test_update.services.yml
new file mode 100644
index 000000000000..45d1606daa94
--- /dev/null
+++ b/core/modules/system/tests/modules/entity_test_update/entity_test_update.services.yml
@@ -0,0 +1,6 @@
+services:
+  entity_test_update.entity_schema_listener:
+    class: Drupal\entity_test_update\EventSubscriber\EntitySchemaSubscriber
+    arguments: ['@entity.definition_update_manager', '@state']
+    tags:
+      - { name: 'event_subscriber' }
diff --git a/core/modules/system/tests/modules/entity_test_update/src/EventSubscriber/EntitySchemaSubscriber.php b/core/modules/system/tests/modules/entity_test_update/src/EventSubscriber/EntitySchemaSubscriber.php
new file mode 100644
index 000000000000..37dac651e2c6
--- /dev/null
+++ b/core/modules/system/tests/modules/entity_test_update/src/EventSubscriber/EntitySchemaSubscriber.php
@@ -0,0 +1,74 @@
+<?php
+
+namespace Drupal\entity_test_update\EventSubscriber;
+
+use Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface;
+use Drupal\Core\Entity\EntityTypeEventSubscriberTrait;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\EntityTypeListenerInterface;
+use Drupal\Core\Field\BaseFieldDefinition;
+use Drupal\Core\State\StateInterface;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Defines a class for listening to entity schema changes.
+ */
+class EntitySchemaSubscriber implements EntityTypeListenerInterface, EventSubscriberInterface {
+
+  use EntityTypeEventSubscriberTrait;
+
+  /**
+   * The entity definition update manager.
+   *
+   * @var \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface
+   */
+  protected $entityDefinitionUpdateManager;
+
+  /**
+   * The state service.
+   *
+   * @var \Drupal\Core\State\StateInterface
+   */
+  protected $state;
+
+  /**
+   * Constructs a new EntitySchemaSubscriber.
+   *
+   * @param \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface $entityDefinitionUpdateManager
+   *   The entity definition update manager.
+   * @param \Drupal\Core\State\StateInterface $state
+   *   The state service.
+   */
+  public function __construct(EntityDefinitionUpdateManagerInterface $entityDefinitionUpdateManager, StateInterface $state) {
+    $this->entityDefinitionUpdateManager = $entityDefinitionUpdateManager;
+    $this->state = $state;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents() {
+    return static::getEntityTypeEvents();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function onEntityTypeUpdate(EntityTypeInterface $entity_type, EntityTypeInterface $original) {
+    // Only add the new base field when a test needs it.
+    if (!$this->state->get('entity_test_update.install_new_base_field_during_update', FALSE)) {
+      return;
+    }
+
+    // Add a new base field when the entity type is updated.
+    $definitions = $this->state->get('entity_test_update.additional_base_field_definitions', []);
+    $definitions['new_base_field'] = BaseFieldDefinition::create('string')
+      ->setName('new_base_field')
+      ->setLabel(new TranslatableMarkup('A new base field'));
+    $this->state->set('entity_test_update.additional_base_field_definitions', $definitions);
+
+    $this->entityDefinitionUpdateManager->installFieldStorageDefinition('new_base_field', 'entity_test_update', 'entity_test_update', $definitions['new_base_field']);
+  }
+
+}
diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityDefinitionUpdateTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityDefinitionUpdateTest.php
index 76b63efef159..9afc0eaf305e 100644
--- a/core/tests/Drupal/KernelTests/Core/Entity/EntityDefinitionUpdateTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityDefinitionUpdateTest.php
@@ -14,6 +14,8 @@
 use Drupal\Core\Field\FieldException;
 use Drupal\Core\Field\FieldStorageDefinitionEvents;
 use Drupal\Core\Language\LanguageInterface;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\entity_test\FieldStorageDefinition;
 use Drupal\entity_test_update\Entity\EntityTestUpdate;
 use Drupal\Tests\system\Functional\Entity\Traits\EntityDefinitionTestTrait;
 
@@ -35,6 +37,13 @@ class EntityDefinitionUpdateTest extends EntityKernelTestBase {
    */
   protected $entityDefinitionUpdateManager;
 
+  /**
+   * The entity field manager.
+   *
+   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
+   */
+  protected $entityFieldManager;
+
   /**
    * The database connection.
    *
@@ -55,6 +64,7 @@ class EntityDefinitionUpdateTest extends EntityKernelTestBase {
   protected function setUp() {
     parent::setUp();
     $this->entityDefinitionUpdateManager = $this->container->get('entity.definition_update_manager');
+    $this->entityFieldManager = $this->container->get('entity_field.manager');
     $this->database = $this->container->get('database');
 
     // Install every entity type's schema that wasn't installed in the parent
@@ -833,30 +843,74 @@ public function testDefinitionEvents() {
     /** @var \Drupal\entity_test\EntityTestDefinitionSubscriber $event_subscriber */
     $event_subscriber = $this->container->get('entity_test.definition.subscriber');
     $event_subscriber->enableEventTracking();
+    $event_subscriber->enableLiveDefinitionUpdates();
 
     // Test field storage definition events.
-    $storage_definition = current(\Drupal::service('entity_field.manager')->getFieldStorageDefinitions('entity_test_rev'));
-    $this->assertFalse($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::DELETE), 'Entity type delete was not dispatched yet.');
-    \Drupal::service('field_storage_definition.listener')->onFieldStorageDefinitionDelete($storage_definition);
-    $this->assertTrue($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::DELETE), 'Entity type delete event successfully dispatched.');
+    $storage_definition = FieldStorageDefinition::create('string')
+      ->setName('field_storage_test')
+      ->setLabel(new TranslatableMarkup('Field storage test'))
+      ->setTargetEntityTypeId('entity_test_rev');
+
     $this->assertFalse($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::CREATE), 'Entity type create was not dispatched yet.');
     \Drupal::service('field_storage_definition.listener')->onFieldStorageDefinitionCreate($storage_definition);
     $this->assertTrue($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::CREATE), 'Entity type create event successfully dispatched.');
+    $this->assertTrue($event_subscriber->hasDefinitionBeenUpdated(FieldStorageDefinitionEvents::CREATE), 'Last installed field storage definition was created before the event was fired.');
+
+    // Check that the newly added field can be retrieved from the live field
+    // storage definitions.
+    $field_storage_definitions = $this->entityFieldManager->getFieldStorageDefinitions('entity_test_rev');
+    $this->assertArrayHasKey('field_storage_test', $field_storage_definitions);
+
+    $updated_storage_definition = clone $storage_definition;
+    $updated_storage_definition->setLabel(new TranslatableMarkup('Updated field storage test'));
     $this->assertFalse($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::UPDATE), 'Entity type update was not dispatched yet.');
-    \Drupal::service('field_storage_definition.listener')->onFieldStorageDefinitionUpdate($storage_definition, $storage_definition);
+    \Drupal::service('field_storage_definition.listener')->onFieldStorageDefinitionUpdate($updated_storage_definition, $storage_definition);
     $this->assertTrue($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::UPDATE), 'Entity type update event successfully dispatched.');
+    $this->assertTrue($event_subscriber->hasDefinitionBeenUpdated(FieldStorageDefinitionEvents::UPDATE), 'Last installed field storage definition was updated before the event was fired.');
+
+    // Check that the updated field can be retrieved from the live field storage
+    // definitions.
+    $field_storage_definitions = $this->entityFieldManager->getFieldStorageDefinitions('entity_test_rev');
+    $this->assertEquals(new TranslatableMarkup('Updated field storage test'), $field_storage_definitions['field_storage_test']->getLabel());
+
+    $this->assertFalse($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::DELETE), 'Entity type delete was not dispatched yet.');
+    \Drupal::service('field_storage_definition.listener')->onFieldStorageDefinitionDelete($storage_definition);
+    $this->assertTrue($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::DELETE), 'Entity type delete event successfully dispatched.');
+    $this->assertTrue($event_subscriber->hasDefinitionBeenUpdated(FieldStorageDefinitionEvents::DELETE), 'Last installed field storage definition was deleted before the event was fired.');
+
+    // Check that the deleted field can no longer be retrieved from the live
+    // field storage definitions.
+    $field_storage_definitions = $this->entityFieldManager->getFieldStorageDefinitions('entity_test_rev');
+    $this->assertArrayNotHasKey('field_storage_test', $field_storage_definitions);
 
     // Test entity type events.
     $entity_type = $this->entityTypeManager->getDefinition('entity_test_rev');
+
     $this->assertFalse($event_subscriber->hasEventFired(EntityTypeEvents::CREATE), 'Entity type create was not dispatched yet.');
     \Drupal::service('entity_type.listener')->onEntityTypeCreate($entity_type);
     $this->assertTrue($event_subscriber->hasEventFired(EntityTypeEvents::CREATE), 'Entity type create event successfully dispatched.');
+    $this->assertTrue($event_subscriber->hasDefinitionBeenUpdated(EntityTypeEvents::CREATE), 'Last installed entity type definition was created before the event was fired.');
+
+    $updated_entity_type = clone $entity_type;
+    $updated_entity_type->set('label', new TranslatableMarkup('Updated entity test rev'));
     $this->assertFalse($event_subscriber->hasEventFired(EntityTypeEvents::UPDATE), 'Entity type update was not dispatched yet.');
-    \Drupal::service('entity_type.listener')->onEntityTypeUpdate($entity_type, $entity_type);
+    \Drupal::service('entity_type.listener')->onEntityTypeUpdate($updated_entity_type, $entity_type);
     $this->assertTrue($event_subscriber->hasEventFired(EntityTypeEvents::UPDATE), 'Entity type update event successfully dispatched.');
+    $this->assertTrue($event_subscriber->hasDefinitionBeenUpdated(EntityTypeEvents::UPDATE), 'Last installed entity type definition was updated before the event was fired.');
+
+    // Check that the updated definition can be retrieved from the live entity
+    // type definitions.
+    $entity_type = $this->entityTypeManager->getDefinition('entity_test_rev');
+    $this->assertEquals(new TranslatableMarkup('Updated entity test rev'), $entity_type->getLabel());
+
     $this->assertFalse($event_subscriber->hasEventFired(EntityTypeEvents::DELETE), 'Entity type delete was not dispatched yet.');
     \Drupal::service('entity_type.listener')->onEntityTypeDelete($entity_type);
     $this->assertTrue($event_subscriber->hasEventFired(EntityTypeEvents::DELETE), 'Entity type delete event successfully dispatched.');
+    $this->assertTrue($event_subscriber->hasDefinitionBeenUpdated(EntityTypeEvents::DELETE), 'Last installed entity type definition was deleted before the event was fired.');
+
+    // Check that the deleted entity type can no longer be retrieved from the
+    // live entity type definitions.
+    $this->assertNull($this->entityTypeManager->getDefinition('entity_test_rev', FALSE));
   }
 
   /**
diff --git a/core/tests/Drupal/KernelTests/Core/Entity/FieldableEntityDefinitionUpdateTest.php b/core/tests/Drupal/KernelTests/Core/Entity/FieldableEntityDefinitionUpdateTest.php
index 937a2ab5a452..a525f650eb31 100644
--- a/core/tests/Drupal/KernelTests/Core/Entity/FieldableEntityDefinitionUpdateTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Entity/FieldableEntityDefinitionUpdateTest.php
@@ -151,13 +151,17 @@ public function testFieldableEntityTypeUpdates($initial_rev, $initial_mul, $new_
       $this->assertEntityData($initial_rev, $initial_mul);
     }
 
+    // Enable the creation of a new base field during a fieldable entity type
+    // update.
+    $this->state->set('entity_test_update.install_new_base_field_during_update', TRUE);
+
     // Simulate a batch run since we are converting the entities one by one.
     $sandbox = [];
     do {
       $this->entityDefinitionUpdateManager->updateFieldableEntityType($updated_entity_type, $updated_field_storage_definitions, $sandbox);
     } while ($sandbox['#finished'] != 1);
 
-    $this->assertEntityTypeSchema($new_rev, $new_mul);
+    $this->assertEntityTypeSchema($new_rev, $new_mul, TRUE);
     $this->assertEntityData($initial_rev, $initial_mul);
 
     $change_list = $this->entityDefinitionUpdateManager->getChangeList();
@@ -427,8 +431,20 @@ protected function assertEntityData($revisionable, $translatable) {
    *   Whether the entity type is revisionable or not.
    * @param bool $translatable
    *   Whether the entity type is translatable or not.
+   * @param bool $new_base_field
+   *   (optional) Whether a new base field was added as part of the update.
+   *   Defaults to FALSE.
    */
-  protected function assertEntityTypeSchema($revisionable, $translatable) {
+  protected function assertEntityTypeSchema($revisionable, $translatable, $new_base_field = FALSE) {
+    // Check whether the 'new_base_field' field has been installed correctly.
+    $field_storage_definition = $this->entityDefinitionUpdateManager->getFieldStorageDefinition('new_base_field', $this->entityTypeId);
+    if ($new_base_field) {
+      $this->assertNotNull($field_storage_definition);
+    }
+    else {
+      $this->assertNull($field_storage_definition);
+    }
+
     if ($revisionable && $translatable) {
       $this->assertRevisionableAndTranslatable();
     }
-- 
GitLab