diff --git a/core/core.services.yml b/core/core.services.yml
index 72d7d42..7f9ed9b 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -287,13 +287,13 @@ services:
     arguments: ['@container.namespaces', '@cache.discovery', '@module_handler']
   module_handler:
     class: Drupal\Core\Extension\ModuleHandler
-    arguments: ['%container.modules%', '@cache.bootstrap']
+    arguments: ['%container.modules%', '@kernel', '@cache.bootstrap']
   theme_handler:
     class: Drupal\Core\Extension\ThemeHandler
     arguments: ['@config.factory', '@module_handler', '@state', '@info_parser', '@logger.channel.default', '@asset.css.collection_optimizer', '@config.installer', '@config.manager', '@router.builder_indicator']
   entity.manager:
     class: Drupal\Core\Entity\EntityManager
-    arguments: ['@container.namespaces', '@module_handler', '@cache.discovery', '@language_manager', '@string_translation', '@class_resolver', '@typed_data_manager', '@entity.definitions.installed']
+    arguments: ['@container.namespaces', '@module_handler', '@cache.discovery', '@language_manager', '@string_translation', '@class_resolver', '@typed_data_manager', '@entity.definitions.installed', '@event_dispatcher']
     parent: container.trait
     tags:
       - { name: plugin_manager_cache_clear }
diff --git a/core/lib/Drupal/Core/DrupalKernelInterface.php b/core/lib/Drupal/Core/DrupalKernelInterface.php
index ca7af2b..a5eb3b9 100644
--- a/core/lib/Drupal/Core/DrupalKernelInterface.php
+++ b/core/lib/Drupal/Core/DrupalKernelInterface.php
@@ -52,7 +52,8 @@ public function getServiceProviders($origin);
   /**
    * Gets the current container.
    *
-   * @return ContainerInterface A ContainerInterface instance
+   * @return \Symfony\Component\DependencyInjection\ContainerInterface
+   *   A ContainerInterface instance.
    */
   public function getContainer();
 
diff --git a/core/lib/Drupal/Core/Entity/EntityDefinitionUpdateManager.php b/core/lib/Drupal/Core/Entity/EntityDefinitionUpdateManager.php
index 4f92c8a..197e4a4 100644
--- a/core/lib/Drupal/Core/Entity/EntityDefinitionUpdateManager.php
+++ b/core/lib/Drupal/Core/Entity/EntityDefinitionUpdateManager.php
@@ -7,8 +7,8 @@
 
 namespace Drupal\Core\Entity;
 
-use Drupal\Core\Entity\Schema\EntityStorageSchemaInterface;
 use Drupal\Core\Entity\Schema\DynamicallyFieldableEntityStorageSchemaInterface;
+use Drupal\Core\Entity\Schema\EntityStorageSchemaInterface;
 use Drupal\Core\Field\FieldStorageDefinitionInterface;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
 
diff --git a/core/lib/Drupal/Core/Entity/EntityManager.php b/core/lib/Drupal/Core/Entity/EntityManager.php
index 3226239..128d21d 100644
--- a/core/lib/Drupal/Core/Entity/EntityManager.php
+++ b/core/lib/Drupal/Core/Entity/EntityManager.php
@@ -10,25 +10,28 @@
 use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
 use Drupal\Component\Plugin\Exception\PluginNotFoundException;
 use Drupal\Component\Utility\String;
-use Drupal\Core\DependencyInjection\ClassResolverInterface;
-use Drupal\Core\Field\BaseFieldDefinition;
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\DependencyInjection\ClassResolverInterface;
 use Drupal\Core\Entity\Exception\AmbiguousEntityClassException;
 use Drupal\Core\Entity\Exception\NoCorrespondingEntityClassException;
 use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Field\BaseFieldDefinition;
+use Drupal\Core\Field\FieldStorageDefinitionEvent;
+use Drupal\Core\Field\FieldStorageDefinitionEvents;
 use Drupal\Core\Field\FieldStorageDefinitionInterface;
 use Drupal\Core\Field\FieldStorageDefinitionListenerInterface;
-use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\Core\KeyValueStore\KeyValueStoreInterface;
 use Drupal\Core\Language\LanguageInterface;
+use Drupal\Core\Language\LanguageManagerInterface;
 use Drupal\Core\Plugin\DefaultPluginManager;
-use Drupal\Core\KeyValueStore\KeyValueStoreInterface;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
 use Drupal\Core\StringTranslation\TranslationInterface;
 use Drupal\Core\TypedData\TranslatableInterface;
 use Drupal\Core\TypedData\TypedDataManager;
 use Symfony\Component\DependencyInjection\ContainerAwareInterface;
 use Symfony\Component\DependencyInjection\ContainerAwareTrait;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
 
 /**
  * Manages entity type plugin definitions.
@@ -123,6 +126,13 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa
   protected $installedDefinitions;
 
   /**
+   * The event dispatcher.
+   *
+   * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
+   */
+  protected $eventDispatcher;
+
+  /**
    * Static cache of bundle information.
    *
    * @var array
@@ -183,8 +193,10 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa
    *   The typed data manager.
    * @param \Drupal\Core\KeyValueStore\KeyValueStoreInterface $installed_definitions
    *   The keyvalue collection for tracking installed definitions.
+   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
+   *   The event dispatcher.
    */
-  public function __construct(\Traversable $namespaces, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache, LanguageManagerInterface $language_manager, TranslationInterface $translation_manager, ClassResolverInterface $class_resolver, TypedDataManager $typed_data_manager, KeyValueStoreInterface $installed_definitions) {
+  public function __construct(\Traversable $namespaces, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache, LanguageManagerInterface $language_manager, TranslationInterface $translation_manager, ClassResolverInterface $class_resolver, TypedDataManager $typed_data_manager, KeyValueStoreInterface $installed_definitions, EventDispatcherInterface $event_dispatcher) {
     parent::__construct('Entity', $namespaces, $module_handler, 'Drupal\Core\Entity\EntityInterface', 'Drupal\Core\Entity\Annotation\EntityType');
 
     $this->setCacheBackend($cache, 'entity_type', array('entity_types'));
@@ -195,6 +207,7 @@ public function __construct(\Traversable $namespaces, ModuleHandlerInterface $mo
     $this->classResolver = $class_resolver;
     $this->typedDataManager = $typed_data_manager;
     $this->installedDefinitions = $installed_definitions;
+    $this->eventDispatcher = $event_dispatcher;
   }
 
   /**
@@ -986,6 +999,8 @@ public function onEntityTypeCreate(EntityTypeInterface $entity_type) {
       $storage->onEntityTypeCreate($entity_type);
     }
 
+    $this->eventDispatcher->dispatch(EntityTypeEvents::CREATE, new EntityTypeEvent($entity_type));
+
     $this->setLastInstalledDefinition($entity_type);
     if ($entity_type->isSubclassOf('\Drupal\Core\Entity\FieldableEntityInterface')) {
       $this->setLastInstalledFieldStorageDefinitions($entity_type_id, $this->getFieldStorageDefinitions($entity_type_id));
@@ -1005,6 +1020,8 @@ public function onEntityTypeUpdate(EntityTypeInterface $entity_type, EntityTypeI
       $storage->onEntityTypeUpdate($entity_type, $original);
     }
 
+    $this->eventDispatcher->dispatch(EntityTypeEvents::UPDATE, new EntityTypeEvent($entity_type, $original));
+
     $this->setLastInstalledDefinition($entity_type);
   }
 
@@ -1021,6 +1038,8 @@ public function onEntityTypeDelete(EntityTypeInterface $entity_type) {
       $storage->onEntityTypeDelete($entity_type);
     }
 
+    $this->eventDispatcher->dispatch(EntityTypeEvents::DELETE, new EntityTypeEvent($entity_type));
+
     $this->deleteLastInstalledDefinition($entity_type_id);
   }
 
@@ -1037,6 +1056,8 @@ public function onFieldStorageDefinitionCreate(FieldStorageDefinitionInterface $
       $storage->onFieldStorageDefinitionCreate($storage_definition);
     }
 
+    $this->eventDispatcher->dispatch(FieldStorageDefinitionEvents::CREATE, new FieldStorageDefinitionEvent($storage_definition));
+
     $this->setLastInstalledFieldStorageDefinition($storage_definition);
     $this->clearCachedFieldDefinitions();
   }
@@ -1054,6 +1075,8 @@ public function onFieldStorageDefinitionUpdate(FieldStorageDefinitionInterface $
       $storage->onFieldStorageDefinitionUpdate($storage_definition, $original);
     }
 
+    $this->eventDispatcher->dispatch(FieldStorageDefinitionEvents::UPDATE, new FieldStorageDefinitionEvent($storage_definition, $original));
+
     $this->setLastInstalledFieldStorageDefinition($storage_definition);
     $this->clearCachedFieldDefinitions();
   }
@@ -1071,6 +1094,8 @@ public function onFieldStorageDefinitionDelete(FieldStorageDefinitionInterface $
       $storage->onFieldStorageDefinitionDelete($storage_definition);
     }
 
+    $this->eventDispatcher->dispatch(FieldStorageDefinitionEvents::DELETE, new FieldStorageDefinitionEvent($storage_definition));
+
     $this->deleteLastInstalledFieldStorageDefinition($storage_definition);
     $this->clearCachedFieldDefinitions();
   }
diff --git a/core/lib/Drupal/Core/Entity/EntityTypeEvent.php b/core/lib/Drupal/Core/Entity/EntityTypeEvent.php
new file mode 100644
index 0000000..baaffb5
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/EntityTypeEvent.php
@@ -0,0 +1,63 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\EntityTypeEvent.
+ */
+
+namespace Drupal\Core\Entity;
+
+use Symfony\Component\EventDispatcher\GenericEvent;
+
+/**
+ * Defines a base class for all entity type events.
+ */
+class EntityTypeEvent extends GenericEvent {
+
+  /**
+   * The entity type.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeInterface
+   */
+  protected $entityType;
+
+  /**
+   * The original entity type.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeInterface
+   */
+  protected $original;
+
+  /**
+   * Constructs a new EntityTypeEvent.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+   *   The field storage definition.
+   * @param \Drupal\Core\Entity\EntityTypeInterface $original
+   *   (optional) The original entity type. This should be passed only when
+   *   updating the entity type.
+   */
+  public function __construct(EntityTypeInterface $entity_type, EntityTypeInterface $original = NULL) {
+    $this->entityType = $entity_type;
+    $this->original = $original;
+  }
+
+  /**
+   * The entity type the event refers to.
+   *
+   * @return \Drupal\Core\Entity\EntityTypeInterface
+   */
+  public function getEntityType() {
+    return $this->entityType;
+  }
+
+  /**
+   * The original entity type.
+   *
+   * @return \Drupal\Core\Entity\EntityTypeInterface
+   */
+  public function getOriginal() {
+    return $this->original;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Entity/EntityTypeEventSubscriberTrait.php b/core/lib/Drupal/Core/Entity/EntityTypeEventSubscriberTrait.php
new file mode 100644
index 0000000..3b579a1
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/EntityTypeEventSubscriberTrait.php
@@ -0,0 +1,76 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\EntityTypeEventSubscriberTrait.
+ */
+
+namespace Drupal\Core\Entity;
+
+/**
+ * Helper methods for EntityTypeListenerInterface.
+ *
+ * This allows a class implementing EntityTypeListenerInterface to subscribe and
+ * react to entity type events.
+ *
+ * @see \Symfony\Component\EventDispatcher\EventSubscriberInterface
+ * @see \Drupal\Core\Entity\EntityTypeListenerInterface
+ */
+trait EntityTypeEventSubscriberTrait {
+
+  /**
+   * Returns the subscribed events.
+   *
+   * @return array
+   *   An array of subscribed event names.
+   *
+   * @see \Symfony\Component\EventDispatcher\EventSubscriberInterface::getSubscribedEvents()
+   */
+  public static function getEntityTypeEvents() {
+    $event = array('onEntityTypeEvent', 100);
+    $events[EntityTypeEvents::CREATE][] = $event;
+    $events[EntityTypeEvents::UPDATE][] = $event;
+    $events[EntityTypeEvents::DELETE][] = $event;
+    return $events;
+  }
+
+  /**
+   * Listener method for any entity type definition event.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeEvent $event
+   *   The field storage definition event object.
+   * @param string $event_name
+   *   The event name.
+   */
+  public function onEntityTypeEvent(EntityTypeEvent $event, $event_name) {
+    switch ($event_name) {
+      case EntityTypeEvents::CREATE:
+        $this->onEntityTypeCreate($event->getEntityType());
+        break;
+
+      case EntityTypeEvents::UPDATE:
+        $this->onEntityTypeUpdate($event->getEntityType(), $event->getOriginal());
+        break;
+
+      case EntityTypeEvents::DELETE:
+        $this->onEntityTypeDelete($event->getEntityType());
+        break;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  abstract public function onEntityTypeCreate(EntityTypeInterface $entity_type);
+
+  /**
+   * {@inheritdoc}
+   */
+  abstract public function onEntityTypeUpdate(EntityTypeInterface $entity_type, EntityTypeInterface $original);
+
+  /**
+   * {@inheritdoc}
+   */
+  abstract public function onEntityTypeDelete(EntityTypeInterface $entity_type);
+
+}
diff --git a/core/lib/Drupal/Core/Entity/EntityTypeEvents.php b/core/lib/Drupal/Core/Entity/EntityTypeEvents.php
new file mode 100644
index 0000000..39dc080
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/EntityTypeEvents.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\EntityTypeEvents.
+ */
+
+namespace Drupal\Core\Entity;
+
+/**
+ * Contains all events thrown while handling entity types.
+ */
+final class EntityTypeEvents {
+
+  /**
+   * Event name for entity type creation.
+   *
+   * @var string
+   */
+  const CREATE = 'entity_type.definition.create';
+
+  /**
+   * Event name for entity type update.
+   *
+   * @var string
+   */
+  const UPDATE = 'entity_type.definition.update';
+
+  /**
+   * Event name for entity type deletion.
+   *
+   * @var string
+   */
+  const DELETE = 'entity_type.definition.delete';
+
+}
diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php
index 035e75f..028d7d4 100644
--- a/core/lib/Drupal/Core/Extension/ModuleHandler.php
+++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php
@@ -12,7 +12,7 @@
 use Drupal\Component\Utility\NestedArray;
 use Drupal\Component\Utility\String;
 use Drupal\Core\Cache\CacheBackendInterface;
-use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\Core\DrupalKernelInterface;
 
 /**
  * Class that manages modules in a Drupal installation.
@@ -65,6 +65,13 @@ class ModuleHandler implements ModuleHandlerInterface {
   protected $hookInfo;
 
   /**
+   * The drupal kernel.
+   *
+   * @var \Drupal\Core\DrupalKernelInterface
+   */
+  protected $kernel;
+
+  /**
    * Cache backend for storing module hook implementation information.
    *
    * @var \Drupal\Core\Cache\CacheBackendInterface
@@ -92,17 +99,20 @@ class ModuleHandler implements ModuleHandlerInterface {
    *   An associative array whose keys are the names of installed modules and
    *   whose values are Extension class parameters. This is normally the
    *   %container.modules% parameter being set up by DrupalKernel.
+   * @param \Drupal\Core\DrupalKernelInterface $kernel
+   *   The drupal kernel.
    * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
    *   Cache backend for storing module hook implementation information.
    *
    * @see \Drupal\Core\DrupalKernel
    * @see \Drupal\Core\CoreServiceProvider
    */
-  public function __construct(array $module_list = array(), CacheBackendInterface $cache_backend) {
+  public function __construct(array $module_list = array(), DrupalKernelInterface $kernel, CacheBackendInterface $cache_backend) {
     $this->moduleList = array();
     foreach ($module_list as $name => $module) {
       $this->moduleList[$name] = new Extension($module['type'], $module['pathname'], $module['filename']);
     }
+    $this->kernel = $kernel;
     $this->cacheBackend = $cache_backend;
   }
 
@@ -792,14 +802,7 @@ public function install(array $module_list, $enable_dependencies = TRUE) {
         drupal_static_reset('system_rebuild_module_data');
 
         // Update the kernel to include it.
-        // This reboots the kernel to register the module's bundle and its
-        // services in the service container. The $module_filenames argument is
-        // taken over as %container.modules% parameter, which is passed to a
-        // fresh ModuleHandler instance upon first retrieval.
-        // @todo install_begin_request() creates a container without a kernel.
-        if ($kernel = \Drupal::service('kernel', ContainerInterface::NULL_ON_INVALID_REFERENCE)) {
-          $kernel->updateModules($module_filenames, $module_filenames);
-        }
+        $this->updateKernel($module_filenames);
 
         // Refresh the schema to include it.
         drupal_get_schema(NULL, TRUE);
@@ -822,10 +825,10 @@ public function install(array $module_list, $enable_dependencies = TRUE) {
           $version = max(max($versions), $version);
         }
 
-        // Notify the entity manager that this module's entity types are new,
-        // so that it can notify all interested handlers. For example, a
-        // SQL-based storage handler can use this as an opportunity to create
-        // the necessary database tables.
+        // Notify interested components that this module's entity types are new.
+        // For example, a SQL-based storage handler can use this as an
+        // opportunity to create the necessary database tables.
+        // @todo Clean this up in https://www.drupal.org/node/2350111.
         $entity_manager = \Drupal::entityManager();
         foreach ($entity_manager->getDefinitions() as $entity_type) {
           if ($entity_type->getProvider() == $module) {
@@ -950,6 +953,7 @@ public function uninstall(array $module_list, $uninstall_dependents = TRUE) {
 
       // Clean up all entity bundles (including fields) of every entity type
       // provided by the module that is being uninstalled.
+      // @todo Clean this up in https://www.drupal.org/node/2350111.
       foreach ($entity_manager->getDefinitions() as $entity_type_id => $entity_type) {
         if ($entity_type->getProvider() == $module) {
           foreach (array_keys($entity_manager->getBundleInfo($entity_type_id)) as $bundle) {
@@ -968,10 +972,10 @@ public function uninstall(array $module_list, $uninstall_dependents = TRUE) {
       // Remove all configuration belonging to the module.
       \Drupal::service('config.manager')->uninstall('module', $module);
 
-      // Notify the entity manager that this module's entity types are being
-      // deleted, so that it can notify all interested handlers. For example,
-      // a SQL-based storage handler can use this as an opportunity to drop
-      // the corresponding database tables.
+      // Notify interested components that this module's entity types are being
+      // deleted. For example, a SQL-based storage handler can use this as an
+      // opportunity to drop the corresponding database tables.
+      // @todo Clean this up in https://www.drupal.org/node/2350111.
       foreach ($entity_manager->getDefinitions() as $entity_type) {
         if ($entity_type->getProvider() == $module) {
           $entity_manager->onEntityTypeDelete($entity_type);
@@ -1004,7 +1008,7 @@ public function uninstall(array $module_list, $uninstall_dependents = TRUE) {
       \Drupal::service('router.builder_indicator')->setRebuildNeeded();
 
       // Update the kernel to exclude the uninstalled modules.
-      \Drupal::service('kernel')->updateModules($module_filenames, $module_filenames);
+      $this->updateKernel($module_filenames);
 
       // Update the theme registry to remove the newly uninstalled module.
       drupal_theme_rebuild();
@@ -1084,4 +1088,23 @@ public function getName($module) {
     $module_data = system_rebuild_module_data();
     return $module_data[$module]->info['name'];
   }
+
+  /**
+   * Updates the kernel module list.
+   *
+   * @param string $module_filenames
+   *   The list of installed modules.
+   */
+  protected function updateKernel($module_filenames) {
+    // This reboots the kernel to register the module's bundle and its services
+    // in the service container. The $module_filenames argument is taken over as
+    // %container.modules% parameter, which is passed to a fresh ModuleHandler
+    // instance upon first retrieval.
+    $this->kernel->updateModules($module_filenames, $module_filenames);
+    // After rebuilding the container we need to update the injected
+    // dependencies.
+    $container = $this->kernel->getContainer();
+    $this->cacheBackend = $container->get('cache.bootstrap');
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Field/FieldStorageDefinitionEvent.php b/core/lib/Drupal/Core/Field/FieldStorageDefinitionEvent.php
new file mode 100644
index 0000000..325780c
--- /dev/null
+++ b/core/lib/Drupal/Core/Field/FieldStorageDefinitionEvent.php
@@ -0,0 +1,63 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Field\FieldStorageDefinitionEvent.
+ */
+
+namespace Drupal\Core\Field;
+
+use Symfony\Component\EventDispatcher\GenericEvent;
+
+/**
+ * Defines a base class for all field storage definition events.
+ */
+class FieldStorageDefinitionEvent extends GenericEvent {
+
+  /**
+   * The field storage definition.
+   *
+   * @var \Drupal\Core\Field\FieldStorageDefinitionInterface
+   */
+  protected $fieldStorageDefinition;
+
+  /**
+   * The original field storage definition.
+   *
+   * @var \Drupal\Core\Field\FieldStorageDefinitionInterface
+   */
+  protected $original;
+
+  /**
+   * Constructs a new FieldStorageDefinitionEvent.
+   *
+   * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $field_storage_definition
+   *   The field storage definition.
+   * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $original
+   *   (optional) The original field storage definition. This should be passed
+   *   only when updating the storage definition.
+   */
+  public function __construct(FieldStorageDefinitionInterface $field_storage_definition, FieldStorageDefinitionInterface $original = NULL) {
+    $this->fieldStorageDefinition = $field_storage_definition;
+    $this->original = $original;
+  }
+
+  /**
+   * The field storage definition.
+   *
+   * @return \Drupal\Core\Field\FieldStorageDefinitionInterface
+   */
+  public function getFieldStorageDefinition() {
+    return $this->fieldStorageDefinition;
+  }
+
+  /**
+   * The original field storage definition.
+   *
+   * @return \Drupal\Core\Field\FieldStorageDefinitionInterface
+   */
+  public function getOriginal() {
+    return $this->original;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Field/FieldStorageDefinitionEventSubscriberTrait.php b/core/lib/Drupal/Core/Field/FieldStorageDefinitionEventSubscriberTrait.php
new file mode 100644
index 0000000..2947564
--- /dev/null
+++ b/core/lib/Drupal/Core/Field/FieldStorageDefinitionEventSubscriberTrait.php
@@ -0,0 +1,76 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Field\FieldStorageDefinitionEventSubscriberTrait.
+ */
+
+namespace Drupal\Core\Field;
+
+/**
+ * Helper methods for FieldStorageDefinitionListenerInterface.
+ *
+ * This allows a class implementing FieldStorageDefinitionListenerInterface to
+ * subscribe and react to field storage definition events.
+ *
+ * @see \Symfony\Component\EventDispatcher\EventSubscriberInterface
+ * @see \Drupal\Core\Field\FieldStorageDefinitionListenerInterface
+ */
+trait FieldStorageDefinitionEventSubscriberTrait {
+
+  /**
+   * Returns the subscribed events.
+   *
+   * @return array
+   *   An array of subscribed event names.
+   *
+   * @see \Symfony\Component\EventDispatcher\EventSubscriberInterface::getSubscribedEvents()
+   */
+  public static function getFieldStorageDefinitionEvents() {
+    $event = array('onFieldStorageDefinitionEvent', 100);
+    $events[FieldStorageDefinitionEvents::CREATE][] = $event;
+    $events[FieldStorageDefinitionEvents::UPDATE][] = $event;
+    $events[FieldStorageDefinitionEvents::DELETE][] = $event;
+    return $events;
+  }
+
+  /**
+   * Listener method for any field storage definition event.
+   *
+   * @param \Drupal\Core\Field\FieldStorageDefinitionEvent $event
+   *   The field storage definition event object.
+   * @param string $event_name
+   *   The event name.
+   */
+  public function onFieldStorageDefinitionEvent(FieldStorageDefinitionEvent $event, $event_name) {
+    switch ($event_name) {
+      case FieldStorageDefinitionEvents::CREATE:
+        $this->onFieldStorageDefinitionCreate($event->getFieldStorageDefinition());
+        break;
+
+      case FieldStorageDefinitionEvents::UPDATE:
+        $this->onFieldStorageDefinitionUpdate($event->getFieldStorageDefinition(), $event->getOriginal());
+        break;
+
+      case FieldStorageDefinitionEvents::DELETE:
+        $this->onFieldStorageDefinitionDelete($event->getFieldStorageDefinition());
+        break;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  abstract public function onFieldStorageDefinitionCreate(FieldStorageDefinitionInterface $storage_definition);
+
+  /**
+   * {@inheritdoc}
+   */
+  abstract public function onFieldStorageDefinitionUpdate(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original);
+
+  /**
+   * {@inheritdoc}
+   */
+  abstract public function onFieldStorageDefinitionDelete(FieldStorageDefinitionInterface $storage_definition);
+
+}
diff --git a/core/lib/Drupal/Core/Field/FieldStorageDefinitionEvents.php b/core/lib/Drupal/Core/Field/FieldStorageDefinitionEvents.php
new file mode 100644
index 0000000..ea17ec6
--- /dev/null
+++ b/core/lib/Drupal/Core/Field/FieldStorageDefinitionEvents.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Field\FieldStorageDefinitionEvent.
+ */
+
+namespace Drupal\Core\Field;
+
+/**
+ * Contains all events thrown while handling field storage definitions.
+ */
+final class FieldStorageDefinitionEvents {
+
+  /**
+   * Event name for field storage definition creation.
+   *
+   * @var string
+   */
+  const CREATE = 'field_storage.definition.create';
+
+  /**
+   * Event name for field storage definition update.
+   *
+   * @var string
+   */
+  const UPDATE = 'field_storage.definition.update';
+
+  /**
+   * Event name for field storage definition deletion.
+   *
+   * @var string
+   */
+  const DELETE = 'field_storage.definition.delete';
+
+}
diff --git a/core/lib/Drupal/Core/Plugin/DefaultPluginBag.php b/core/lib/Drupal/Core/Plugin/DefaultPluginBag.php
index 9287d7c..de410d8 100644
--- a/core/lib/Drupal/Core/Plugin/DefaultPluginBag.php
+++ b/core/lib/Drupal/Core/Plugin/DefaultPluginBag.php
@@ -11,6 +11,7 @@
 use Drupal\Component\Plugin\PluginBag;
 use Drupal\Component\Plugin\PluginManagerInterface;
 use Drupal\Component\Plugin\ConfigurablePluginInterface;
+use Drupal\Core\DependencyInjection\DependencySerializationTrait;
 
 /**
  * Provides a default plugin bag for a plugin type.
@@ -20,6 +21,7 @@
  * key containing the plugin ID is specified by self::$pluginKey.
  */
 class DefaultPluginBag extends PluginBag {
+  use DependencySerializationTrait;
 
   /**
    * The manager used to instantiate the plugins.
diff --git a/core/lib/Drupal/Core/Plugin/DefaultSinglePluginBag.php b/core/lib/Drupal/Core/Plugin/DefaultSinglePluginBag.php
index 2108ce1..575db9e 100644
--- a/core/lib/Drupal/Core/Plugin/DefaultSinglePluginBag.php
+++ b/core/lib/Drupal/Core/Plugin/DefaultSinglePluginBag.php
@@ -10,6 +10,7 @@
 use Drupal\Component\Plugin\PluginManagerInterface;
 use Drupal\Component\Plugin\PluginBag;
 use Drupal\Component\Plugin\ConfigurablePluginInterface;
+use Drupal\Core\DependencyInjection\DependencySerializationTrait;
 
 /**
  * Provides a default plugin bag for a plugin type.
@@ -21,6 +22,7 @@
  * self::initializePlugin().
  */
 class DefaultSinglePluginBag extends PluginBag {
+  use DependencySerializationTrait;
 
   /**
    * The manager used to instantiate the plugins.
diff --git a/core/modules/system/src/Tests/Entity/EntityDefinitionUpdateTest.php b/core/modules/system/src/Tests/Entity/EntityDefinitionUpdateTest.php
index 752a1f3..96c7583 100644
--- a/core/modules/system/src/Tests/Entity/EntityDefinitionUpdateTest.php
+++ b/core/modules/system/src/Tests/Entity/EntityDefinitionUpdateTest.php
@@ -8,8 +8,10 @@
 namespace Drupal\system\Tests\Entity;
 
 use Drupal\Core\Entity\EntityStorageException;
+use Drupal\Core\Entity\EntityTypeEvents;
 use Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException;
 use Drupal\Core\Field\BaseFieldDefinition;
+use Drupal\Core\Field\FieldStorageDefinitionEvents;
 use Drupal\entity_test\FieldStorageDefinition;
 
 /**
@@ -443,6 +445,39 @@ public function testEntityIndexCreateWithData() {
   }
 
   /**
+   * Tests entity type and field storage definition events.
+   */
+  public function testDefinitionEvents() {
+    /** @var \Drupal\entity_test\EntityTestDefinitionSubscriber $event_subscriber */
+    $event_subscriber = $this->container->get('entity_test.definition.subscriber');
+    $event_subscriber->enableEventTracking();
+
+    // Test field storage definition events.
+    $storage_definition = current($this->entityManager->getFieldStorageDefinitions('entity_test_rev'));
+    $this->assertFalse($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::DELETE), 'Entity type delete was not dispatched yet.');
+    $this->entityManager->onFieldStorageDefinitionDelete($storage_definition);
+    $this->assertTrue($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::DELETE), 'Entity type delete event successfully dispatched.');
+    $this->assertFalse($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::CREATE), 'Entity type create was not dispatched yet.');
+    $this->entityManager->onFieldStorageDefinitionCreate($storage_definition);
+    $this->assertTrue($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::CREATE), 'Entity type create event successfully dispatched.');
+    $this->assertFalse($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::UPDATE), 'Entity type update was not dispatched yet.');
+    $this->entityManager->onFieldStorageDefinitionUpdate($storage_definition, $storage_definition);
+    $this->assertTrue($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::UPDATE), 'Entity type update event successfully dispatched.');
+
+    // Test entity type events.
+    $entity_type = $this->entityManager->getDefinition('entity_test_rev');
+    $this->assertFalse($event_subscriber->hasEventFired(EntityTypeEvents::CREATE), 'Entity type create was not dispatched yet.');
+    $this->entityManager->onEntityTypeCreate($entity_type);
+    $this->assertTrue($event_subscriber->hasEventFired(EntityTypeEvents::CREATE), 'Entity type create event successfully dispatched.');
+    $this->assertFalse($event_subscriber->hasEventFired(EntityTypeEvents::UPDATE), 'Entity type update was not dispatched yet.');
+    $this->entityManager->onEntityTypeUpdate($entity_type, $entity_type);
+    $this->assertTrue($event_subscriber->hasEventFired(EntityTypeEvents::UPDATE), 'Entity type update event successfully dispatched.');
+    $this->assertFalse($event_subscriber->hasEventFired(EntityTypeEvents::DELETE), 'Entity type delete was not dispatched yet.');
+    $this->entityManager->onEntityTypeDelete($entity_type);
+    $this->assertTrue($event_subscriber->hasEventFired(EntityTypeEvents::DELETE), 'Entity type delete event successfully dispatched.');
+  }
+
+  /**
    * Updates the 'entity_test_update' entity type to revisionable.
    */
   protected function updateEntityTypeToRevisionable() {
diff --git a/core/modules/system/src/Tests/Plugin/PluginTestBase.php b/core/modules/system/src/Tests/Plugin/PluginTestBase.php
index 3391c73..0285312 100644
--- a/core/modules/system/src/Tests/Plugin/PluginTestBase.php
+++ b/core/modules/system/src/Tests/Plugin/PluginTestBase.php
@@ -47,7 +47,7 @@ protected function setUp() {
     //   as derivatives and ReflectionFactory.
     $this->testPluginManager = new TestPluginManager();
     $this->mockBlockManager = new MockBlockManager();
-    $module_handler = new ModuleHandler(array(), new MemoryBackend('plugin'));
+    $module_handler = new ModuleHandler(array(), $this->container->get('kernel'), new MemoryBackend('plugin'), $this->container->get('event_dispatcher'));
     $this->defaultsTestPluginManager = new DefaultsTestPluginManager($module_handler);
 
     // The expected plugin definitions within each manager. Several tests assert
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
new file mode 100644
index 0000000..8769fbc
--- /dev/null
+++ b/core/modules/system/tests/modules/entity_test/entity_test.services.yml
@@ -0,0 +1,6 @@
+services:
+  entity_test.definition.subscriber:
+    class: Drupal\entity_test\EntityTestDefinitionSubscriber
+    arguments: ['@state']
+    tags:
+      - { name: event_subscriber }
diff --git a/core/modules/system/tests/modules/entity_test/src/EntityTestDefinitionSubscriber.php b/core/modules/system/tests/modules/entity_test/src/EntityTestDefinitionSubscriber.php
new file mode 100644
index 0000000..298f5c3
--- /dev/null
+++ b/core/modules/system/tests/modules/entity_test/src/EntityTestDefinitionSubscriber.php
@@ -0,0 +1,131 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\entity_test\EntityTestDefinitionSubscriber.
+ */
+
+namespace Drupal\entity_test;
+
+use Drupal\Core\Entity\EntityTypeEvents;
+use Drupal\Core\Entity\EntityTypeEventSubscriberTrait;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\EntityTypeListenerInterface;
+use Drupal\Core\Field\FieldStorageDefinitionEvents;
+use Drupal\Core\Field\FieldStorageDefinitionEventSubscriberTrait;
+use Drupal\Core\Field\FieldStorageDefinitionInterface;
+use Drupal\Core\Field\FieldStorageDefinitionListenerInterface;
+use Drupal\Core\State\StateInterface;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Test entity type and field storage definition event subscriber.
+ */
+class EntityTestDefinitionSubscriber implements EventSubscriberInterface, EntityTypeListenerInterface, FieldStorageDefinitionListenerInterface {
+
+  use EntityTypeEventSubscriberTrait;
+  use FieldStorageDefinitionEventSubscriberTrait;
+
+  /**
+   * The state service.
+   *
+   * @var \Drupal\Core\State\StateInterface
+   */
+  protected $state;
+
+  /**
+   * Flag determining whether events should be tracked.
+   *
+   * @var bool
+   */
+  protected $trackEvents = FALSE;
+
+  /**
+   * {@inheritdoc}
+   */
+  function __construct(StateInterface $state) {
+    $this->state = $state;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents() {
+    return static::getEntityTypeEvents() + static::getFieldStorageDefinitionEvents();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function onEntityTypeCreate(EntityTypeInterface $entity_type) {
+    $this->storeEvent(EntityTypeEvents::CREATE);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function onEntityTypeUpdate(EntityTypeInterface $entity_type, EntityTypeInterface $original) {
+    $this->storeEvent(EntityTypeEvents::UPDATE);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function onEntityTypeDelete(EntityTypeInterface $entity_type) {
+    $this->storeEvent(EntityTypeEvents::DELETE);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function onFieldStorageDefinitionCreate(FieldStorageDefinitionInterface $storage_definition) {
+    $this->storeEvent(FieldStorageDefinitionEvents::CREATE);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function onFieldStorageDefinitionUpdate(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) {
+    $this->storeEvent(FieldStorageDefinitionEvents::UPDATE);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function onFieldStorageDefinitionDelete(FieldStorageDefinitionInterface $storage_definition) {
+    $this->storeEvent(FieldStorageDefinitionEvents::DELETE);
+  }
+
+  /**
+   * Enables event tracking.
+   */
+  public function enableEventTracking() {
+    $this->trackEvents = TRUE;
+  }
+
+  /**
+   * Checks whether an event has been dispatched.
+   *
+   * @param string $event_name
+   *   The event name.
+   *
+   * @return bool
+   *   TRUE if the event has been dispatched, FALSE otherwise.
+   */
+  public function hasEventFired($event_name) {
+    return (bool) $this->state->get($event_name);
+  }
+
+  /**
+   * Stores the specified event.
+   *
+   * @param string $event_name
+   *   The event name.
+   */
+  protected function storeEvent($event_name) {
+    if ($this->trackEvents) {
+      $this->state->set($event_name, TRUE);
+    }
+  }
+
+}
diff --git a/core/modules/update/src/UpdateManager.php b/core/modules/update/src/UpdateManager.php
index a1801fe..21cfe80 100644
--- a/core/modules/update/src/UpdateManager.php
+++ b/core/modules/update/src/UpdateManager.php
@@ -7,6 +7,7 @@
 namespace Drupal\update;
 
 use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\DependencyInjection\DependencySerializationTrait;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
 use Drupal\Core\StringTranslation\TranslationInterface;
@@ -17,6 +18,7 @@
  * Default implementation of UpdateManagerInterface.
  */
 class UpdateManager implements UpdateManagerInterface {
+  use DependencySerializationTrait;
   use StringTranslationTrait;
 
   /**
diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityManagerTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityManagerTest.php
index b933132..3b17502 100644
--- a/core/tests/Drupal/Tests/Core/Entity/EntityManagerTest.php
+++ b/core/tests/Drupal/Tests/Core/Entity/EntityManagerTest.php
@@ -101,6 +101,13 @@ class EntityManagerTest extends UnitTestCase {
   protected $installedDefinitions;
 
   /**
+   * The event dispatcher.
+   *
+   * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
+   */
+  protected $eventDispatcher;
+
+  /**
    * {@inheritdoc}
    */
   protected function setUp() {
@@ -140,6 +147,8 @@ protected function setUp() {
       ->disableOriginalConstructor()
       ->getMock();
 
+    $this->eventDispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
+
     $this->installedDefinitions = $this->getMock('Drupal\Core\KeyValueStore\KeyValueStoreInterface');
   }
 
@@ -171,7 +180,7 @@ protected function setUpEntityManager($definitions = array()) {
       ->method('getDefinitions')
       ->will($this->returnValue($definitions));
 
-    $this->entityManager = new TestEntityManager(new \ArrayObject(), $this->moduleHandler, $this->cache, $this->languageManager, $this->translationManager, $this->getClassResolverStub(), $this->typedDataManager, $this->installedDefinitions);
+    $this->entityManager = new TestEntityManager(new \ArrayObject(), $this->moduleHandler, $this->cache, $this->languageManager, $this->translationManager, $this->getClassResolverStub(), $this->typedDataManager, $this->installedDefinitions, $this->eventDispatcher);
     $this->entityManager->setContainer($this->container);
     $this->entityManager->setDiscovery($this->discovery);
   }
diff --git a/core/tests/Drupal/Tests/Core/Extension/ModuleHandlerTest.php b/core/tests/Drupal/Tests/Core/Extension/ModuleHandlerTest.php
index bc5da97..493e201 100644
--- a/core/tests/Drupal/Tests/Core/Extension/ModuleHandlerTest.php
+++ b/core/tests/Drupal/Tests/Core/Extension/ModuleHandlerTest.php
@@ -18,13 +18,19 @@
 class ModuleHandlerTest extends UnitTestCase {
 
   /**
+   * The mocked drupal kernel.
+   *
+   * @var \Drupal\Core\DrupalKernelInterface
+   */
+  protected $kernel;
+
+  /**
    * The mocked cache backend.
    *
    * @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit_Framework_MockObject_MockObject
    */
   protected $cacheBackend;
 
-
   /**
    * The tested module handler.
    *
@@ -38,6 +44,7 @@ class ModuleHandlerTest extends UnitTestCase {
    * @covers ::__construct
    */
   protected function setUp() {
+    $this->kernel = $this->getMock('Drupal\Core\DrupalKernelInterface');
     $this->cacheBackend = $this->getMock('Drupal\Core\Cache\CacheBackendInterface');
     $this->moduleHandler = new ModuleHandler(array(
       'module_handler_test' => array(
@@ -45,7 +52,7 @@ protected function setUp() {
         'pathname' => 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test/module_handler_test.info.yml',
         'filename' => 'module_handler_test.module',
       )
-    ), $this->cacheBackend);
+    ), $this->kernel, $this->cacheBackend);
   }
 
   /**
@@ -96,7 +103,7 @@ public function testModuleReloading() {
             'pathname' => 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test/module_handler_test.info.yml',
             'filename' => 'module_handler_test.module',
           )
-        ), $this->cacheBackend
+        ), $this->kernel, $this->cacheBackend
       ))
       ->setMethods(array('load'))
       ->getMock();
@@ -164,7 +171,7 @@ public function testGetModuleWithNonExistingModule() {
   public function testSetModuleList() {
     $module_handler = $this->getMockBuilder('Drupal\Core\Extension\ModuleHandler')
       ->setConstructorArgs(array(
-        array(), $this->cacheBackend
+        array(), $this->kernel, $this->cacheBackend
       ))
       ->setMethods(array('resetImplementations'))
       ->getMock();
@@ -192,7 +199,7 @@ public function testAddModule() {
 
     $module_handler = $this->getMockBuilder('Drupal\Core\Extension\ModuleHandler')
       ->setConstructorArgs(array(
-        array(), $this->cacheBackend
+        array(), $this->kernel, $this->cacheBackend
       ))
       ->setMethods(array('resetImplementations'))
       ->getMock();
@@ -214,7 +221,7 @@ public function testAddProfile() {
 
     $module_handler = $this->getMockBuilder('Drupal\Core\Extension\ModuleHandler')
       ->setConstructorArgs(array(
-        array(), $this->cacheBackend
+        array(), $this->kernel, $this->cacheBackend
       ))
       ->setMethods(array('resetImplementations'))
       ->getMock();
@@ -250,7 +257,7 @@ public function testLoadAllIncludes() {
             'pathname' => 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test/module_handler_test.info.yml',
             'filename' => 'module_handler_test.module',
           )
-        ), $this->cacheBackend
+        ), $this->kernel, $this->cacheBackend
       ))
       ->setMethods(array('loadInclude'))
       ->getMock();
@@ -331,7 +338,7 @@ public function testCachedGetImplementations() {
             'pathname' => 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test/module_handler_test.info.yml',
             'filename' => 'module_handler_test.module',
           )
-        ), $this->cacheBackend
+        ), $this->kernel, $this->cacheBackend
       ))
       ->setMethods(array('buildImplementationInfo', 'loadInclude'))
       ->getMock();
@@ -366,7 +373,7 @@ public function testCachedGetImplementationsMissingMethod() {
             'pathname' => 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test/module_handler_test.info.yml',
             'filename' => 'module_handler_test.module',
           )
-        ), $this->cacheBackend
+        ), $this->kernel, $this->cacheBackend
       ))
       ->setMethods(array('buildImplementationInfo'))
       ->getMock();
