diff --git a/core/core.services.yml b/core/core.services.yml
index ea1a56b..98ec055 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -472,10 +472,24 @@ services:
     arguments: ['@theme_handler', '@config.factory', '@config.installer', '@module_handler', '@config.manager', '@asset.css.collection_optimizer', '@router.builder', '@logger.channel.default', '@state']
   entity.manager:
     class: Drupal\Core\Entity\EntityManager
-    arguments: ['@container.namespaces', '@module_handler', '@cache.discovery', '@language_manager', '@string_translation', '@class_resolver', '@typed_data_manager', '@keyvalue', '@event_dispatcher']
+    arguments: ['@module_handler', '@cache.discovery', '@language_manager', '@typed_data_manager', '@keyvalue', '@event_dispatcher', '@entity_type.manager', '@entity_type.repository', '@entity_type.bundle.manager', '@entity_display.manager']
+    tags:
+      - { name: plugin_manager_cache_clear }
+  entity_type.manager:
+    class: Drupal\Core\Entity\EntityTypeManager
+    arguments: ['@container.namespaces', '@module_handler', '@cache.discovery', '@string_translation', '@class_resolver']
     parent: container.trait
     tags:
       - { name: plugin_manager_cache_clear }
+  entity_type.repository:
+    class: Drupal\Core\Entity\EntityTypeRepository
+    arguments: ['@entity_type.manager']
+  entity_type.bundle.manager:
+    class: Drupal\Core\Entity\EntityTypeBundleManager
+    arguments: ['@entity_type.manager', '@language_manager', '@module_handler', '@typed_data_manager', '@cache.discovery']
+  entity_display.manager:
+    class: Drupal\Core\Entity\EntityDisplayManager
+    arguments: ['@entity_type.manager', '@module_handler', '@cache.discovery', '@language_manager']
   entity_route_subscriber:
     class: Drupal\Core\EventSubscriber\EntityRouteProviderSubscriber
     arguments: ['@entity.manager']
diff --git a/core/lib/Drupal/Core/Entity/EntityDisplayManager.php b/core/lib/Drupal/Core/Entity/EntityDisplayManager.php
new file mode 100644
index 0000000..0563fa8
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/EntityDisplayManager.php
@@ -0,0 +1,198 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\EntityDisplayManager.
+ */
+
+namespace Drupal\Core\Entity;
+
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Language\LanguageInterface;
+use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\Core\Plugin\Discovery\CacheBackendTrait;
+
+/**
+ * @todo.
+ */
+class EntityDisplayManager implements EntityDisplayManagerInterface {
+
+  use CacheBackendTrait;
+
+  /**
+   * Static cache of display modes information.
+   *
+   * @var array
+   */
+  protected $displayModeInfo = [];
+
+  /**
+   * The language manager.
+   *
+   * @var \Drupal\Core\Language\LanguageManagerInterface
+   */
+  protected $languageManager;
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * The module handler.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * Constructs a new EntityDisplayManager.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler.
+   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
+   *   The cache backend.
+   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
+   *   The language manager.
+   */
+  public function __construct(EntityTypeManagerInterface $entity_type_manager, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache_backend, LanguageManagerInterface $language_manager) {
+    $this->entityTypeManager = $entity_type_manager;
+    $this->moduleHandler = $module_handler;
+    $this->cacheBackend = $cache_backend;
+    $this->languageManager = $language_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getAllViewModes() {
+    return $this->getAllDisplayModesByEntityType('view_mode');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getViewModes($entity_type_id) {
+    return $this->getDisplayModesByEntityType('view_mode', $entity_type_id);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getAllFormModes() {
+    return $this->getAllDisplayModesByEntityType('form_mode');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormModes($entity_type_id) {
+    return $this->getDisplayModesByEntityType('form_mode', $entity_type_id);
+  }
+
+  /**
+   * Gets the entity display mode info for all entity types.
+   *
+   * @param string $display_type
+   *   The display type to be retrieved. It can be "view_mode" or "form_mode".
+   *
+   * @return array
+   *   The display mode info for all entity types.
+   */
+  protected function getAllDisplayModesByEntityType($display_type) {
+    if (!isset($this->displayModeInfo[$display_type])) {
+      $key = 'entity_' . $display_type . '_info';
+      $entity_type_id = 'entity_' . $display_type;
+      $langcode = $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_INTERFACE)->getId();
+      if ($cache = $this->cacheGet("$key:$langcode")) {
+        $this->displayModeInfo[$display_type] = $cache->data;
+      }
+      else {
+        $this->displayModeInfo[$display_type] = array();
+        foreach ($this->entityTypeManager->getStorage($entity_type_id)->loadMultiple() as $display_mode) {
+          list($display_mode_entity_type, $display_mode_name) = explode('.', $display_mode->id(), 2);
+          $this->displayModeInfo[$display_type][$display_mode_entity_type][$display_mode_name] = $display_mode->toArray();
+        }
+        $this->moduleHandler->alter($key, $this->displayModeInfo[$display_type]);
+        $this->cacheSet("$key:$langcode", $this->displayModeInfo[$display_type], CacheBackendInterface::CACHE_PERMANENT, array('entity_types', 'entity_field_info'));
+      }
+    }
+
+    return $this->displayModeInfo[$display_type];
+  }
+
+  /**
+   * Gets the entity display mode info for a specific entity type.
+   *
+   * @param string $display_type
+   *   The display type to be retrieved. It can be "view_mode" or "form_mode".
+   * @param string $entity_type_id
+   *   The entity type whose display mode info should be returned.
+   *
+   * @return array
+   *   The display mode info for a specific entity type.
+   */
+  protected function getDisplayModesByEntityType($display_type, $entity_type_id) {
+    if (isset($this->displayModeInfo[$display_type][$entity_type_id])) {
+      return $this->displayModeInfo[$display_type][$entity_type_id];
+    }
+    else {
+      $display_modes = $this->getAllDisplayModesByEntityType($display_type);
+      if (isset($display_modes[$entity_type_id])) {
+        return $display_modes[$entity_type_id];
+      }
+    }
+    return array();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getViewModeOptions($entity_type, $include_disabled = FALSE) {
+    return $this->getDisplayModeOptions('view_mode', $entity_type, $include_disabled);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormModeOptions($entity_type, $include_disabled = FALSE) {
+    return $this->getDisplayModeOptions('form_mode', $entity_type, $include_disabled);
+  }
+
+  /**
+   * Gets an array of display mode options.
+   *
+   * @param string $display_type
+   *   The display type to be retrieved. It can be "view_mode" or "form_mode".
+   * @param string $entity_type_id
+   *   The entity type whose display mode options should be returned.
+   * @param bool $include_disabled
+   *   Force to include disabled display modes. Defaults to FALSE.
+   *
+   * @return array
+   *   An array of display mode labels, keyed by the display mode ID.
+   */
+  protected function getDisplayModeOptions($display_type, $entity_type_id, $include_disabled = FALSE) {
+    $options = array('default' => t('Default'));
+    foreach ($this->getDisplayModesByEntityType($display_type, $entity_type_id) as $mode => $settings) {
+      if (!empty($settings['status']) || $include_disabled) {
+        $options[$mode] = $settings['label'];
+      }
+    }
+    return $options;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function clearDisplayModeInfo() {
+    $this->displayModeInfo = [];
+    return $this;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Entity/EntityDisplayManagerInterface.php b/core/lib/Drupal/Core/Entity/EntityDisplayManagerInterface.php
new file mode 100644
index 0000000..149b0c9
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/EntityDisplayManagerInterface.php
@@ -0,0 +1,86 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\EntityDisplayManagerInterface.
+ */
+
+namespace Drupal\Core\Entity;
+
+/**
+ * @todo.
+ */
+interface EntityDisplayManagerInterface {
+
+  /**
+   * Gets the entity view mode info for all entity types.
+   *
+   * @return array
+   *   The view mode info for all entity types.
+   */
+  public function getAllViewModes();
+
+  /**
+   * Gets the entity view mode info for a specific entity type.
+   *
+   * @param string $entity_type_id
+   *   The entity type whose view mode info should be returned.
+   *
+   * @return array
+   *   The view mode info for a specific entity type.
+   */
+  public function getViewModes($entity_type_id);
+
+  /**
+   * Gets the entity form mode info for all entity types.
+   *
+   * @return array
+   *   The form mode info for all entity types.
+   */
+  public function getAllFormModes();
+
+  /**
+   * Gets the entity form mode info for a specific entity type.
+   *
+   * @param string $entity_type_id
+   *   The entity type whose form mode info should be returned.
+   *
+   * @return array
+   *   The form mode info for a specific entity type.
+   */
+  public function getFormModes($entity_type_id);
+
+  /**
+   * Gets an array of view mode options.
+   *
+   * @param string $entity_type_id
+   *   The entity type whose view mode options should be returned.
+   * @param bool $include_disabled
+   *   Force to include disabled view modes. Defaults to FALSE.
+   *
+   * @return array
+   *   An array of view mode labels, keyed by the display mode ID.
+   */
+  public function getViewModeOptions($entity_type_id, $include_disabled = FALSE);
+
+  /**
+   * Gets an array of form mode options.
+   *
+   * @param string $entity_type_id
+   *   The entity type whose form mode options should be returned.
+   * @param bool $include_disabled
+   *   Force to include disabled form modes. Defaults to FALSE.
+   *
+   * @return array
+   *   An array of form mode labels, keyed by the display mode ID.
+   */
+  public function getFormModeOptions($entity_type_id, $include_disabled = FALSE);
+
+  /**
+   * Clears the gathered display mode info.
+   *
+   * @return $this
+   */
+  public function clearDisplayModeInfo();
+
+}
diff --git a/core/lib/Drupal/Core/Entity/EntityForm.php b/core/lib/Drupal/Core/Entity/EntityForm.php
index 5450e25..0ec59c0 100644
--- a/core/lib/Drupal/Core/Entity/EntityForm.php
+++ b/core/lib/Drupal/Core/Entity/EntityForm.php
@@ -41,10 +41,19 @@ class EntityForm extends FormBase implements EntityFormInterface {
    * The entity manager.
    *
    * @var \Drupal\Core\Entity\EntityManagerInterface
+   *
+   * @deprecated
    */
   protected $entityManager;
 
   /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
    * The entity being used by this form.
    *
    * @var \Drupal\Core\Entity\EntityInterface
@@ -404,4 +413,12 @@ public function setEntityManager(EntityManagerInterface $entity_manager) {
     return $this;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function setEntityTypeManager(EntityTypeManagerInterface $entity_type_manager) {
+    $this->entityTypeManager = $entity_type_manager;
+    return $this;
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Entity/EntityFormInterface.php b/core/lib/Drupal/Core/Entity/EntityFormInterface.php
index 8ae1a91..738440e 100644
--- a/core/lib/Drupal/Core/Entity/EntityFormInterface.php
+++ b/core/lib/Drupal/Core/Entity/EntityFormInterface.php
@@ -137,7 +137,19 @@ public function setModuleHandler(ModuleHandlerInterface $module_handler);
    *   The entity manager.
    *
    * @return $this
+   *
+   * @deprecated
    */
   public function setEntityManager(EntityManagerInterface $entity_manager);
 
+  /**
+   * Sets the entity type manager for this form.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
+   *
+   * @return $this
+   */
+  public function setEntityTypeManager(EntityTypeManagerInterface $entity_type_manager);
+
 }
diff --git a/core/lib/Drupal/Core/Entity/EntityManager.php b/core/lib/Drupal/Core/Entity/EntityManager.php
index 351361f..110b366 100644
--- a/core/lib/Drupal/Core/Entity/EntityManager.php
+++ b/core/lib/Drupal/Core/Entity/EntityManager.php
@@ -7,15 +7,8 @@
 
 namespace Drupal\Core\Entity;
 
-use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
-use Drupal\Component\Plugin\Exception\PluginNotFoundException;
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\Cache\CacheBackendInterface;
-use Drupal\Core\Config\Entity\ConfigEntityType;
-use Drupal\Core\DependencyInjection\ClassResolverInterface;
-use Drupal\Core\Entity\Exception\AmbiguousEntityClassException;
-use Drupal\Core\Entity\Exception\InvalidLinkTemplateException;
-use Drupal\Core\Entity\Exception\NoCorrespondingEntityClassException;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Field\BaseFieldDefinition;
 use Drupal\Core\Field\FieldDefinitionInterface;
@@ -24,36 +17,22 @@
 use Drupal\Core\Field\FieldStorageDefinitionInterface;
 use Drupal\Core\Field\FieldStorageDefinitionListenerInterface;
 use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
-use Drupal\Core\KeyValueStore\KeyValueStoreInterface;
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Language\LanguageManagerInterface;
-use Drupal\Core\Plugin\DefaultPluginManager;
-use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
+use Drupal\Core\Plugin\Discovery\CacheBackendTrait;
 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.
+ * Provides helper functionality around entity fields.
  *
- * Each entity type definition array is set in the entity type's
- * annotation and altered by hook_entity_type_alter().
- *
- * The defaults for the plugin definition are provided in
- * \Drupal\Core\Entity\EntityManagerInterface::defaults.
- *
- * @see \Drupal\Core\Entity\Annotation\EntityType
- * @see \Drupal\Core\Entity\EntityInterface
- * @see \Drupal\Core\Entity\EntityTypeInterface
- * @see hook_entity_type_alter()
+ * @todo Rename to EntityFieldManager.
  */
-class EntityManager extends DefaultPluginManager implements EntityManagerInterface, ContainerAwareInterface {
+class EntityManager implements EntityManagerInterface {
 
-  use ContainerAwareTrait;
+  use CacheBackendTrait;
   use StringTranslationTrait;
 
   /**
@@ -64,13 +43,6 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa
   protected $extraFields = array();
 
   /**
-   * Contains instantiated handlers keyed by handler type and entity type.
-   *
-   * @var array
-   */
-  protected $handlers = array();
-
-  /**
    * Static cache of base field definitions.
    *
    * @var array
@@ -95,20 +67,6 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa
   protected $fieldStorageDefinitions;
 
   /**
-   * The string translationManager.
-   *
-   * @var \Drupal\Core\StringTranslation\TranslationInterface
-   */
-  protected $translationManager;
-
-  /**
-   * The class resolver.
-   *
-   * @var \Drupal\Core\DependencyInjection\ClassResolverInterface
-   */
-  protected $classResolver;
-
-  /**
    * The typed data manager.
    *
    * @var \Drupal\Core\TypedData\TypedDataManager
@@ -137,20 +95,6 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa
   protected $eventDispatcher;
 
   /**
-   * Static cache of bundle information.
-   *
-   * @var array
-   */
-  protected $bundleInfo;
-
-  /**
-   * Static cache of display modes information.
-   *
-   * @var array
-   */
-  protected $displayModeInfo = array();
-
-  /**
    * An array keyed by entity type. Each value is an array whose keys are
    * field names and whose value is an array with two entries:
    *   - type: The field type.
@@ -171,216 +115,174 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa
   protected $fieldMapByFieldType = array();
 
   /**
-   * Contains cached mappings of class names to entity types.
+   * The module handler.
    *
-   * @var array
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
    */
-  protected $classNameEntityTypeMap = array();
+  protected $moduleHandler;
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * The entity type repository.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeRepositoryInterface
+   */
+  protected $entityTypeRepository;
+
+  /**
+   * The entity type bundle manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeBundleManagerInterface
+   */
+  protected $entityTypeBundleManager;
+
+  /**
+   * The entity display manager.
+   *
+   * @var \Drupal\Core\Entity\EntityDisplayManagerInterface
+   */
+  protected $entityDisplayManager;
 
   /**
    * Constructs a new Entity plugin manager.
    *
-   * @param \Traversable $namespaces
-   *   An object that implements \Traversable which contains the root paths
-   *   keyed by the corresponding namespace to look for plugin implementations,
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
    *   The module handler.
    * @param \Drupal\Core\Cache\CacheBackendInterface $cache
    *   The cache backend to use.
    * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
    *   The language manager.
-   * @param \Drupal\Core\StringTranslation\TranslationInterface $translation_manager
-   *   The string translationManager.
-   * @param \Drupal\Core\DependencyInjection\ClassResolverInterface $class_resolver
-   *   The class resolver.
    * @param \Drupal\Core\TypedData\TypedDataManager $typed_data_manager
    *   The typed data manager.
    * @param \Drupal\Core\KeyValueStore\KeyValueFactoryInterface $key_value_factory
    *   The keyvalue factory.
    * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
    *   The event dispatcher.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
+   * @param \Drupal\Core\Entity\EntityTypeRepositoryInterface $entity_type_repository
+   *   The entity type repository.
+   * @param \Drupal\Core\Entity\EntityTypeBundleManagerInterface $entity_type_bundle_manager
+   *   The entity type bundle manager.
+   * @param \Drupal\Core\Entity\EntityDisplayManagerInterface $entity_display_manager
+   *   The entity display manager.
    */
-  public function __construct(\Traversable $namespaces, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache, LanguageManagerInterface $language_manager, TranslationInterface $translation_manager, ClassResolverInterface $class_resolver, TypedDataManager $typed_data_manager, KeyValueFactoryInterface $key_value_factory, EventDispatcherInterface $event_dispatcher) {
-    parent::__construct('Entity', $namespaces, $module_handler, 'Drupal\Core\Entity\EntityInterface');
-
-    $this->setCacheBackend($cache, 'entity_type', array('entity_types'));
-    $this->alterInfo('entity_type');
-
-    $this->discovery = new AnnotatedClassDiscovery('Entity', $namespaces, 'Drupal\Core\Entity\Annotation\EntityType');
+  public function __construct(ModuleHandlerInterface $module_handler, CacheBackendInterface $cache, LanguageManagerInterface $language_manager, TypedDataManager $typed_data_manager, KeyValueFactoryInterface $key_value_factory, EventDispatcherInterface $event_dispatcher, EntityTypeManagerInterface $entity_type_manager, EntityTypeRepositoryInterface $entity_type_repository, EntityTypeBundleManagerInterface $entity_type_bundle_manager, EntityDisplayManagerInterface $entity_display_manager) {
+    $this->cacheBackend = $cache;
+    $this->moduleHandler = $module_handler;
     $this->languageManager = $language_manager;
-    $this->translationManager = $translation_manager;
-    $this->classResolver = $class_resolver;
     $this->typedDataManager = $typed_data_manager;
     $this->keyValueFactory = $key_value_factory;
     $this->eventDispatcher = $event_dispatcher;
+    $this->entityTypeManager = $entity_type_manager;
+    $this->entityTypeRepository = $entity_type_repository;
+    $this->entityTypeBundleManager = $entity_type_bundle_manager;
+    $this->entityDisplayManager = $entity_display_manager;
   }
 
   /**
    * {@inheritdoc}
    */
   public function clearCachedDefinitions() {
-    parent::clearCachedDefinitions();
+    $this->entityTypeManager->clearCachedDefinitions();
     $this->clearCachedBundles();
     $this->clearCachedFieldDefinitions();
-    $this->classNameEntityTypeMap = array();
-    $this->handlers = array();
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function processDefinition(&$definition, $plugin_id) {
-    /** @var \Drupal\Core\Entity\EntityTypeInterface $definition */
-    parent::processDefinition($definition, $plugin_id);
-
-    // All link templates must have a leading slash.
-    foreach ((array) $definition->getLinkTemplates() as $link_relation_name => $link_template) {
-      if ($link_template[0] != '/') {
-        throw new InvalidLinkTemplateException("Link template '$link_relation_name' for entity type '$plugin_id' must start with a leading slash, the current link template is '$link_template'");
-      }
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function findDefinitions() {
-    $definitions = $this->getDiscovery()->getDefinitions();
-
-    // Directly call the hook implementations to pass the definitions to them
-    // by reference, so new entity types can be added.
-    foreach ($this->moduleHandler->getImplementations('entity_type_build') as $module) {
-      $function = $module . '_' . 'entity_type_build';
-      $function($definitions);
-    }
-    foreach ($definitions as $plugin_id => $definition) {
-      $this->processDefinition($definition, $plugin_id);
-    }
-    if ($this->alterHook) {
-      $this->moduleHandler->alter($this->alterHook, $definitions);
-    }
-    return $definitions;
   }
 
   /**
    * {@inheritdoc}
+   *
+   * @deprecated
    */
   public function getDefinition($entity_type_id, $exception_on_invalid = TRUE) {
-    if (($entity_type = parent::getDefinition($entity_type_id, FALSE)) && class_exists($entity_type->getClass())) {
-      return $entity_type;
-    }
-    elseif (!$exception_on_invalid) {
-      return NULL;
-    }
-
-    throw new PluginNotFoundException($entity_type_id, sprintf('The "%s" entity type does not exist.', $entity_type_id));
+    return $this->entityTypeManager->getDefinition($entity_type_id, $exception_on_invalid);
   }
 
   /**
    * {@inheritdoc}
+   *
+   * @deprecated
    */
   public function hasHandler($entity_type, $handler_type) {
-    if ($definition = $this->getDefinition($entity_type, FALSE)) {
-      return $definition->hasHandlerClass($handler_type);
-    }
-    return FALSE;
+    return $this->entityTypeManager->hasHandler($entity_type, $handler_type);
   }
 
   /**
    * {@inheritdoc}
+   *
+   * @deprecated
    */
   public function getStorage($entity_type) {
-    return $this->getHandler($entity_type, 'storage');
+    return $this->entityTypeManager->getStorage($entity_type);
   }
 
   /**
    * {@inheritdoc}
+   *
+   * @deprecated
    */
   public function getListBuilder($entity_type) {
-    return $this->getHandler($entity_type, 'list_builder');
+    return $this->entityTypeManager->getListBuilder($entity_type);
   }
 
   /**
    * {@inheritdoc}
+   *
+   * @deprecated
    */
   public function getFormObject($entity_type, $operation) {
-    if (!isset($this->handlers['form'][$operation][$entity_type])) {
-      if (!$class = $this->getDefinition($entity_type, TRUE)->getFormClass($operation)) {
-        throw new InvalidPluginDefinitionException($entity_type, sprintf('The "%s" entity type did not specify a "%s" form class.', $entity_type, $operation));
-      }
-
-      $form_object = $this->classResolver->getInstanceFromDefinition($class);
-
-      $form_object
-        ->setStringTranslation($this->translationManager)
-        ->setModuleHandler($this->moduleHandler)
-        ->setEntityManager($this)
-        ->setOperation($operation);
-      $this->handlers['form'][$operation][$entity_type] = $form_object;
-    }
-    return $this->handlers['form'][$operation][$entity_type];
+    return $this->entityTypeManager->getFormObject($entity_type, $operation);
   }
 
   /**
    * {@inheritdoc}
+   *
+   * @deprecated
    */
   public function getRouteProviders($entity_type) {
-    if (!isset($this->handlers['route_provider'][$entity_type])) {
-      $route_provider_classes = $this->getDefinition($entity_type, TRUE)->getRouteProviderClasses();
-
-      foreach ($route_provider_classes as $type => $class) {
-        $this->handlers['route_provider'][$entity_type][$type] = $this->createHandlerInstance($class, $this->getDefinition($entity_type));
-      }
-    }
-    return isset($this->handlers['route_provider'][$entity_type]) ? $this->handlers['route_provider'][$entity_type] : [];
+    return $this->entityTypeManager->getRouteProviders($entity_type);
   }
 
   /**
    * {@inheritdoc}
+   *
+   * @deprecated
    */
   public function getViewBuilder($entity_type) {
-    return $this->getHandler($entity_type, 'view_builder');
+    return $this->entityTypeManager->getViewBuilder($entity_type);
   }
 
   /**
    * {@inheritdoc}
+   *
+   * @deprecated
    */
   public function getAccessControlHandler($entity_type) {
-    return $this->getHandler($entity_type, 'access');
+    return $this->entityTypeManager->getAccessControlHandler($entity_type);
   }
 
   /**
    * {@inheritdoc}
+   *
+   * @deprecated
    */
   public function getHandler($entity_type, $handler_type) {
-    if (!isset($this->handlers[$handler_type][$entity_type])) {
-      $definition = $this->getDefinition($entity_type);
-      $class = $definition->getHandlerClass($handler_type);
-      if (!$class) {
-        throw new InvalidPluginDefinitionException($entity_type, sprintf('The "%s" entity type did not specify a %s handler.', $entity_type, $handler_type));
-      }
-      $this->handlers[$handler_type][$entity_type] = $this->createHandlerInstance($class, $definition);
-    }
-    return $this->handlers[$handler_type][$entity_type];
+    return $this->entityTypeManager->getHandler($entity_type, $handler_type);
   }
 
   /**
    * {@inheritdoc}
+   *
+   * @deprecated
    */
   public function createHandlerInstance($class, EntityTypeInterface $definition = null) {
-    if (is_subclass_of($class, 'Drupal\Core\Entity\EntityHandlerInterface')) {
-      $handler = $class::createInstance($this->container, $definition);
-    }
-    else {
-      $handler = new $class($definition);
-    }
-    if (method_exists($handler, 'setModuleHandler')) {
-      $handler->setModuleHandler($this->moduleHandler);
-    }
-    if (method_exists($handler, 'setStringTranslation')) {
-      $handler->setStringTranslation($this->translationManager);
-    }
-    return $handler;
+    return $this->entityTypeManager->createHandlerInstance($class, $definition);
   }
 
   /**
@@ -840,7 +742,7 @@ public function clearCachedFieldDefinitions() {
     $this->fieldStorageDefinitions = array();
     $this->fieldMap = array();
     $this->fieldMapByFieldType = array();
-    $this->displayModeInfo = array();
+    $this->entityDisplayManager->clearDisplayModeInfo();
     $this->extraFields = array();
     Cache::invalidateTags(array('entity_field_info'));
     // The typed data manager statically caches prototype objects with injected
@@ -850,54 +752,29 @@ public function clearCachedFieldDefinitions() {
 
   /**
    * {@inheritdoc}
+   *
+   * @deprecated
    */
   public function clearCachedBundles() {
-    $this->bundleInfo = array();
-    Cache::invalidateTags(array('entity_bundles'));
-    // Entity bundles are exposed as data types, clear that cache too.
-    $this->typedDataManager->clearCachedDefinitions();
+    $this->entityTypeBundleManager->clearCachedBundles();
   }
 
   /**
    * {@inheritdoc}
+   *
+   * @deprecated
    */
   public function getBundleInfo($entity_type) {
-    $bundle_info = $this->getAllBundleInfo();
-    return isset($bundle_info[$entity_type]) ? $bundle_info[$entity_type] : array();
+    return $this->entityTypeBundleManager->getBundleInfo($entity_type);
   }
 
   /**
    * {@inheritdoc}
+   *
+   * @deprecated
    */
   public function getAllBundleInfo() {
-    if (empty($this->bundleInfo)) {
-      $langcode = $this->languageManager->getCurrentLanguage()->getId();
-      if ($cache = $this->cacheGet("entity_bundle_info:$langcode")) {
-        $this->bundleInfo = $cache->data;
-      }
-      else {
-        $this->bundleInfo = $this->moduleHandler->invokeAll('entity_bundle_info');
-        // First look for entity types that act as bundles for others, load them
-        // and add them as bundles.
-        foreach ($this->getDefinitions() as $type => $entity_type) {
-          if ($entity_type->getBundleOf()) {
-            foreach ($this->getStorage($type)->loadMultiple() as $entity) {
-              $this->bundleInfo[$entity_type->getBundleOf()][$entity->id()]['label'] = $entity->label();
-            }
-          }
-        }
-        foreach ($this->getDefinitions() as $type => $entity_type) {
-          // If no bundles are provided, use the entity type name and label.
-          if (!isset($this->bundleInfo[$type])) {
-            $this->bundleInfo[$type][$type]['label'] = $entity_type->getLabel();
-          }
-        }
-        $this->moduleHandler->alter('entity_bundle_info', $this->bundleInfo);
-        $this->cacheSet("entity_bundle_info:$langcode", $this->bundleInfo, Cache::PERMANENT, array('entity_types', 'entity_bundles'));
-      }
-    }
-
-    return $this->bundleInfo;
+    return $this->entityTypeBundleManager->getAllBundleInfo();
   }
 
   /**
@@ -938,32 +815,11 @@ public function getExtraFields($entity_type_id, $bundle) {
 
   /**
    * {@inheritdoc}
+   *
+   * @deprecated
    */
   public function getEntityTypeLabels($group = FALSE) {
-    $options = array();
-    $definitions = $this->getDefinitions();
-
-    foreach ($definitions as $entity_type_id => $definition) {
-      if ($group) {
-        $options[$definition->getGroupLabel()][$entity_type_id] = $definition->getLabel();
-      }
-      else {
-        $options[$entity_type_id] = $definition->getLabel();
-      }
-    }
-
-    if ($group) {
-      foreach ($options as &$group_options) {
-        // Sort the list alphabetically by group label.
-        array_multisort($group_options, SORT_ASC, SORT_NATURAL);
-      }
-
-      // Make sure that the 'Content' group is situated at the top.
-      $content = $this->t('Content', array(), array('context' => 'Entity type group'));
-      $options = array($content => $options[$content]) + $options;
-    }
-
-    return $options;
+    return $this->entityTypeRepository->getEntityTypeLabels($group);
   }
 
   /**
@@ -1004,189 +860,90 @@ public function getTranslationFromContext(EntityInterface $entity, $langcode = N
 
   /**
    * {@inheritdoc}
+   *
+   * @deprecated
    */
   public function getAllViewModes() {
-    return $this->getAllDisplayModesByEntityType('view_mode');
+    return $this->entityDisplayManager->getAllViewModes();
   }
 
   /**
    * {@inheritdoc}
+   *
+   * @deprecated
    */
   public function getViewModes($entity_type_id) {
-    return $this->getDisplayModesByEntityType('view_mode', $entity_type_id);
+    return $this->entityDisplayManager->getViewModes($entity_type_id);
   }
 
   /**
    * {@inheritdoc}
+   *
+   * @deprecated
    */
   public function getAllFormModes() {
-    return $this->getAllDisplayModesByEntityType('form_mode');
+    return $this->entityDisplayManager->getAllFormModes();
   }
 
   /**
    * {@inheritdoc}
+   *
+   * @deprecated
    */
   public function getFormModes($entity_type_id) {
-    return $this->getDisplayModesByEntityType('form_mode', $entity_type_id);
-  }
-
-  /**
-   * Gets the entity display mode info for all entity types.
-   *
-   * @param string $display_type
-   *   The display type to be retrieved. It can be "view_mode" or "form_mode".
-   *
-   * @return array
-   *   The display mode info for all entity types.
-   */
-  protected function getAllDisplayModesByEntityType($display_type) {
-    if (!isset($this->displayModeInfo[$display_type])) {
-      $key = 'entity_' . $display_type . '_info';
-      $entity_type_id = 'entity_' . $display_type;
-      $langcode = $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_INTERFACE)->getId();
-      if ($cache = $this->cacheGet("$key:$langcode")) {
-        $this->displayModeInfo[$display_type] = $cache->data;
-      }
-      else {
-        $this->displayModeInfo[$display_type] = array();
-        foreach ($this->getStorage($entity_type_id)->loadMultiple() as $display_mode) {
-          list($display_mode_entity_type, $display_mode_name) = explode('.', $display_mode->id(), 2);
-          $this->displayModeInfo[$display_type][$display_mode_entity_type][$display_mode_name] = $display_mode->toArray();
-        }
-        $this->moduleHandler->alter($key, $this->displayModeInfo[$display_type]);
-        $this->cacheSet("$key:$langcode", $this->displayModeInfo[$display_type], CacheBackendInterface::CACHE_PERMANENT, array('entity_types', 'entity_field_info'));
-      }
-    }
-
-    return $this->displayModeInfo[$display_type];
-  }
-
-  /**
-   * Gets the entity display mode info for a specific entity type.
-   *
-   * @param string $display_type
-   *   The display type to be retrieved. It can be "view_mode" or "form_mode".
-   * @param string $entity_type_id
-   *   The entity type whose display mode info should be returned.
-   *
-   * @return array
-   *   The display mode info for a specific entity type.
-   */
-  protected function getDisplayModesByEntityType($display_type, $entity_type_id) {
-    if (isset($this->displayModeInfo[$display_type][$entity_type_id])) {
-      return $this->displayModeInfo[$display_type][$entity_type_id];
-    }
-    else {
-      $display_modes = $this->getAllDisplayModesByEntityType($display_type);
-      if (isset($display_modes[$entity_type_id])) {
-        return $display_modes[$entity_type_id];
-      }
-    }
-    return array();
+    return $this->entityDisplayManager->getFormModes($entity_type_id);
   }
 
   /**
    * {@inheritdoc}
+   *
+   * @deprecated
    */
   public function getViewModeOptions($entity_type, $include_disabled = FALSE) {
-    return $this->getDisplayModeOptions('view_mode', $entity_type, $include_disabled);
+    return $this->entityDisplayManager->getViewModeOptions($entity_type, $include_disabled);
   }
 
   /**
    * {@inheritdoc}
+   *
+   * @deprecated
    */
   public function getFormModeOptions($entity_type, $include_disabled = FALSE) {
-    return $this->getDisplayModeOptions('form_mode', $entity_type, $include_disabled);
-  }
-
-  /**
-   * Gets an array of display mode options.
-   *
-   * @param string $display_type
-   *   The display type to be retrieved. It can be "view_mode" or "form_mode".
-   * @param string $entity_type_id
-   *   The entity type whose display mode options should be returned.
-   * @param bool $include_disabled
-   *   Force to include disabled display modes. Defaults to FALSE.
-   *
-   * @return array
-   *   An array of display mode labels, keyed by the display mode ID.
-   */
-  protected function getDisplayModeOptions($display_type, $entity_type_id, $include_disabled = FALSE) {
-    $options = array('default' => t('Default'));
-    foreach ($this->getDisplayModesByEntityType($display_type, $entity_type_id) as $mode => $settings) {
-      if (!empty($settings['status']) || $include_disabled) {
-        $options[$mode] = $settings['label'];
-      }
-    }
-    return $options;
+    return $this->entityDisplayManager->getFormModeOptions($entity_type, $include_disabled);
   }
 
   /**
    * {@inheritdoc}
    */
+  public function clearDisplayModeInfo() {
+    $this->entityDisplayManager->clearDisplayModeInfo();
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * @deprecated
+   */
   public function loadEntityByUuid($entity_type_id, $uuid) {
-    $entity_type = $this->getDefinition($entity_type_id);
-
-    if (!$uuid_key = $entity_type->getKey('uuid')) {
-      throw new EntityStorageException("Entity type $entity_type_id does not support UUIDs.");
-    }
-
-    $entities = $this->getStorage($entity_type_id)->loadByProperties(array($uuid_key => $uuid));
-
-    return reset($entities);
+    return $this->entityTypeRepository->loadEntityByUuid($entity_type_id, $uuid);
   }
 
   /**
    * {@inheritdoc}
+   *
+   * @deprecated
    */
   public function loadEntityByConfigTarget($entity_type_id, $target) {
-    $entity_type = $this->getDefinition($entity_type_id);
-
-    // For configuration entities, the config target is given by the entity ID.
-    // @todo Consider adding a method to allow entity types to indicate the
-    //   target identifier key rather than hard-coding this check. Issue:
-    //   https://www.drupal.org/node/2412983.
-    if ($entity_type instanceof ConfigEntityType) {
-      $entity = $this->getStorage($entity_type_id)->load($target);
-    }
-
-    // For content entities, the config target is given by the UUID.
-    else {
-      $entity = $this->loadEntityByUuid($entity_type_id, $target);
-    }
-
-    return $entity;
+    return $this->entityTypeRepository->loadEntityByConfigTarget($entity_type_id, $target);
   }
 
   /**
    * {@inheritdoc}
+   *
+   * @deprecated
    */
   public function getEntityTypeFromClass($class_name) {
-
-    // Check the already calculated classes first.
-    if (isset($this->classNameEntityTypeMap[$class_name])) {
-      return $this->classNameEntityTypeMap[$class_name];
-    }
-
-    $same_class = 0;
-    $entity_type_id = NULL;
-    foreach ($this->getDefinitions() as $entity_type) {
-      if ($entity_type->getOriginalClass() == $class_name  || $entity_type->getClass() == $class_name) {
-        $entity_type_id = $entity_type->id();
-        if ($same_class++) {
-          throw new AmbiguousEntityClassException($class_name);
-        }
-      }
-    }
-
-    // Return the matching entity type ID if there is one.
-    if ($entity_type_id) {
-      $this->classNameEntityTypeMap[$class_name] = $entity_type_id;
-      return $entity_type_id;
-    }
-
-    throw new NoCorrespondingEntityClassException($class_name);
+    return $this->entityTypeRepository->getEntityTypeFromClass($class_name);
   }
 
   /**
@@ -1368,9 +1125,9 @@ public function getLastInstalledDefinition($entity_type_id) {
    * {@inheritdoc}
    */
   public function useCaches($use_caches = FALSE) {
-    parent::useCaches($use_caches);
+    $this->useCaches = $use_caches;
+    $this->entityTypeManager->useCaches($use_caches);
     if (!$use_caches) {
-      $this->handlers = [];
       $this->fieldDefinitions = [];
       $this->baseFieldDefinitions = [];
       $this->fieldStorageDefinitions = [];
@@ -1447,4 +1204,40 @@ protected function deleteLastInstalledFieldStorageDefinition(FieldStorageDefinit
     $this->setLastInstalledFieldStorageDefinitions($entity_type_id, $definitions);
   }
 
+  /**
+   * {@inheritdoc}
+   *
+   * @deprecated
+   */
+  public function getDefinitions() {
+    return $this->entityTypeManager->getDefinitions();
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * @deprecated
+   */
+  public function hasDefinition($plugin_id) {
+    return $this->entityTypeManager->hasDefinition($plugin_id);
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * @deprecated
+   */
+  public function createInstance($plugin_id, array $configuration = []) {
+    return $this->entityTypeManager->createInstance($plugin_id, $configuration);
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * @deprecated
+   */
+  public function getInstance(array $options) {
+    return $this->entityTypeManager->getInstance($options);
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Entity/EntityManagerInterface.php b/core/lib/Drupal/Core/Entity/EntityManagerInterface.php
index 7b180d2..68c9e50 100644
--- a/core/lib/Drupal/Core/Entity/EntityManagerInterface.php
+++ b/core/lib/Drupal/Core/Entity/EntityManagerInterface.php
@@ -14,20 +14,14 @@
 
 /**
  * Provides an interface for entity type managers.
+ *
+ * Note: The following interfaces that are extended are deprecated as of 8.0.0 and will be removed in 9.0.0:
+ * - \Drupal\Core\Entity\EntityTypeManagerInterface
+ * - \Drupal\Core\Entity\EntityTypeRepositoryInterface
+ * - \Drupal\Core\Entity\EntityTypeBundleManagerInterface
+ * - \Drupal\Core\Entity\EntityDisplayManagerInterface
  */
-interface EntityManagerInterface extends PluginManagerInterface, EntityTypeListenerInterface, EntityBundleListenerInterface, FieldStorageDefinitionListenerInterface, FieldDefinitionListenerInterface, CachedDiscoveryInterface {
-
-  /**
-   * Builds a list of entity type labels suitable for a Form API options list.
-   *
-   * @param bool $group
-   *   (optional) Whether to group entity types by plugin group (e.g. 'content',
-   *   'config'). Defaults to FALSE.
-   *
-   * @return array
-   *   An array of entity type labels, keyed by entity type name.
-   */
-  public function getEntityTypeLabels($group = FALSE);
+interface EntityManagerInterface extends EntityTypeListenerInterface, EntityBundleListenerInterface, FieldStorageDefinitionListenerInterface, FieldDefinitionListenerInterface, CachedDiscoveryInterface, EntityTypeManagerInterface, EntityTypeRepositoryInterface, EntityTypeBundleManagerInterface, EntityDisplayManagerInterface {
 
   /**
    * Gets the base field definitions for a content entity type.
@@ -144,154 +138,11 @@ public function getFieldMap();
   public function getFieldMapByFieldType($field_type);
 
   /**
-   * Creates a new access control handler instance.
-   *
-   * @param string $entity_type
-   *   The entity type for this access control handler.
-   *
-   * @return \Drupal\Core\Entity\EntityAccessControlHandlerInterface.
-   *   A access control handler instance.
-   */
-  public function getAccessControlHandler($entity_type);
-
-  /**
-   * Creates a new storage instance.
-   *
-   * @param string $entity_type
-   *   The entity type for this storage.
-   *
-   * @return \Drupal\Core\Entity\EntityStorageInterface
-   *   A storage instance.
-   *
-   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
-   */
-  public function getStorage($entity_type);
-
-  /**
-   * Get the bundle info of all entity types.
-   *
-   * @return array
-   *   An array of all bundle information.
-   */
-  public function getAllBundleInfo();
-
-  /**
-   * {@inheritdoc}
-   */
-  public function clearCachedDefinitions();
-
-  /**
    * Clears static and persistent field definition caches.
    */
   public function clearCachedFieldDefinitions();
 
   /**
-   * Clears static and persistent bundles.
-   */
-  public function clearCachedBundles();
-
-  /**
-   * Creates a new view builder instance.
-   *
-   * @param string $entity_type
-   *   The entity type for this view builder.
-   *
-   * @return \Drupal\Core\Entity\EntityViewBuilderInterface.
-   *   A view builder instance.
-   */
-  public function getViewBuilder($entity_type);
-
-  /**
-   * Creates a new entity list builder.
-   *
-   * @param string $entity_type
-   *   The entity type for this list builder.
-   *
-   * @return \Drupal\Core\Entity\EntityListBuilderInterface
-   *   An entity list builder instance.
-   */
-  public function getListBuilder($entity_type);
-
-  /**
-   * Creates a new form instance.
-   *
-   * @param string $entity_type
-   *   The entity type for this form.
-   * @param string $operation
-   *   The name of the operation to use, e.g., 'default'.
-   *
-   * @return \Drupal\Core\Entity\EntityFormInterface
-   *   A form instance.
-   */
-  public function getFormObject($entity_type, $operation);
-
-  /**
-   * Gets all route provider instances.
-   *
-   * @param string $entity_type
-   *   The entity type for this route providers.
-   *
-   * @return \Drupal\Core\Entity\Routing\EntityRouteProviderInterface[]
-   */
-  public function getRouteProviders($entity_type);
-
-  /**
-   * Checks whether a certain entity type has a certain handler.
-   *
-   * @param string $entity_type
-   *   The name of the entity type.
-   * @param string $handler_type
-   *   The name of the handler.
-   *
-   * @return bool
-   *   Returns TRUE if the entity type has the handler, else FALSE.
-   */
-  public function hasHandler($entity_type, $handler_type);
-
-  /**
-   * Creates a new handler instance for a entity type and handler type.
-   *
-   * @param string $entity_type
-   *   The entity type for this controller.
-   * @param string $handler_type
-   *   The controller type to create an instance for.
-   *
-   * @return object
-   *   A handler instance.
-   *
-   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
-   */
-  public function getHandler($entity_type, $handler_type);
-
-  /**
-   * Creates new handler instance.
-   *
-   * Usually \Drupal\Core\Entity\EntityManagerInterface::getHandler() is
-   * preferred since that method has additional checking that the class exists
-   * and has static caches.
-   *
-   * @param mixed $class
-   *   The handler class to instantiate.
-   * @param \Drupal\Core\Entity\EntityTypeInterface $definition
-   *   The entity type definition.
-   *
-   * @return object
-   *   A handler instance.
-   */
-  public function createHandlerInstance($class, EntityTypeInterface $definition = null);
-
-  /**
-   * Gets the bundle info of an entity type.
-   *
-   * @param string $entity_type
-   *   The entity type.
-   *
-   * @return array
-   *   Returns the bundle information for the specified entity type.
-   */
-  public function getBundleInfo($entity_type);
-
-  /**
    * Gets the "extra fields" for a bundle.
    *
    * @param string $entity_type_id
@@ -344,13 +195,6 @@ public function getExtraFields($entity_type_id, $bundle);
   public function getTranslationFromContext(EntityInterface $entity, $langcode = NULL, $context = array());
 
   /**
-   * {@inheritdoc}
-   *
-   * @return \Drupal\Core\Entity\EntityTypeInterface|null
-   */
-  public function getDefinition($entity_type_id, $exception_on_invalid = TRUE);
-
-  /**
    * Gets the entity type definition in its most recently installed state.
    *
    * During the application lifetime, entity type definitions can change. For
@@ -382,137 +226,4 @@ public function getDefinition($entity_type_id, $exception_on_invalid = TRUE);
    */
   public function getLastInstalledDefinition($entity_type_id);
 
-  /**
-   * {@inheritdoc}
-   *
-   * @return \Drupal\Core\Entity\EntityTypeInterface[]
-   */
-  public function getDefinitions();
-
-  /**
-   * Gets the entity view mode info for all entity types.
-   *
-   * @return array
-   *   The view mode info for all entity types.
-   */
-  public function getAllViewModes();
-
-  /**
-   * Gets the entity view mode info for a specific entity type.
-   *
-   * @param string $entity_type_id
-   *   The entity type whose view mode info should be returned.
-   *
-   * @return array
-   *   The view mode info for a specific entity type.
-   */
-  public function getViewModes($entity_type_id);
-
-  /**
-   * Gets the entity form mode info for all entity types.
-   *
-   * @return array
-   *   The form mode info for all entity types.
-   */
-  public function getAllFormModes();
-
-  /**
-   * Gets the entity form mode info for a specific entity type.
-   *
-   * @param string $entity_type_id
-   *   The entity type whose form mode info should be returned.
-   *
-   * @return array
-   *   The form mode info for a specific entity type.
-   */
-  public function getFormModes($entity_type_id);
-
-  /**
-   * Gets an array of view mode options.
-   *
-   * @param string $entity_type_id
-   *   The entity type whose view mode options should be returned.
-   * @param bool $include_disabled
-   *   Force to include disabled view modes. Defaults to FALSE.
-   *
-   * @return array
-   *   An array of view mode labels, keyed by the display mode ID.
-   */
-  public function getViewModeOptions($entity_type_id, $include_disabled = FALSE);
-
-  /**
-   * Gets an array of form mode options.
-   *
-   * @param string $entity_type_id
-   *   The entity type whose form mode options should be returned.
-   * @param bool $include_disabled
-   *   Force to include disabled form modes. Defaults to FALSE.
-   *
-   * @return array
-   *   An array of form mode labels, keyed by the display mode ID.
-   */
-  public function getFormModeOptions($entity_type_id, $include_disabled = FALSE);
-
-  /**
-   * Loads an entity by UUID.
-   *
-   * Note that some entity types may not support UUIDs.
-   *
-   * @param string $entity_type_id
-   *   The entity type ID to load from.
-   * @param string $uuid
-   *   The UUID of the entity to load.
-   *
-   * @return \Drupal\Core\Entity\EntityInterface|null
-   *   The entity object, or NULL if there is no entity with the given UUID.
-   *
-   * @throws \Drupal\Core\Entity\EntityStorageException
-   *   Thrown in case the requested entity type does not support UUIDs.
-   */
-  public function loadEntityByUuid($entity_type_id, $uuid);
-
-  /**
-   * Loads an entity by the config target identifier.
-   *
-   * @param string $entity_type_id
-   *   The entity type ID to load from.
-   * @param string $target
-   *   The configuration target to load, as returned from
-   *   \Drupal\Core\Entity\EntityInterface::getConfigTarget().
-   *
-   * @return \Drupal\Core\Entity\EntityInterface|null
-   *   The entity object, or NULL if there is no entity with the given config
-   *   target identifier.
-   *
-   * @throws \Drupal\Core\Entity\EntityStorageException
-   *   Thrown if the target identifier is a UUID but the entity type does not
-   *   support UUIDs.
-   *
-   * @see \Drupal\Core\Entity\EntityInterface::getConfigTarget()
-   */
-  public function loadEntityByConfigTarget($entity_type_id, $target);
-
-  /**
-   * Gets the entity type ID based on the class that is called on.
-   *
-   * Compares the class this is called on against the known entity classes
-   * and returns the entity type ID of a direct match or a subclass as fallback,
-   * to support entity type definitions that were altered.
-   *
-   * @param string $class_name
-   *   Class name to use for searching the entity type ID.
-   *
-   * @return string
-   *   The entity type ID.
-   *
-   * @throws \Drupal\Core\Entity\Exception\AmbiguousEntityClassException
-   *   Thrown when multiple subclasses correspond to the called class.
-   * @throws \Drupal\Core\Entity\Exception\NoCorrespondingEntityClassException
-   *   Thrown when no entity class corresponds to the called class.
-   *
-   * @see \Drupal\Core\Entity\Entity::load()
-   * @see \Drupal\Core\Entity\Entity::loadMultiple()
-   */
-  public function getEntityTypeFromClass($class_name);
-
 }
diff --git a/core/lib/Drupal/Core/Entity/EntityTypeBundleManager.php b/core/lib/Drupal/Core/Entity/EntityTypeBundleManager.php
new file mode 100644
index 0000000..14d7d81
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/EntityTypeBundleManager.php
@@ -0,0 +1,133 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\EntityTypeBundleManager.
+ */
+
+namespace Drupal\Core\Entity;
+
+use Drupal\Core\Cache\Cache;
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\Core\Plugin\Discovery\CacheBackendTrait;
+use Drupal\Core\TypedData\TypedDataManager;
+
+/**
+ * @todo.
+ */
+class EntityTypeBundleManager implements EntityTypeBundleManagerInterface {
+
+  use CacheBackendTrait;
+
+  /**
+   * Static cache of bundle information.
+   *
+   * @var array
+   */
+  protected $bundleInfo;
+
+  /**
+   * The language manager.
+   *
+   * @var \Drupal\Core\Language\LanguageManagerInterface
+   */
+  protected $languageManager;
+
+  /**
+   * The module handler.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * The typed data manager.
+   *
+   * @var \Drupal\Core\TypedData\TypedDataManager
+   */
+  protected $typedDataManager;
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * Constructs a new EntityTypeBundleManager.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
+   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
+   *   The language manager.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler.
+   * @param \Drupal\Core\TypedData\TypedDataManager $typed_data_manager
+   *   The typed data manager.
+   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
+   *   The cache backend.
+   */
+  public function __construct(EntityTypeManagerInterface $entity_type_manager, LanguageManagerInterface $language_manager, ModuleHandlerInterface $module_handler, TypedDataManager $typed_data_manager, CacheBackendInterface $cache_backend) {
+    $this->entityTypeManager = $entity_type_manager;
+    $this->languageManager = $language_manager;
+    $this->moduleHandler = $module_handler;
+    $this->typedDataManager = $typed_data_manager;
+    $this->cacheBackend = $cache_backend;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getBundleInfo($entity_type) {
+    $bundle_info = $this->getAllBundleInfo();
+    return isset($bundle_info[$entity_type]) ? $bundle_info[$entity_type] : array();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getAllBundleInfo() {
+    if (empty($this->bundleInfo)) {
+      $langcode = $this->languageManager->getCurrentLanguage()->getId();
+      if ($cache = $this->cacheGet("entity_bundle_info:$langcode")) {
+        $this->bundleInfo = $cache->data;
+      }
+      else {
+        $this->bundleInfo = $this->moduleHandler->invokeAll('entity_bundle_info');
+        // First look for entity types that act as bundles for others, load them
+        // and add them as bundles.
+        foreach ($this->entityTypeManager->getDefinitions() as $type => $entity_type) {
+          if ($entity_type->getBundleOf()) {
+            foreach ($this->entityTypeManager->getStorage($type)->loadMultiple() as $entity) {
+              $this->bundleInfo[$entity_type->getBundleOf()][$entity->id()]['label'] = $entity->label();
+            }
+          }
+        }
+        foreach ($this->entityTypeManager->getDefinitions() as $type => $entity_type) {
+          // If no bundles are provided, use the entity type name and label.
+          if (!isset($this->bundleInfo[$type])) {
+            $this->bundleInfo[$type][$type]['label'] = $entity_type->getLabel();
+          }
+        }
+        $this->moduleHandler->alter('entity_bundle_info', $this->bundleInfo);
+        $this->cacheSet("entity_bundle_info:$langcode", $this->bundleInfo, Cache::PERMANENT, array('entity_types', 'entity_bundles'));
+      }
+    }
+
+    return $this->bundleInfo;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function clearCachedBundles() {
+    $this->bundleInfo = array();
+    Cache::invalidateTags(array('entity_bundles'));
+    // Entity bundles are exposed as data types, clear that cache too.
+    $this->typedDataManager->clearCachedDefinitions();
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Entity/EntityTypeBundleManagerInterface.php b/core/lib/Drupal/Core/Entity/EntityTypeBundleManagerInterface.php
new file mode 100644
index 0000000..886a17f
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/EntityTypeBundleManagerInterface.php
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\EntityTypeBundleManagerInterface.
+ */
+
+namespace Drupal\Core\Entity;
+
+/**
+ * @todo.
+ */
+interface EntityTypeBundleManagerInterface {
+
+  /**
+   * Get the bundle info of all entity types.
+   *
+   * @return array
+   *   An array of all bundle information.
+   */
+  public function getAllBundleInfo();
+
+  /**
+   * Gets the bundle info of an entity type.
+   *
+   * @param string $entity_type
+   *   The entity type.
+   *
+   * @return array
+   *   Returns the bundle information for the specified entity type.
+   */
+  public function getBundleInfo($entity_type);
+
+  /**
+   * Clears static and persistent bundles.
+   */
+  public function clearCachedBundles();
+
+}
diff --git a/core/lib/Drupal/Core/Entity/EntityTypeManager.php b/core/lib/Drupal/Core/Entity/EntityTypeManager.php
new file mode 100644
index 0000000..712460f
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/EntityTypeManager.php
@@ -0,0 +1,253 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\EntityTypeManager.
+ */
+
+namespace Drupal\Core\Entity;
+
+use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
+use Drupal\Component\Plugin\Exception\PluginNotFoundException;
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\DependencyInjection\ClassResolverInterface;
+use Drupal\Core\Entity\Exception\InvalidLinkTemplateException;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Plugin\DefaultPluginManager;
+use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Symfony\Component\DependencyInjection\ContainerAwareInterface;
+use Symfony\Component\DependencyInjection\ContainerAwareTrait;
+
+/**
+ * Manages entity type plugin definitions.
+ *
+ * Each entity type definition array is set in the entity type's
+ * annotation and altered by hook_entity_type_alter().
+ *
+ * @see \Drupal\Core\Entity\Annotation\EntityType
+ * @see \Drupal\Core\Entity\EntityInterface
+ * @see \Drupal\Core\Entity\EntityTypeInterface
+ * @see hook_entity_type_alter()
+ */
+class EntityTypeManager extends DefaultPluginManager implements EntityTypeManagerInterface, ContainerAwareInterface {
+
+  use ContainerAwareTrait;
+
+  /**
+   * Contains instantiated handlers keyed by handler type and entity type.
+   *
+   * @var array
+   */
+  protected $handlers = [];
+
+  /**
+   * Constructs a new Entity plugin manager.
+   *
+   * @param \Traversable $namespaces
+   *   An object that implements \Traversable which contains the root paths
+   *   keyed by the corresponding namespace to look for plugin implementations,
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler.
+   * @param \Drupal\Core\Cache\CacheBackendInterface $cache
+   *   The cache backend to use.
+   * @param \Drupal\Core\StringTranslation\TranslationInterface $translation_manager
+   *   The string translationManager.
+   * @param \Drupal\Core\DependencyInjection\ClassResolverInterface $class_resolver
+   *   The class resolver.
+   */
+  public function __construct(\Traversable $namespaces, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache, TranslationInterface $translation_manager, ClassResolverInterface $class_resolver) {
+    parent::__construct('Entity', $namespaces, $module_handler, 'Drupal\Core\Entity\EntityInterface');
+
+    $this->setCacheBackend($cache, 'entity_type', ['entity_types']);
+    $this->alterInfo('entity_type');
+
+    $this->discovery = new AnnotatedClassDiscovery('Entity', $namespaces, 'Drupal\Core\Entity\Annotation\EntityType');
+    $this->translationManager = $translation_manager;
+    $this->classResolver = $class_resolver;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function processDefinition(&$definition, $plugin_id) {
+    /** @var \Drupal\Core\Entity\EntityTypeInterface $definition */
+    parent::processDefinition($definition, $plugin_id);
+
+    // All link templates must have a leading slash.
+    foreach ((array) $definition->getLinkTemplates() as $link_relation_name => $link_template) {
+      if ($link_template[0] != '/') {
+        throw new InvalidLinkTemplateException("Link template '$link_relation_name' for entity type '$plugin_id' must start with a leading slash, the current link template is '$link_template'");
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function findDefinitions() {
+    $definitions = $this->getDiscovery()->getDefinitions();
+
+    // Directly call the hook implementations to pass the definitions to them
+    // by reference, so new entity types can be added.
+    foreach ($this->moduleHandler->getImplementations('entity_type_build') as $module) {
+      $function = $module . '_' . 'entity_type_build';
+      $function($definitions);
+    }
+    foreach ($definitions as $plugin_id => $definition) {
+      $this->processDefinition($definition, $plugin_id);
+    }
+    $this->alterDefinitions($definitions);
+
+    return $definitions;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDefinition($entity_type_id, $exception_on_invalid = TRUE) {
+    if (($entity_type = parent::getDefinition($entity_type_id, FALSE)) && class_exists($entity_type->getClass())) {
+      return $entity_type;
+    }
+    elseif (!$exception_on_invalid) {
+      return NULL;
+    }
+
+    throw new PluginNotFoundException($entity_type_id, sprintf('The "%s" entity type does not exist.', $entity_type_id));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function clearCachedDefinitions() {
+    parent::clearCachedDefinitions();
+    $this->handlers = [];
+    $this->classNameEntityTypeMap = [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function useCaches($use_caches = FALSE) {
+    parent::useCaches($use_caches);
+    if (!$use_caches) {
+      $this->handlers = [];
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function hasHandler($entity_type, $handler_type) {
+    if ($definition = $this->getDefinition($entity_type, FALSE)) {
+      return $definition->hasHandlerClass($handler_type);
+    }
+
+    return FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getStorage($entity_type) {
+    return $this->getHandler($entity_type, 'storage');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getListBuilder($entity_type) {
+    return $this->getHandler($entity_type, 'list_builder');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormObject($entity_type, $operation) {
+    if (!isset($this->handlers['form'][$operation][$entity_type])) {
+      if (!$class = $this->getDefinition($entity_type, TRUE)->getFormClass($operation)) {
+        throw new InvalidPluginDefinitionException($entity_type, sprintf('The "%s" entity type did not specify a "%s" form class.', $entity_type, $operation));
+      }
+
+      $form_object = $this->classResolver->getInstanceFromDefinition($class);
+
+      $form_object
+        ->setStringTranslation($this->translationManager)
+        ->setModuleHandler($this->moduleHandler)
+        ->setEntityTypeManager($this)
+        ->setOperation($operation)
+        // The entity manager cannot be injected due to a circular dependency.
+        // @todo Remove this set call.
+        ->setEntityManager(\Drupal::entityManager());
+      $this->handlers['form'][$operation][$entity_type] = $form_object;
+    }
+
+    return $this->handlers['form'][$operation][$entity_type];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRouteProviders($entity_type) {
+    if (!isset($this->handlers['route_provider'][$entity_type])) {
+      $route_provider_classes = $this->getDefinition($entity_type, TRUE)->getRouteProviderClasses();
+
+      foreach ($route_provider_classes as $type => $class) {
+        $this->handlers['route_provider'][$entity_type][$type] = $this->createHandlerInstance($class, $this->getDefinition($entity_type));
+      }
+    }
+
+    return isset($this->handlers['route_provider'][$entity_type]) ? $this->handlers['route_provider'][$entity_type] : [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getViewBuilder($entity_type) {
+    return $this->getHandler($entity_type, 'view_builder');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getAccessControlHandler($entity_type) {
+    return $this->getHandler($entity_type, 'access');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getHandler($entity_type, $handler_type) {
+    if (!isset($this->handlers[$handler_type][$entity_type])) {
+      $definition = $this->getDefinition($entity_type);
+      $class = $definition->getHandlerClass($handler_type);
+      if (!$class) {
+        throw new InvalidPluginDefinitionException($entity_type, sprintf('The "%s" entity type did not specify a %s handler.', $entity_type, $handler_type));
+      }
+      $this->handlers[$handler_type][$entity_type] = $this->createHandlerInstance($class, $definition);
+    }
+
+    return $this->handlers[$handler_type][$entity_type];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function createHandlerInstance($class, EntityTypeInterface $definition = NULL) {
+    if (is_subclass_of($class, 'Drupal\Core\Entity\EntityHandlerInterface')) {
+      $handler = $class::createInstance($this->container, $definition);
+    }
+    else {
+      $handler = new $class($definition);
+    }
+    if (method_exists($handler, 'setModuleHandler')) {
+      $handler->setModuleHandler($this->moduleHandler);
+    }
+    if (method_exists($handler, 'setStringTranslation')) {
+      $handler->setStringTranslation($this->translationManager);
+    }
+
+    return $handler;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Entity/EntityTypeManagerInterface.php b/core/lib/Drupal/Core/Entity/EntityTypeManagerInterface.php
new file mode 100644
index 0000000..f58d000
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/EntityTypeManagerInterface.php
@@ -0,0 +1,146 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\EntityTypeManagerInterface.
+ */
+
+namespace Drupal\Core\Entity;
+
+use Drupal\Component\Plugin\Discovery\CachedDiscoveryInterface;
+use Drupal\Component\Plugin\PluginManagerInterface;
+
+/**
+ * Provides an interface for entity type managers.
+ */
+interface EntityTypeManagerInterface extends PluginManagerInterface, CachedDiscoveryInterface {
+
+  /**
+   * Creates a new access control handler instance.
+   *
+   * @param string $entity_type
+   *   The entity type for this access control handler.
+   *
+   * @return \Drupal\Core\Entity\EntityAccessControlHandlerInterface.
+   *   A access control handler instance.
+   */
+  public function getAccessControlHandler($entity_type);
+
+  /**
+   * Creates a new storage instance.
+   *
+   * @param string $entity_type
+   *   The entity type for this storage.
+   *
+   * @return \Drupal\Core\Entity\EntityStorageInterface
+   *   A storage instance.
+   *
+   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+   */
+  public function getStorage($entity_type);
+
+  /**
+   * Creates a new view builder instance.
+   *
+   * @param string $entity_type
+   *   The entity type for this view builder.
+   *
+   * @return \Drupal\Core\Entity\EntityViewBuilderInterface.
+   *   A view builder instance.
+   */
+  public function getViewBuilder($entity_type);
+
+  /**
+   * Creates a new entity list builder.
+   *
+   * @param string $entity_type
+   *   The entity type for this list builder.
+   *
+   * @return \Drupal\Core\Entity\EntityListBuilderInterface
+   *   An entity list builder instance.
+   */
+  public function getListBuilder($entity_type);
+
+  /**
+   * Creates a new form instance.
+   *
+   * @param string $entity_type
+   *   The entity type for this form.
+   * @param string $operation
+   *   The name of the operation to use, e.g., 'default'.
+   *
+   * @return \Drupal\Core\Entity\EntityFormInterface
+   *   A form instance.
+   */
+  public function getFormObject($entity_type, $operation);
+
+  /**
+   * Gets all route provider instances.
+   *
+   * @param string $entity_type
+   *   The entity type for this route providers.
+   *
+   * @return \Drupal\Core\Entity\Routing\EntityRouteProviderInterface[]
+   */
+  public function getRouteProviders($entity_type);
+
+  /**
+   * Checks whether a certain entity type has a certain handler.
+   *
+   * @param string $entity_type
+   *   The name of the entity type.
+   * @param string $handler_type
+   *   The name of the handler.
+   *
+   * @return bool
+   *   Returns TRUE if the entity type has the handler, else FALSE.
+   */
+  public function hasHandler($entity_type, $handler_type);
+
+  /**
+   * Creates a new handler instance for a entity type and handler type.
+   *
+   * @param string $entity_type
+   *   The entity type for this controller.
+   * @param string $handler_type
+   *   The controller type to create an instance for.
+   *
+   * @return object
+   *   A handler instance.
+   *
+   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+   */
+  public function getHandler($entity_type, $handler_type);
+
+  /**
+   * Creates new handler instance.
+   *
+   * Usually \Drupal\Core\Entity\EntityManagerInterface::getHandler() is
+   * preferred since that method has additional checking that the class exists
+   * and has static caches.
+   *
+   * @param mixed $class
+   *   The handler class to instantiate.
+   * @param \Drupal\Core\Entity\EntityTypeInterface $definition
+   *   The entity type definition.
+   *
+   * @return object
+   *   A handler instance.
+   */
+  public function createHandlerInstance($class, EntityTypeInterface $definition = NULL);
+
+  /**
+   * {@inheritdoc}
+   *
+   * @return \Drupal\Core\Entity\EntityTypeInterface|null
+   */
+  public function getDefinition($entity_type_id, $exception_on_invalid = TRUE);
+
+  /**
+   * {@inheritdoc}
+   *
+   * @return \Drupal\Core\Entity\EntityTypeInterface[]
+   */
+  public function getDefinitions();
+
+}
diff --git a/core/lib/Drupal/Core/Entity/EntityTypeRepository.php b/core/lib/Drupal/Core/Entity/EntityTypeRepository.php
new file mode 100644
index 0000000..d8d2015
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/EntityTypeRepository.php
@@ -0,0 +1,142 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\EntityTypeRepository.
+ */
+
+namespace Drupal\Core\Entity;
+
+use Drupal\Core\Config\Entity\ConfigEntityTypeInterface;
+use Drupal\Core\Entity\Exception\AmbiguousEntityClassException;
+use Drupal\Core\Entity\Exception\NoCorrespondingEntityClassException;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+
+/**
+ * @todo.
+ */
+class EntityTypeRepository implements EntityTypeRepositoryInterface {
+
+  use StringTranslationTrait;
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * Contains cached mappings of class names to entity types.
+   *
+   * @var array
+   */
+  protected $classNameEntityTypeMap = [];
+
+  /**
+   * Constructs a new EntityTypeRepository.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
+   */
+  public function __construct(EntityTypeManagerInterface $entity_type_manager) {
+    $this->entityTypeManager = $entity_type_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function loadEntityByUuid($entity_type_id, $uuid) {
+    $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
+
+    if (!$uuid_key = $entity_type->getKey('uuid')) {
+      throw new EntityStorageException("Entity type $entity_type_id does not support UUIDs.");
+    }
+
+    $entities = $this->entityTypeManager->getStorage($entity_type_id)->loadByProperties(array($uuid_key => $uuid));
+
+    return reset($entities);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function loadEntityByConfigTarget($entity_type_id, $target) {
+    $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
+
+    // For configuration entities, the config target is given by the entity ID.
+    // @todo Consider adding a method to allow entity types to indicate the
+    //   target identifier key rather than hard-coding this check. Issue:
+    //   https://www.drupal.org/node/2412983.
+    if ($entity_type instanceof ConfigEntityTypeInterface) {
+      $entity = $this->entityTypeManager->getStorage($entity_type_id)->load($target);
+    }
+
+    // For content entities, the config target is given by the UUID.
+    else {
+      $entity = $this->loadEntityByUuid($entity_type_id, $target);
+    }
+
+    return $entity;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getEntityTypeLabels($group = FALSE) {
+    $options = array();
+    $definitions = $this->entityTypeManager->getDefinitions();
+
+    foreach ($definitions as $entity_type_id => $definition) {
+      if ($group) {
+        $options[$definition->getGroupLabel()][$entity_type_id] = $definition->getLabel();
+      }
+      else {
+        $options[$entity_type_id] = $definition->getLabel();
+      }
+    }
+
+    if ($group) {
+      foreach ($options as &$group_options) {
+        // Sort the list alphabetically by group label.
+        array_multisort($group_options, SORT_ASC, SORT_NATURAL);
+      }
+
+      // Make sure that the 'Content' group is situated at the top.
+      $content = $this->t('Content', array(), array('context' => 'Entity type group'));
+      $options = array($content => $options[$content]) + $options;
+    }
+
+    return $options;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getEntityTypeFromClass($class_name) {
+    // Check the already calculated classes first.
+    if (isset($this->classNameEntityTypeMap[$class_name])) {
+      return $this->classNameEntityTypeMap[$class_name];
+    }
+
+    $same_class = 0;
+    $entity_type_id = NULL;
+    foreach ($this->entityTypeManager->getDefinitions() as $entity_type) {
+      if ($entity_type->getOriginalClass() == $class_name  || $entity_type->getClass() == $class_name) {
+        $entity_type_id = $entity_type->id();
+        if ($same_class++) {
+          throw new AmbiguousEntityClassException($class_name);
+        }
+      }
+    }
+
+    // Return the matching entity type ID if there is one.
+    if ($entity_type_id) {
+      $this->classNameEntityTypeMap[$class_name] = $entity_type_id;
+      return $entity_type_id;
+    }
+
+    throw new NoCorrespondingEntityClassException($class_name);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Entity/EntityTypeRepositoryInterface.php b/core/lib/Drupal/Core/Entity/EntityTypeRepositoryInterface.php
new file mode 100644
index 0000000..cc7f8e8
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/EntityTypeRepositoryInterface.php
@@ -0,0 +1,89 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\EntityTypeRepositoryInterface.
+ */
+
+namespace Drupal\Core\Entity;
+
+/**
+ * @todo.
+ */
+interface EntityTypeRepositoryInterface {
+
+  /**
+   * Loads an entity by UUID.
+   *
+   * Note that some entity types may not support UUIDs.
+   *
+   * @param string $entity_type_id
+   *   The entity type ID to load from.
+   * @param string $uuid
+   *   The UUID of the entity to load.
+   *
+   * @return \Drupal\Core\Entity\EntityInterface|null
+   *   The entity object, or NULL if there is no entity with the given UUID.
+   *
+   * @throws \Drupal\Core\Entity\EntityStorageException
+   *   Thrown in case the requested entity type does not support UUIDs.
+   */
+  public function loadEntityByUuid($entity_type_id, $uuid);
+
+  /**
+   * Loads an entity by the config target identifier.
+   *
+   * @param string $entity_type_id
+   *   The entity type ID to load from.
+   * @param string $target
+   *   The configuration target to load, as returned from
+   *   \Drupal\Core\Entity\EntityInterface::getConfigTarget().
+   *
+   * @return \Drupal\Core\Entity\EntityInterface|null
+   *   The entity object, or NULL if there is no entity with the given config
+   *   target identifier.
+   *
+   * @throws \Drupal\Core\Entity\EntityStorageException
+   *   Thrown if the target identifier is a UUID but the entity type does not
+   *   support UUIDs.
+   *
+   * @see \Drupal\Core\Entity\EntityInterface::getConfigTarget()
+   */
+  public function loadEntityByConfigTarget($entity_type_id, $target);
+
+  /**
+   * Builds a list of entity type labels suitable for a Form API options list.
+   *
+   * @param bool $group
+   *   (optional) Whether to group entity types by plugin group (e.g. 'content',
+   *   'config'). Defaults to FALSE.
+   *
+   * @return array
+   *   An array of entity type labels, keyed by entity type name.
+   */
+  public function getEntityTypeLabels($group = FALSE);
+
+  /**
+   * Gets the entity type ID based on the class that is called on.
+   *
+   * Compares the class this is called on against the known entity classes
+   * and returns the entity type ID of a direct match or a subclass as fallback,
+   * to support entity type definitions that were altered.
+   *
+   * @param string $class_name
+   *   Class name to use for searching the entity type ID.
+   *
+   * @return string
+   *   The entity type ID.
+   *
+   * @throws \Drupal\Core\Entity\Exception\AmbiguousEntityClassException
+   *   Thrown when multiple subclasses correspond to the called class.
+   * @throws \Drupal\Core\Entity\Exception\NoCorrespondingEntityClassException
+   *   Thrown when no entity class corresponds to the called class.
+   *
+   * @see \Drupal\Core\Entity\Entity::load()
+   * @see \Drupal\Core\Entity\Entity::loadMultiple()
+   */
+  public function getEntityTypeFromClass($class_name);
+
+}
diff --git a/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php b/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php
index 7c8799a..e60beff 100644
--- a/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php
+++ b/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php
@@ -8,13 +8,14 @@
 namespace Drupal\Core\Plugin;
 
 use Drupal\Component\Plugin\Discovery\CachedDiscoveryInterface;
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Plugin\Discovery\CacheBackendTrait;
 use Drupal\Component\Plugin\Discovery\DiscoveryCachedTrait;
 use Drupal\Core\Plugin\Discovery\ContainerDerivativeDiscoveryDecorator;
 use Drupal\Component\Plugin\PluginManagerBase;
 use Drupal\Component\Plugin\PluginManagerInterface;
 use Drupal\Component\Utility\NestedArray;
 use Drupal\Core\Cache\Cache;
-use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
 use Drupal\Core\Plugin\Factory\ContainerFactory;
@@ -27,13 +28,7 @@
 class DefaultPluginManager extends PluginManagerBase implements PluginManagerInterface, CachedDiscoveryInterface {
 
   use DiscoveryCachedTrait;
-
-  /**
-   * Cache backend instance.
-   *
-   * @var \Drupal\Core\Cache\CacheBackendInterface
-   */
-  protected $cacheBackend;
+  use CacheBackendTrait;
 
   /**
    * The cache key.
@@ -81,13 +76,6 @@ class DefaultPluginManager extends PluginManagerBase implements PluginManagerInt
   protected $defaults = array();
 
   /**
-   * Flag whether persistent caches should be used.
-   *
-   * @var bool
-   */
-  protected $useCaches = TRUE;
-
-  /**
    * The name of the annotation that contains the plugin definition.
    *
    * @var string
@@ -237,30 +225,6 @@ public function useCaches($use_caches = FALSE) {
   }
 
   /**
-   * Fetches from the cache backend, respecting the use caches flag.
-   *
-   * @see \Drupal\Core\Cache\CacheBackendInterface::get()
-   */
-  protected function cacheGet($cid) {
-    if ($this->useCaches && $this->cacheBackend) {
-      return $this->cacheBackend->get($cid);
-    }
-    return FALSE;
-  }
-
-  /**
-   * Stores data in the persistent cache, respecting the use caches flag.
-   *
-   * @see \Drupal\Core\Cache\CacheBackendInterface::set()
-   */
-  protected function cacheSet($cid, $data, $expire = Cache::PERMANENT, array $tags = array()) {
-    if ($this->cacheBackend && $this->useCaches) {
-      $this->cacheBackend->set($cid, $data, $expire, $tags);
-    }
-  }
-
-
-  /**
    * Performs extra processing on plugin definitions.
    *
    * By default we add defaults for the type to the definition. If a type has
diff --git a/core/lib/Drupal/Core/Plugin/Discovery/CacheBackendTrait.php b/core/lib/Drupal/Core/Plugin/Discovery/CacheBackendTrait.php
new file mode 100644
index 0000000..ee1bdc3
--- /dev/null
+++ b/core/lib/Drupal/Core/Plugin/Discovery/CacheBackendTrait.php
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Plugin\Discovery\CacheBackendTrait.
+ */
+
+namespace Drupal\Core\Plugin\Discovery;
+
+use Drupal\Core\Cache\Cache;
+use Drupal\Core\Cache\CacheBackendInterface;
+
+/**
+ * @todo.
+ */
+trait CacheBackendTrait {
+
+  /**
+   * Cache backend instance.
+   *
+   * @var \Drupal\Core\Cache\CacheBackendInterface
+   */
+  protected $cacheBackend;
+
+  /**
+   * Flag whether persistent caches should be used.
+   *
+   * @var bool
+   */
+  protected $useCaches = TRUE;
+
+  /**
+   * Fetches from the cache backend, respecting the use caches flag.
+   *
+   * @see \Drupal\Core\Cache\CacheBackendInterface::get()
+   */
+  protected function cacheGet($cid) {
+    if ($this->useCaches && $this->cacheBackend) {
+      return $this->cacheBackend->get($cid);
+    }
+    return FALSE;
+  }
+
+  /**
+   * Stores data in the persistent cache, respecting the use caches flag.
+   *
+   * @see \Drupal\Core\Cache\CacheBackendInterface::set()
+   */
+  protected function cacheSet($cid, $data, $expire = Cache::PERMANENT, array $tags = []) {
+    if ($this->cacheBackend && $this->useCaches) {
+      $this->cacheBackend->set($cid, $data, $expire, $tags);
+    }
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityManagerTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityManagerTest.php
index 9a67a26..20c8ed7 100644
--- a/core/tests/Drupal/Tests/Core/Entity/EntityManagerTest.php
+++ b/core/tests/Drupal/Tests/Core/Entity/EntityManagerTest.php
@@ -5,24 +5,23 @@
  * Contains \Drupal\Tests\Core\Entity\EntityManagerTest.
  */
 
-namespace Drupal\Tests\Core\Entity {
+namespace Drupal\Tests\Core\Entity;
 
-use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
 use Drupal\Component\Plugin\Exception\PluginNotFoundException;
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
-use Drupal\Core\Config\Entity\ConfigEntityStorage;
-use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\Core\Config\Entity\ConfigEntityStorageInterface;
 use Drupal\Core\Entity\ContentEntityInterface;
 use Drupal\Core\Entity\ContentEntityTypeInterface;
 use Drupal\Core\Entity\DynamicallyFieldableEntityStorageInterface;
-use Drupal\Core\Entity\EntityHandlerBase;
-use Drupal\Core\Entity\EntityHandlerInterface;
+use Drupal\Core\Entity\EntityDisplayManagerInterface;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityManager;
-use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Entity\EntityTypeBundleManagerInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Entity\EntityTypeRepositoryInterface;
 use Drupal\Core\Entity\FieldableEntityInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Field\BaseFieldDefinition;
@@ -63,13 +62,6 @@ class EntityManagerTest extends UnitTestCase {
   protected $entityType;
 
   /**
-   * The plugin discovery.
-   *
-   * @var \Drupal\Component\Plugin\Discovery\DiscoveryInterface|\Prophecy\Prophecy\ProphecyInterface
-   */
-  protected $discovery;
-
-  /**
    * The dependency injection container.
    *
    * @var \Symfony\Component\DependencyInjection\ContainerInterface|\Prophecy\Prophecy\ProphecyInterface
@@ -126,14 +118,40 @@ class EntityManagerTest extends UnitTestCase {
   protected $eventDispatcher;
 
   /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface|\Prophecy\Prophecy\ProphecyInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * The entity type repository.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeRepositoryInterface|\Prophecy\Prophecy\ProphecyInterface
+   */
+  protected $entityTypeRepository;
+
+  /**
+   * The entity type bundle manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeBundleManagerInterface|\Prophecy\Prophecy\ProphecyInterface
+   */
+  protected $entityTypeBundleManager;
+
+  /**
+   * The entity display manager.
+   *
+   * @var \Drupal\Core\Entity\EntityDisplayManagerInterface|\Prophecy\Prophecy\ProphecyInterface
+   */
+  protected $entityDisplayManager;
+
+  /**
    * {@inheritdoc}
    */
   protected function setUp() {
     parent::setUp();
 
     $this->moduleHandler = $this->prophesize(ModuleHandlerInterface::class);
-    $this->moduleHandler->getImplementations('entity_type_build')->willReturn([]);
-    $this->moduleHandler->alter('entity_type', Argument::type('array'))->willReturn(NULL);
     $this->moduleHandler->alter('entity_base_field_info', Argument::type('array'), Argument::any())->willReturn(NULL);
     $this->moduleHandler->alter('entity_bundle_field_info', Argument::type('array'), Argument::any(), Argument::type('string'))->willReturn(NULL);
 
@@ -155,16 +173,15 @@ protected function setUp() {
     $this->keyValueFactory = $this->prophesize(KeyValueFactoryInterface::class);
 
     $this->container = $this->prophesize(ContainerInterface::class);
-    $this->container->get('cache_tags.invalidator')->willReturn($this->cacheTagsInvalidator->reveal());
     $this->container->get('typed_data_manager')->willReturn($this->typedDataManager->reveal());
     \Drupal::setContainer($this->container->reveal());
 
-    $this->discovery = $this->prophesize(DiscoveryInterface::class);
-    $translation_manager = $this->prophesize(TranslationInterface::class);
+    $this->entityTypeManager = $this->prophesize(EntityTypeManagerInterface::class);
+    $this->entityTypeRepository = $this->prophesize(EntityTypeRepositoryInterface::class);
+    $this->entityTypeBundleManager = $this->prophesize(EntityTypeBundleManagerInterface::class);
+    $this->entityDisplayManager = $this->prophesize(EntityDisplayManagerInterface::class);
 
-    $this->entityManager = new TestEntityManager(new \ArrayObject(), $this->moduleHandler->reveal(), $this->cacheBackend->reveal(), $this->languageManager->reveal(), $translation_manager->reveal(), $this->getClassResolverStub(), $this->typedDataManager->reveal(), $this->keyValueFactory->reveal(), $this->eventDispatcher->reveal());
-    $this->entityManager->setContainer($this->container->reveal());
-    $this->entityManager->setDiscovery($this->discovery->reveal());
+    $this->entityManager = new TestEntityManager($this->moduleHandler->reveal(), $this->cacheBackend->reveal(), $this->languageManager->reveal(), $this->typedDataManager->reveal(), $this->keyValueFactory->reveal(), $this->eventDispatcher->reveal(), $this->entityTypeManager->reveal(), $this->entityTypeRepository->reveal(), $this->entityTypeBundleManager->reveal(), $this->entityDisplayManager->reveal());
   }
 
   /**
@@ -187,7 +204,7 @@ protected function setUpEntityManager($definitions = array()) {
       $definitions[$key] = $entity_type->reveal();
     }
 
-    $this->discovery->getDefinition(Argument::cetera())
+    $this->entityTypeManager->getDefinition(Argument::cetera())
       ->will(function ($args) use ($definitions) {
         $entity_type_id = $args[0];
         $exception_on_invalid = $args[1];
@@ -199,7 +216,7 @@ protected function setUpEntityManager($definitions = array()) {
         }
         else throw new PluginNotFoundException($entity_type_id);
     });
-    $this->discovery->getDefinitions()->willReturn($definitions);
+    $this->entityTypeManager->getDefinitions()->willReturn($definitions);
   }
 
   /**
@@ -212,255 +229,15 @@ public function testClearCachedDefinitions() {
     $this->setUpEntityManager();
 
     $this->typedDataManager->clearCachedDefinitions()->shouldBeCalled();
+    $this->entityTypeManager->clearCachedDefinitions()->shouldBeCalled();
 
-    $this->cacheTagsInvalidator->invalidateTags(['entity_types'])->shouldBeCalled();
-    $this->cacheTagsInvalidator->invalidateTags(['entity_bundles'])->shouldBeCalled();
     $this->cacheTagsInvalidator->invalidateTags(['entity_field_info'])->shouldBeCalled();
+    $this->container->get('cache_tags.invalidator')->willReturn($this->cacheTagsInvalidator->reveal())->shouldBeCalled();
 
     $this->entityManager->clearCachedDefinitions();
   }
 
   /**
-   * Tests the processDefinition() method.
-   *
-   * @covers ::processDefinition
-   *
-   * @expectedException \Drupal\Core\Entity\Exception\InvalidLinkTemplateException
-   * @expectedExceptionMessage Link template 'canonical' for entity type 'apple' must start with a leading slash, the current link template is 'path/to/apple'
-   */
-  public function testProcessDefinition() {
-    $apple = $this->prophesize(EntityTypeInterface::class);
-    $this->setUpEntityManager(array('apple' => $apple));
-
-    $apple->getLinkTemplates()->willReturn(['canonical' => 'path/to/apple']);
-
-    $definition = $apple->reveal();
-    $this->entityManager->processDefinition($definition, 'apple');
-  }
-
-  /**
-   * Tests the getDefinition() method.
-   *
-   * @covers ::getDefinition
-   *
-   * @dataProvider providerTestGetDefinition
-   */
-  public function testGetDefinition($entity_type_id, $expected) {
-    $entity = $this->prophesize(EntityTypeInterface::class);
-
-    $this->setUpEntityManager(array(
-      'apple' => $entity,
-      'banana' => $entity,
-    ));
-
-    $entity_type = $this->entityManager->getDefinition($entity_type_id, FALSE);
-    if ($expected) {
-      $this->assertInstanceOf(EntityTypeInterface::class, $entity_type);
-    }
-    else {
-      $this->assertNull($entity_type);
-    }
-  }
-
-  /**
-   * Provides test data for testGetDefinition().
-   *
-   * @return array
-   *   Test data.
-   */
-  public function providerTestGetDefinition() {
-    return array(
-      array('apple', TRUE),
-      array('banana', TRUE),
-      array('pear', FALSE),
-    );
-  }
-
-  /**
-   * Tests the getDefinition() method with an invalid definition.
-   *
-   * @covers ::getDefinition
-   *
-   * @expectedException \Drupal\Component\Plugin\Exception\PluginNotFoundException
-   * @expectedExceptionMessage The "pear" entity type does not exist.
-   */
-  public function testGetDefinitionInvalidException() {
-    $this->setUpEntityManager();
-
-    $this->entityManager->getDefinition('pear', TRUE);
-  }
-
-  /**
-   * Tests the hasHandler() method.
-   *
-   * @covers ::hasHandler
-   *
-   * @dataProvider providerTestHasHandler
-   */
-  public function testHasHandler($entity_type_id, $expected) {
-    $apple = $this->prophesize(EntityTypeInterface::class);
-    $apple->hasHandlerClass('storage')->willReturn(TRUE);
-
-    $banana = $this->prophesize(EntityTypeInterface::class);
-    $banana->hasHandlerClass('storage')->willReturn(FALSE);
-
-    $this->setUpEntityManager(array(
-      'apple' => $apple,
-      'banana' => $banana,
-    ));
-
-    $entity_type = $this->entityManager->hasHandler($entity_type_id, 'storage');
-    $this->assertSame($expected, $entity_type);
-  }
-
-  /**
-   * Provides test data for testHasHandler().
-   *
-   * @return array
-   *   Test data.
-   */
-  public function providerTestHasHandler() {
-    return array(
-      array('apple', TRUE),
-      array('banana', FALSE),
-      array('pear', FALSE),
-    );
-  }
-
-  /**
-   * Tests the getStorage() method.
-   *
-   * @covers ::getStorage
-   */
-  public function testGetStorage() {
-    $class = $this->getTestHandlerClass();
-    $entity = $this->prophesize(EntityTypeInterface::class);
-    $entity->getHandlerClass('storage')->willReturn($class);
-    $this->setUpEntityManager(array('test_entity_type' => $entity));
-
-    $this->assertInstanceOf($class, $this->entityManager->getStorage('test_entity_type'));
-  }
-
-  /**
-   * Tests the getListBuilder() method.
-   *
-   * @covers ::getListBuilder
-   */
-  public function testGetListBuilder() {
-    $class = $this->getTestHandlerClass();
-    $entity = $this->prophesize(EntityTypeInterface::class);
-    $entity->getHandlerClass('list_builder')->willReturn($class);
-    $this->setUpEntityManager(array('test_entity_type' => $entity));
-
-    $this->assertInstanceOf($class, $this->entityManager->getListBuilder('test_entity_type'));
-  }
-
-  /**
-   * Tests the getViewBuilder() method.
-   *
-   * @covers ::getViewBuilder
-   */
-  public function testGetViewBuilder() {
-    $class = $this->getTestHandlerClass();
-    $entity = $this->prophesize(EntityTypeInterface::class);
-    $entity->getHandlerClass('view_builder')->willReturn($class);
-    $this->setUpEntityManager(array('test_entity_type' => $entity));
-
-    $this->assertInstanceOf($class, $this->entityManager->getViewBuilder('test_entity_type'));
-  }
-
-  /**
-   * Tests the getAccessControlHandler() method.
-   *
-   * @covers ::getAccessControlHandler
-   */
-  public function testGetAccessControlHandler() {
-    $class = $this->getTestHandlerClass();
-    $entity = $this->prophesize(EntityTypeInterface::class);
-    $entity->getHandlerClass('access')->willReturn($class);
-    $this->setUpEntityManager(array('test_entity_type' => $entity));
-
-    $this->assertInstanceOf($class, $this->entityManager->getAccessControlHandler('test_entity_type'));
-  }
-
-  /**
-   * Tests the getFormObject() method.
-   *
-   * @covers ::getFormObject
-   */
-  public function testGetFormObject() {
-    $apple = $this->prophesize(EntityTypeInterface::class);
-    $apple->getFormClass('default')->willReturn(TestEntityForm::class);
-
-    $banana = $this->prophesize(EntityTypeInterface::class);
-    $banana->getFormClass('default')->willReturn(TestEntityFormInjected::class);
-
-    $this->setUpEntityManager(array(
-      'apple' => $apple,
-      'banana' => $banana,
-    ));
-
-    $apple_form = $this->entityManager->getFormObject('apple', 'default');
-    $this->assertInstanceOf(TestEntityForm::class, $apple_form);
-    $this->assertAttributeInstanceOf(ModuleHandlerInterface::class, 'moduleHandler', $apple_form);
-    $this->assertAttributeInstanceOf(TranslationInterface::class, 'stringTranslation', $apple_form);
-
-    $banana_form = $this->entityManager->getFormObject('banana', 'default');
-    $this->assertInstanceOf(TestEntityFormInjected::class, $banana_form);
-    $this->assertAttributeEquals('yellow', 'color', $banana_form);
-
-  }
-
-  /**
-   * Tests the getFormObject() method with an invalid operation.
-   *
-   * @covers ::getFormObject
-   *
-   * @expectedException \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
-   */
-  public function testGetFormObjectInvalidOperation() {
-    $entity = $this->prophesize(EntityTypeInterface::class);
-    $entity->getFormClass('edit')->willReturn('');
-    $this->setUpEntityManager(array('test_entity_type' => $entity));
-
-    $this->entityManager->getFormObject('test_entity_type', 'edit');
-  }
-
-  /**
-   * Tests the getHandler() method.
-   *
-   * @covers ::getHandler
-   */
-  public function testGetHandler() {
-    $class = $this->getTestHandlerClass();
-    $apple = $this->prophesize(EntityTypeInterface::class);
-    $apple->getHandlerClass('storage')->willReturn($class);
-
-    $this->setUpEntityManager(array(
-      'apple' => $apple,
-    ));
-
-    $apple_controller = $this->entityManager->getHandler('apple', 'storage');
-    $this->assertInstanceOf($class, $apple_controller);
-    $this->assertAttributeInstanceOf(ModuleHandlerInterface::class, 'moduleHandler', $apple_controller);
-    $this->assertAttributeInstanceOf(TranslationInterface::class, 'stringTranslation', $apple_controller);
-  }
-
-  /**
-   * Tests the getHandler() method when no controller is defined.
-   *
-   * @covers ::getHandler
-   *
-   * @expectedException \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
-   */
-  public function testGetHandlerMissingHandler() {
-    $entity = $this->prophesize(EntityTypeInterface::class);
-    $entity->getHandlerClass('storage')->willReturn('');
-    $this->setUpEntityManager(array('test_entity_type' => $entity));
-    $this->entityManager->getHandler('test_entity_type', 'storage');
-  }
-
-  /**
    * Tests the getBaseFieldDefinitions() method.
    *
    * @covers ::getBaseFieldDefinitions
@@ -615,8 +392,6 @@ public function testGetBaseFieldDefinitionsWithCaching() {
           ->shouldBeCalled();
       })
       ->shouldBeCalled();
-    $this->cacheBackend->get('entity_type')->willReturn(FALSE);
-    $this->cacheBackend->set('entity_type', Argument::any(), Cache::PERMANENT, ['entity_types'])->shouldBeCalled();
 
     $this->assertSame($expected, $this->entityManager->getBaseFieldDefinitions('test_entity_type'));
     $this->entityManager->testClearEntityFieldInfo();
@@ -639,8 +414,6 @@ public function testGetFieldDefinitionsWithCaching() {
     $this->cacheBackend->get('entity_bundle_field_definitions:test_entity_type:test_bundle:en')
       ->willReturn(FALSE)
       ->shouldBeCalledTimes(1);
-    $this->cacheBackend->get('entity_type')->willReturn(FALSE);
-    $this->cacheBackend->set('entity_type', Argument::any(), Cache::PERMANENT, ['entity_types'])->shouldBeCalled();
     $this->cacheBackend->set('entity_bundle_field_definitions:test_entity_type:test_bundle:en', Argument::any(), Cache::PERMANENT, ['entity_types', 'entity_field_info'])
       ->will(function ($args) {
         $data = (object) ['data' => $args[1]];
@@ -680,9 +453,7 @@ public function testGetFieldStorageDefinitionsWithCaching() {
       ->willReturn((object) ['data' => ['id' => $expected['id']]])
       ->shouldBeCalledTimes(2);
     $this->cacheBackend->get('entity_field_storage_definitions:test_entity_type:en')->willReturn(FALSE);
-    $this->cacheBackend->get('entity_type')->willReturn(FALSE);
 
-    $this->cacheBackend->set('entity_type', Argument::any(), Cache::PERMANENT, ['entity_types'])->shouldBeCalled();
     $this->cacheBackend->set('entity_field_storage_definitions:test_entity_type:en', Argument::any(), Cache::PERMANENT, ['entity_types', 'entity_field_info'])
       ->will(function () use ($expected) {
         $this->get('entity_field_storage_definitions:test_entity_type:en')
@@ -787,8 +558,9 @@ protected function setUpEntityWithFieldDefinition($custom_invoke_all = FALSE, $f
     $this->entityType = $this->prophesize(EntityTypeInterface::class);
     $this->setUpEntityManager(array('test_entity_type' => $this->entityType, 'base_field_override' => $override_entity_type));
 
-    $override_entity_type->getClass()->willReturn($entity_class);
-    $override_entity_type->getHandlerClass('storage')->willReturn(TestConfigEntityStorage::class);
+    $storage = $this->prophesize(ConfigEntityStorageInterface::class);
+    $storage->loadMultiple(Argument::type('array'))->willReturn([]);
+    $this->entityTypeManager->getStorage('base_field_override')->willReturn($storage->reveal());
 
     $this->entityType->getClass()->willReturn($entity_class);
     $this->entityType->getKeys()->willReturn($entity_keys + ['default_langcode' => 'default_langcode']);
@@ -809,6 +581,7 @@ public function testClearCachedFieldDefinitions() {
     $this->setUpEntityManager();
 
     $this->cacheTagsInvalidator->invalidateTags(['entity_field_info'])->shouldBeCalled();
+    $this->container->get('cache_tags.invalidator')->willReturn($this->cacheTagsInvalidator->reveal())->shouldBeCalled();
 
     $this->typedDataManager->clearCachedDefinitions()->shouldBeCalled();
 
@@ -816,160 +589,6 @@ public function testClearCachedFieldDefinitions() {
   }
 
   /**
-   * Tests the clearCachedBundles() method.
-   *
-   * @covers ::clearCachedBundles
-   */
-  public function testClearCachedBundles() {
-    $this->setUpEntityManager();
-
-    $this->typedDataManager->clearCachedDefinitions()->shouldBeCalled();
-
-    $this->cacheTagsInvalidator->invalidateTags(['entity_bundles'])->shouldBeCalled();
-
-    $this->entityManager->clearCachedBundles();
-  }
-
-  /**
-   * Tests the getBundleInfo() method.
-   *
-   * @covers ::getBundleInfo
-   *
-   * @dataProvider providerTestGetBundleInfo
-   */
-  public function testGetBundleInfo($entity_type_id, $expected) {
-    $this->moduleHandler->invokeAll('entity_bundle_info')->willReturn([]);
-    $this->moduleHandler->alter('entity_bundle_info', Argument::type('array'))->willReturn(NULL);
-
-    $apple = $this->prophesize(EntityTypeInterface::class);
-    $apple->getLabel()->willReturn('Apple');
-    $apple->getBundleOf()->willReturn(NULL);
-
-    $banana = $this->prophesize(EntityTypeInterface::class);
-    $banana->getLabel()->willReturn('Banana');
-    $banana->getBundleOf()->willReturn(NULL);
-
-    $this->setUpEntityManager(array(
-      'apple' => $apple,
-      'banana' => $banana,
-    ));
-
-    $bundle_info = $this->entityManager->getBundleInfo($entity_type_id);
-    $this->assertSame($expected, $bundle_info);
-  }
-
-  /**
-   * Provides test data for testGetBundleInfo().
-   *
-   * @return array
-   *   Test data.
-   */
-  public function providerTestGetBundleInfo() {
-    return array(
-      array('apple', array(
-        'apple' => array(
-          'label' => 'Apple',
-        ),
-      )),
-      array('banana', array(
-        'banana' => array(
-          'label' => 'Banana',
-        ),
-      )),
-      array('pear', array()),
-    );
-  }
-
-  /**
-   * Tests the getAllBundleInfo() method.
-   *
-   * @covers ::getAllBundleInfo
-   */
-  public function testGetAllBundleInfo() {
-    $this->moduleHandler->invokeAll('entity_bundle_info')->willReturn([]);
-    $this->moduleHandler->alter('entity_bundle_info', Argument::type('array'))->willReturn(NULL);
-
-    $apple = $this->prophesize(EntityTypeInterface::class);
-    $apple->getLabel()->willReturn('Apple');
-    $apple->getBundleOf()->willReturn(NULL);
-
-    $banana = $this->prophesize(EntityTypeInterface::class);
-    $banana->getLabel()->willReturn('Banana');
-    $banana->getBundleOf()->willReturn(NULL);
-
-    $this->setUpEntityManager(array(
-      'apple' => $apple,
-      'banana' => $banana,
-    ));
-
-    $this->cacheBackend->get('entity_bundle_info:en')->willReturn(FALSE);
-    $this->cacheBackend->get('entity_type')->willReturn(FALSE);
-    $this->cacheBackend->set('entity_type', Argument::any(), Cache::PERMANENT, ['entity_types'])->shouldBeCalled();
-    $this->cacheBackend->set('entity_bundle_info:en', Argument::any(), Cache::PERMANENT, ['entity_types', 'entity_bundles'])
-      ->will(function () {
-        $this->get('entity_bundle_info:en')
-          ->willReturn((object) ['data' => 'cached data'])
-          ->shouldBeCalled();
-      })
-      ->shouldBeCalled();
-
-    $this->cacheTagsInvalidator->invalidateTags(['entity_types'])->shouldBeCalled();
-    $this->cacheTagsInvalidator->invalidateTags(['entity_bundles'])->shouldBeCalled();
-    $this->cacheTagsInvalidator->invalidateTags(['entity_field_info'])->shouldBeCalled();
-
-    $this->typedDataManager->clearCachedDefinitions()->shouldBeCalled();
-
-    $expected = array(
-      'apple' => array(
-        'apple' => array(
-          'label' => 'Apple',
-        ),
-      ),
-      'banana' => array(
-        'banana' => array(
-          'label' => 'Banana',
-        ),
-      ),
-    );
-    $bundle_info = $this->entityManager->getAllBundleInfo();
-    $this->assertSame($expected, $bundle_info);
-
-    $bundle_info = $this->entityManager->getAllBundleInfo();
-    $this->assertSame($expected, $bundle_info);
-
-    $this->entityManager->clearCachedDefinitions();
-
-    $bundle_info = $this->entityManager->getAllBundleInfo();
-    $this->assertSame('cached data', $bundle_info);
-  }
-
-  /**
-   * Tests the getEntityTypeLabels() method.
-   *
-   * @covers ::getEntityTypeLabels
-   */
-  public function testGetEntityTypeLabels() {
-    $apple = $this->prophesize(EntityTypeInterface::class);
-    $apple->getLabel()->willReturn('Apple');
-    $apple->getBundleOf()->willReturn(NULL);
-
-    $banana = $this->prophesize(EntityTypeInterface::class);
-    $banana->getLabel()->willReturn('Banana');
-    $banana->getBundleOf()->willReturn(NULL);
-
-    $this->setUpEntityManager(array(
-      'apple' => $apple,
-      'banana' => $banana,
-    ));
-
-    $expected = array(
-      'apple' => 'Apple',
-      'banana' => 'Banana',
-    );
-    $this->assertSame($expected, $this->entityManager->getEntityTypeLabels());
-  }
-
-  /**
    * Tests the getTranslationFromContext() method.
    *
    * @covers ::getTranslationFromContext
@@ -1052,8 +671,7 @@ function testGetExtraFields() {
    * @covers ::getFieldMap
    */
   public function testGetFieldMap() {
-    $this->moduleHandler->invokeAll('entity_bundle_info')->willReturn([]);
-    $this->moduleHandler->alter('entity_bundle_info', Argument::type('array'))->willReturn(NULL);
+    $this->entityTypeBundleManager->getBundleInfo('test_entity_type')->willReturn([])->shouldBeCalled();
 
     // Set up a content entity type.
     $entity_type = $this->prophesize(ContentEntityTypeInterface::class);
@@ -1093,31 +711,21 @@ public function testGetFieldMap() {
 
     $entity_type->getClass()->willReturn($entity_class);
     $entity_type->getKeys()->willReturn(['default_langcode' => 'default_langcode']);
-    $entity_type->getBundleOf()->willReturn(NULL);
-    $entity_type->id()->willReturn('test_entity_type');
     $entity_type->isSubclassOf(FieldableEntityInterface::class)->willReturn(TRUE);
     $entity_type->isTranslatable()->shouldBeCalled();
     $entity_type->getProvider()->shouldBeCalled();
 
     $non_content_entity_type->isSubclassOf(FieldableEntityInterface::class)->willReturn(FALSE);
-    $non_content_entity_type->getBundleOf()->willReturn(NULL);
-    $non_content_entity_type->getLabel()->shouldBeCalled();
 
     $override_entity_type->isSubclassOf(FieldableEntityInterface::class)->willReturn(FALSE);
-    $override_entity_type->getHandlerClass('storage')->willReturn(TestConfigEntityStorage::class);
-    $override_entity_type->getBundleOf()->willReturn(NULL);
-    $override_entity_type->getLabel()->shouldBeCalled();
 
-    // Set up the module handler to return two bundles for the fieldable entity
-    // type.
-    $this->moduleHandler->alter(Argument::type('string'), Argument::type('array'));
+    // Set up the entity type bundle manager to return two bundles for the
+    // fieldable entity type.
+    $this->entityTypeBundleManager->getBundleInfo('test_entity_type')->willReturn([
+      'first_bundle' => 'first_bundle',
+      'second_bundle' => 'second_bundle',
+    ])->shouldBeCalled();
     $this->moduleHandler->getImplementations('entity_base_field_info')->willReturn([]);
-    $this->moduleHandler->invokeAll('entity_bundle_info')->willReturn([
-      'test_entity_type' => [
-        'first_bundle' => [],
-        'second_bundle' => [],
-      ],
-    ]);
 
     $expected = array(
       'test_entity_type' => array(
@@ -1166,20 +774,17 @@ public function testGetFieldMapByFieldType() {
     $entity_type = $this->prophesize(ContentEntityTypeInterface::class);
     $entity_class = EntityManagerTestEntity::class;
 
-    // Set up the module handler to return two bundles for the fieldable entity
-    // type.
-    $this->moduleHandler->getImplementations('entity_base_field_info')->willReturn([]);
-    $this->moduleHandler->invokeAll('entity_bundle_info')->willReturn([
-      'test_entity_type' => [
-        'first_bundle' => [],
-        'second_bundle' => [],
-      ],
-    ]);
-    $this->moduleHandler->alter('entity_bundle_info', Argument::type('array'))->willReturn(NULL);
+    // Set up the entity type bundle manager to return two bundles for the
+    // fieldable entity type.
+    $this->entityTypeBundleManager->getBundleInfo('test_entity_type')->willReturn([
+      'first_bundle' => 'first_bundle',
+      'second_bundle' => 'second_bundle',
+    ])->shouldBeCalled();
+    $this->moduleHandler->getImplementations('entity_base_field_info')->willReturn([])->shouldBeCalled();
 
     // Define an ID field definition as a base field.
     $id_definition = $this->prophesize(FieldDefinitionInterface::class);
-    $id_definition->getType()->willReturn('integer');
+    $id_definition->getType()->willReturn('integer')->shouldBeCalled();
     $base_field_definitions = array(
       'id' => $id_definition->reveal(),
     );
@@ -1187,7 +792,7 @@ public function testGetFieldMapByFieldType() {
 
     // Set up the stored bundle field map.
     $key_value_store = $this->prophesize(KeyValueStoreInterface::class);
-    $this->keyValueFactory->get('entity.definitions.bundle_field_map')->willReturn($key_value_store->reveal());
+    $this->keyValueFactory->get('entity.definitions.bundle_field_map')->willReturn($key_value_store->reveal())->shouldBeCalled();
     $key_value_store->getAll()->willReturn([
       'test_entity_type' => [
         'by_bundle' => [
@@ -1195,7 +800,7 @@ public function testGetFieldMapByFieldType() {
           'bundles' => ['second_bundle' => 'second_bundle'],
         ],
       ],
-    ]);
+    ])->shouldBeCalled();
 
     // Mock the base field definition override.
     $override_entity_type = $this->prophesize(EntityTypeInterface::class);
@@ -1205,19 +810,13 @@ public function testGetFieldMapByFieldType() {
       'base_field_override' => $override_entity_type,
     ));
 
-    $entity_type->getClass()->willReturn($entity_class);
-    $entity_type->getKeys()->willReturn(['default_langcode' => 'default_langcode']);
-    $entity_type->id()->willReturn('test_entity_type');
-    $entity_type->isSubclassOf(FieldableEntityInterface::class)->willReturn(TRUE);
-    $entity_type->getBundleOf()->shouldBeCalled();
+    $entity_type->getClass()->willReturn($entity_class)->shouldBeCalled();
+    $entity_type->getKeys()->willReturn(['default_langcode' => 'default_langcode'])->shouldBeCalled();
+    $entity_type->isSubclassOf(FieldableEntityInterface::class)->willReturn(TRUE)->shouldBeCalled();
     $entity_type->isTranslatable()->shouldBeCalled();
     $entity_type->getProvider()->shouldBeCalled();
 
-    $override_entity_type->getClass()->willReturn($entity_class);
-    $override_entity_type->isSubclassOf(FieldableEntityInterface::class)->willReturn(FALSE);
-    $override_entity_type->getHandlerClass('storage')->willReturn(TestConfigEntityStorage::class);
-    $override_entity_type->getBundleOf()->shouldBeCalled();
-    $override_entity_type->getLabel()->shouldBeCalled();
+    $override_entity_type->isSubclassOf(FieldableEntityInterface::class)->willReturn(FALSE)->shouldBeCalled();
 
     $integerFields = $this->entityManager->getFieldMapByFieldType('integer');
     $this->assertCount(1, $integerFields['test_entity_type']);
@@ -1242,17 +841,12 @@ public function testOnFieldDefinitionCreateNewField() {
     $field_definition->getName()->willReturn('test_field');
     $field_definition->getType()->willReturn('test_type');
 
-    $class = $this->getMockClass(DynamicallyFieldableEntityStorageInterface::class);
-    $entity = $this->prophesize(EntityTypeInterface::class);
-    $entity->getHandlerClass('storage')->willReturn($class);
-    $this->setUpEntityManager(array('test_entity_type' => $entity));
+    $storage = $this->prophesize(DynamicallyFieldableEntityStorageInterface::class);
+    $storage->onFieldDefinitionCreate($field_definition->reveal())->shouldBeCalledTimes(1);
+    $this->entityTypeManager->getStorage('test_entity_type')->willReturn($storage->reveal());
 
-    // The entity manager will instantiate a new object with the given class
-    // name. Define the mock expectations on that.
-    $storage = $this->entityManager->getStorage('test_entity_type');
-    $storage->expects($this->once())
-      ->method('onFieldDefinitionCreate')
-      ->with($field_definition->reveal());
+    $entity = $this->prophesize(EntityTypeInterface::class);
+    $this->setUpEntityManager(array('test_entity_type' => $entity));
 
     // Set up the stored bundle field map.
     $key_value_store = $this->prophesize(KeyValueStoreInterface::class);
@@ -1277,17 +871,12 @@ public function testOnFieldDefinitionCreateExistingField() {
     $field_definition->getTargetBundle()->willReturn('test_bundle');
     $field_definition->getName()->willReturn('test_field');
 
-    $class = $this->getMockClass(DynamicallyFieldableEntityStorageInterface::class);
-    $entity = $this->prophesize(EntityTypeInterface::class);
-    $entity->getHandlerClass('storage')->willReturn($class);
-    $this->setUpEntityManager(array('test_entity_type' => $entity));
+    $storage = $this->prophesize(DynamicallyFieldableEntityStorageInterface::class);
+    $storage->onFieldDefinitionCreate($field_definition->reveal())->shouldBeCalledTimes(1);
+    $this->entityTypeManager->getStorage('test_entity_type')->willReturn($storage->reveal());
 
-    // The entity manager will instantiate a new object with the given class
-    // name. Define the mock expectations on that.
-    $storage = $this->entityManager->getStorage('test_entity_type');
-    $storage->expects($this->once())
-      ->method('onFieldDefinitionCreate')
-      ->with($field_definition->reveal());
+    $entity = $this->prophesize(EntityTypeInterface::class);
+    $this->setUpEntityManager(array('test_entity_type' => $entity));
 
     // Set up the stored bundle field map.
     $key_value_store = $this->prophesize(KeyValueStoreInterface::class);
@@ -1316,17 +905,12 @@ public function testOnFieldDefinitionUpdate() {
     $field_definition = $this->prophesize(FieldDefinitionInterface::class);
     $field_definition->getTargetEntityTypeId()->willReturn('test_entity_type');
 
-    $class = $this->getMockClass(DynamicallyFieldableEntityStorageInterface::class);
-    $entity = $this->prophesize(EntityTypeInterface::class);
-    $entity->getHandlerClass('storage')->willReturn($class);
-    $this->setUpEntityManager(array('test_entity_type' => $entity));
+    $storage = $this->prophesize(DynamicallyFieldableEntityStorageInterface::class);
+    $storage->onFieldDefinitionUpdate($field_definition->reveal(), $field_definition->reveal())->shouldBeCalledTimes(1);
+    $this->entityTypeManager->getStorage('test_entity_type')->willReturn($storage->reveal());
 
-    // The entity manager will instantiate a new object with the given class
-    // name. Define the mock expectations on that.
-    $storage = $this->entityManager->getStorage('test_entity_type');
-    $storage->expects($this->once())
-      ->method('onFieldDefinitionUpdate')
-      ->with($field_definition->reveal());
+    $entity = $this->prophesize(EntityTypeInterface::class);
+    $this->setUpEntityManager(array('test_entity_type' => $entity));
 
     $this->entityManager->onFieldDefinitionUpdate($field_definition->reveal(), $field_definition->reveal());
   }
@@ -1340,17 +924,12 @@ public function testOnFieldDefinitionDeleteMultipleBundles() {
     $field_definition->getTargetBundle()->willReturn('test_bundle');
     $field_definition->getName()->willReturn('test_field');
 
-    $class = $this->getMockClass(DynamicallyFieldableEntityStorageInterface::class);
-    $entity = $this->prophesize(EntityTypeInterface::class);
-    $entity->getHandlerClass('storage')->willReturn($class);
-    $this->setUpEntityManager(array('test_entity_type' => $entity));
+    $storage = $this->prophesize(DynamicallyFieldableEntityStorageInterface::class);
+    $storage->onFieldDefinitionDelete($field_definition->reveal())->shouldBeCalledTimes(1);
+    $this->entityTypeManager->getStorage('test_entity_type')->willReturn($storage->reveal());
 
-    // The entity manager will instantiate a new object with the given class
-    // name. Define the mock expectations on that.
-    $storage = $this->entityManager->getStorage('test_entity_type');
-    $storage->expects($this->once())
-      ->method('onFieldDefinitionDelete')
-      ->with($field_definition->reveal());
+    $entity = $this->prophesize(EntityTypeInterface::class);
+    $this->setUpEntityManager(array('test_entity_type' => $entity));
 
     // Set up the stored bundle field map.
     $key_value_store = $this->prophesize(KeyValueStoreInterface::class);
@@ -1386,17 +965,12 @@ public function testOnFieldDefinitionDeleteSingleBundles() {
     $field_definition->getTargetBundle()->willReturn('test_bundle');
     $field_definition->getName()->willReturn('test_field');
 
-    $class = $this->getMockClass(DynamicallyFieldableEntityStorageInterface::class);
-    $entity = $this->prophesize(EntityTypeInterface::class);
-    $entity->getHandlerClass('storage')->willReturn($class);
-    $this->setUpEntityManager(array('test_entity_type' => $entity));
+    $storage = $this->prophesize(DynamicallyFieldableEntityStorageInterface::class);
+    $storage->onFieldDefinitionDelete($field_definition->reveal())->shouldBeCalledTimes(1);
+    $this->entityTypeManager->getStorage('test_entity_type')->willReturn($storage->reveal());
 
-    // The entity manager will instantiate a new object with the given class
-    // name. Define the mock expectations on that.
-    $storage = $this->entityManager->getStorage('test_entity_type');
-    $storage->expects($this->once())
-      ->method('onFieldDefinitionDelete')
-      ->with($field_definition->reveal());
+    $entity = $this->prophesize(EntityTypeInterface::class);
+    $this->setUpEntityManager(array('test_entity_type' => $entity));
 
     // Set up the stored bundle field map.
     $key_value_store = $this->prophesize(KeyValueStoreInterface::class);
@@ -1418,106 +992,9 @@ public function testOnFieldDefinitionDeleteSingleBundles() {
     $this->entityManager->onFieldDefinitionDelete($field_definition->reveal());
   }
 
-  /**
-   * @covers ::getEntityTypeFromClass
-   */
-  public function testGetEntityTypeFromClass() {
-    $apple = $this->prophesize(EntityTypeInterface::class);
-    $banana = $this->prophesize(EntityTypeInterface::class);
-
-    $this->setUpEntityManager(array(
-      'apple' => $apple,
-      'banana' => $banana,
-    ));
-
-    $apple->getOriginalClass()->willReturn('\Drupal\apple\Entity\Apple');
-
-    $banana->getOriginalClass()->willReturn('\Drupal\banana\Entity\Banana');
-    $banana->getClass()->willReturn('\Drupal\mango\Entity\Mango');
-    $banana->id()
-      ->willReturn('banana')
-      ->shouldBeCalledTimes(2);
-
-    $entity_type_id = $this->entityManager->getEntityTypeFromClass('\Drupal\banana\Entity\Banana');
-    $this->assertSame('banana', $entity_type_id);
-    $entity_type_id = $this->entityManager->getEntityTypeFromClass('\Drupal\mango\Entity\Mango');
-    $this->assertSame('banana', $entity_type_id);
-  }
-
-  /**
-   * @covers ::getEntityTypeFromClass
-   *
-   * @expectedException \Drupal\Core\Entity\Exception\NoCorrespondingEntityClassException
-   * @expectedExceptionMessage The \Drupal\pear\Entity\Pear class does not correspond to an entity type.
-   */
-  public function testGetEntityTypeFromClassNoMatch() {
-    $apple = $this->prophesize(EntityTypeInterface::class);
-    $banana = $this->prophesize(EntityTypeInterface::class);
-
-    $this->setUpEntityManager(array(
-      'apple' => $apple,
-      'banana' => $banana,
-    ));
-
-    $apple->getOriginalClass()->willReturn('\Drupal\apple\Entity\Apple');
-    $banana->getOriginalClass()->willReturn('\Drupal\banana\Entity\Banana');
-
-    $this->entityManager->getEntityTypeFromClass('\Drupal\pear\Entity\Pear');
-  }
-
-  /**
-   * @covers ::getEntityTypeFromClass
-   *
-   * @expectedException \Drupal\Core\Entity\Exception\AmbiguousEntityClassException
-   * @expectedExceptionMessage Multiple entity types found for \Drupal\apple\Entity\Apple.
-   */
-  public function testGetEntityTypeFromClassAmbiguous() {
-    $boskoop = $this->prophesize(EntityTypeInterface::class);
-    $boskoop->getOriginalClass()->willReturn('\Drupal\apple\Entity\Apple');
-    $boskoop->id()->willReturn('boskop');
-
-    $gala = $this->prophesize(EntityTypeInterface::class);
-    $gala->getOriginalClass()->willReturn('\Drupal\apple\Entity\Apple');
-    $gala->id()->willReturn('gala');
-
-    $this->setUpEntityManager(array(
-      'boskoop' => $boskoop,
-      'gala' => $gala,
-    ));
-
-    $this->entityManager->getEntityTypeFromClass('\Drupal\apple\Entity\Apple');
-  }
-
-  /**
-   * @covers ::getRouteProviders
-   */
-  public function testGetRouteProviders() {
-    $apple = $this->prophesize(EntityTypeInterface::class);
-    $apple->getRouteProviderClasses()->willReturn(['default' => TestRouteProvider::class]);
-
-    $this->setUpEntityManager(array(
-      'apple' => $apple,
-    ));
-
-    $apple_route_provider = $this->entityManager->getRouteProviders('apple');
-    $this->assertInstanceOf(TestRouteProvider::class, $apple_route_provider['default']);
-    $this->assertAttributeInstanceOf(ModuleHandlerInterface::class, 'moduleHandler', $apple_route_provider['default']);
-    $this->assertAttributeInstanceOf(TranslationInterface::class, 'stringTranslation', $apple_route_provider['default']);
-  }
-
-  /**
-   * Gets a mock controller class name.
-   *
-   * @return string
-   *   A mock controller class name.
-   */
-  protected function getTestHandlerClass() {
-    return get_class($this->getMockForAbstractClass(EntityHandlerBase::class));
-  }
-
 }
 
-/*
+/**
  * Provides a content entity with dummy static method implementations.
  */
 abstract class EntityManagerTestEntity implements \Iterator, ContentEntityInterface {
@@ -1560,16 +1037,6 @@ public static function bundleFieldDefinitions(EntityTypeInterface $entity_type,
 class TestEntityManager extends EntityManager {
 
   /**
-   * Sets the discovery for the manager.
-   *
-   * @param \Drupal\Component\Plugin\Discovery\DiscoveryInterface $discovery
-   *   The discovery object.
-   */
-  public function setDiscovery(DiscoveryInterface $discovery) {
-    $this->discovery = $discovery;
-  }
-
-  /**
    * Allows the static caches to be cleared.
    */
   public function testClearEntityFieldInfo() {
@@ -1579,153 +1046,3 @@ public function testClearEntityFieldInfo() {
   }
 
 }
-
-/**
- * Provides a test entity handler that uses injection.
- */
-class TestEntityHandlerInjected implements EntityHandlerInterface {
-
-  /**
-   * The color of the entity type.
-   *
-   * @var string
-   */
-  protected $color;
-
-  /**
-   * Constructs a new TestEntityHandlerInjected.
-   *
-   * @param string $color
-   *   The color of the entity type.
-   */
-  public function __construct($color) {
-    $this->color = $color;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
-    return new static('yellow');
-  }
-
-}
-
-/**
- * Provides a test entity form.
- */
-class TestEntityForm extends EntityHandlerBase {
-
-  /**
-   * The entity manager.
-   *
-   * @var \Drupal\Tests\Core\Entity\TestEntityManager
-   */
-  protected $entityManager;
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getBaseFormId() {
-    return 'the_base_form_id';
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getFormId() {
-    return 'the_form_id';
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function setEntity(EntityInterface $entity) {
-    return $this;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function setOperation($operation) {
-    return $this;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function setEntityManager(EntityManagerInterface $entity_manager) {
-    $this->entityManager = $entity_manager;
-    return $this;
-  }
-
-}
-
-/**
- * Provides a test entity form that uses injection.
- */
-class TestEntityFormInjected extends TestEntityForm implements ContainerInjectionInterface {
-
-  /**
-   * The color of the entity type.
-   *
-   * @var string
-   */
-  protected $color;
-
-  /**
-   * Constructs a new TestEntityFormInjected.
-   *
-   * @param string $color
-   *   The color of the entity type.
-   */
-  public function __construct($color) {
-    $this->color = $color;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function create(ContainerInterface $container) {
-    return new static('yellow');
-  }
-
-}
-
-/**
- * Provides a test entity route provider.
- */
-class TestRouteProvider extends EntityHandlerBase {
-
-}
-
-
-/**
- * Provides a test config entity storage for base field overrides.
- */
-class TestConfigEntityStorage extends ConfigEntityStorage {
-
-  public function __construct($entity_type) {
-  }
-
-  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
-    return new static(
-      $entity_type
-    );
-  }
-
-  public function loadMultiple(array $ids = NULL) {
-    return array();
-  }
-}
-
-}
-
-namespace {
-
-  /**
-   * Implements hook_entity_type_build().
-   */
-  function entity_manager_test_module_entity_type_build() {
-  }
-}
diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityTypeBundleManagerTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityTypeBundleManagerTest.php
new file mode 100644
index 0000000..0479d90
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Entity/EntityTypeBundleManagerTest.php
@@ -0,0 +1,273 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Entity\EntityTypeBundleManagerTest.
+ */
+
+namespace Drupal\Tests\Core\Entity;
+
+use Drupal\Component\Plugin\Exception\PluginNotFoundException;
+use Drupal\Core\Cache\Cache;
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityTypeBundleManager;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Language\Language;
+use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\Core\TypedData\TypedDataManager;
+use Drupal\Tests\UnitTestCase;
+use Prophecy\Argument;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * @coversDefaultClass \Drupal\Core\Entity\EntityTypeBundleManager
+ * @group Entity
+ */
+class EntityTypeBundleManagerTest extends UnitTestCase {
+
+  /**
+   * The module handler.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface|\Prophecy\Prophecy\ProphecyInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * The cache backend to use.
+   *
+   * @var \Drupal\Core\Cache\CacheBackendInterface|\Prophecy\Prophecy\ProphecyInterface
+   */
+  protected $cacheBackend;
+
+  /**
+   * The cache tags invalidator.
+   *
+   * @var \Drupal\Core\Cache\CacheTagsInvalidatorInterface|\Prophecy\Prophecy\ProphecyInterface
+   */
+  protected $cacheTagsInvalidator;
+
+  /**
+   * The typed data manager.
+   *
+   * @var \Drupal\Core\TypedData\TypedDataManager|\Prophecy\Prophecy\ProphecyInterface
+   */
+  protected $typedDataManager;
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface|\Prophecy\Prophecy\ProphecyInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * The language manager.
+   *
+   * @var \Drupal\Core\Language\LanguageManagerInterface
+   */
+  protected $languageManager;
+
+  /**
+   * The entity type bundle manager under test.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeBundleManager
+   */
+  protected $entityTypeBundleManager;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->moduleHandler = $this->prophesize(ModuleHandlerInterface::class);
+    $this->moduleHandler->getImplementations('entity_type_build')->willReturn([]);
+    $this->moduleHandler->alter('entity_type', Argument::type('array'))->willReturn(NULL);
+
+    $this->cacheBackend = $this->prophesize(CacheBackendInterface::class);
+
+    $this->entityTypeManager = $this->prophesize(EntityTypeManagerInterface::class);
+
+    $this->cacheTagsInvalidator = $this->prophesize(CacheTagsInvalidatorInterface::class);
+
+    $language = new Language(['id' => 'en']);
+    $this->languageManager = $this->prophesize(LanguageManagerInterface::class);
+    $this->languageManager->getCurrentLanguage()->willReturn($language);
+    $this->languageManager->getLanguages()->willReturn(['en' => (object) ['id' => 'en']]);
+
+    $this->typedDataManager = $this->prophesize(TypedDataManager::class);
+
+    $this->cacheBackend = $this->prophesize(CacheBackendInterface::class);
+
+    $container = $this->prophesize(ContainerInterface::class);
+    $container->get('cache_tags.invalidator')->willReturn($this->cacheTagsInvalidator->reveal());
+    \Drupal::setContainer($container->reveal());
+
+    $this->entityTypeBundleManager = new EntityTypeBundleManager($this->entityTypeManager->reveal(), $this->languageManager->reveal(), $this->moduleHandler->reveal(), $this->typedDataManager->reveal(), $this->cacheBackend->reveal());
+  }
+
+  /**
+   * Sets up the entity type manager to be tested.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeInterface[]|\Prophecy\Prophecy\ProphecyInterface[] $definitions
+   *   (optional) An array of entity type definitions.
+   */
+  protected function setUpEntityTypeDefinitions($definitions = []) {
+    $class = $this->getMockClass(EntityInterface::class);
+    foreach ($definitions as $key => $entity_type) {
+      // \Drupal\Core\Entity\EntityTypeInterface::getLinkTemplates() is called
+      // by \Drupal\Core\Entity\EntityManager::processDefinition() so it must
+      // always be mocked.
+      $entity_type->getLinkTemplates()->willReturn([]);
+
+      // Give the entity type a legitimate class to return.
+      $entity_type->getClass()->willReturn($class);
+
+      $definitions[$key] = $entity_type->reveal();
+    }
+
+    $this->entityTypeManager->getDefinition(Argument::cetera())
+      ->will(function ($args) use ($definitions) {
+        $entity_type_id = $args[0];
+        $exception_on_invalid = $args[1];
+        if (isset($definitions[$entity_type_id])) {
+          return $definitions[$entity_type_id];
+        }
+        elseif (!$exception_on_invalid) {
+          return NULL;
+        }
+        else throw new PluginNotFoundException($entity_type_id);
+      });
+    $this->entityTypeManager->getDefinitions()->willReturn($definitions);
+
+  }
+
+  /**
+   * Tests the clearCachedBundles() method.
+   *
+   * @covers ::clearCachedBundles
+   */
+  public function testClearCachedBundles() {
+    $this->setUpEntityTypeDefinitions();
+
+    $this->typedDataManager->clearCachedDefinitions()->shouldBeCalled();
+
+    $this->cacheTagsInvalidator->invalidateTags(['entity_bundles'])->shouldBeCalled();
+
+    $this->entityTypeBundleManager->clearCachedBundles();
+  }
+
+  /**
+   * Tests the getBundleInfo() method.
+   *
+   * @covers ::getBundleInfo
+   *
+   * @dataProvider providerTestGetBundleInfo
+   */
+  public function testGetBundleInfo($entity_type_id, $expected) {
+    $this->moduleHandler->invokeAll('entity_bundle_info')->willReturn([]);
+    $this->moduleHandler->alter('entity_bundle_info', Argument::type('array'))->willReturn(NULL);
+
+    $apple = $this->prophesize(EntityTypeInterface::class);
+    $apple->getLabel()->willReturn('Apple');
+    $apple->getBundleOf()->willReturn(NULL);
+
+    $banana = $this->prophesize(EntityTypeInterface::class);
+    $banana->getLabel()->willReturn('Banana');
+    $banana->getBundleOf()->willReturn(NULL);
+
+    $this->setUpEntityTypeDefinitions(array(
+      'apple' => $apple,
+      'banana' => $banana,
+    ));
+
+    $bundle_info = $this->entityTypeBundleManager->getBundleInfo($entity_type_id);
+    $this->assertSame($expected, $bundle_info);
+  }
+
+  /**
+   * Provides test data for testGetBundleInfo().
+   *
+   * @return array
+   *   Test data.
+   */
+  public function providerTestGetBundleInfo() {
+    return array(
+      array('apple', array(
+        'apple' => array(
+          'label' => 'Apple',
+        ),
+      )),
+      array('banana', array(
+        'banana' => array(
+          'label' => 'Banana',
+        ),
+      )),
+      array('pear', array()),
+    );
+  }
+
+  /**
+   * Tests the getAllBundleInfo() method.
+   *
+   * @covers ::getAllBundleInfo
+   */
+  public function testGetAllBundleInfo() {
+    $this->moduleHandler->invokeAll('entity_bundle_info')->willReturn([]);
+    $this->moduleHandler->alter('entity_bundle_info', Argument::type('array'))->willReturn(NULL);
+
+    $apple = $this->prophesize(EntityTypeInterface::class);
+    $apple->getLabel()->willReturn('Apple');
+    $apple->getBundleOf()->willReturn(NULL);
+
+    $banana = $this->prophesize(EntityTypeInterface::class);
+    $banana->getLabel()->willReturn('Banana');
+    $banana->getBundleOf()->willReturn(NULL);
+
+    $this->setUpEntityTypeDefinitions(array(
+      'apple' => $apple,
+      'banana' => $banana,
+    ));
+
+    $this->cacheBackend->get('entity_bundle_info:en')->willReturn(FALSE);
+    $this->cacheBackend->set('entity_bundle_info:en', Argument::any(), Cache::PERMANENT, ['entity_types', 'entity_bundles'])
+      ->will(function () {
+        $this->get('entity_bundle_info:en')
+          ->willReturn((object) ['data' => 'cached data'])
+          ->shouldBeCalled();
+      })
+      ->shouldBeCalled();
+
+    $this->cacheTagsInvalidator->invalidateTags(['entity_bundles'])->shouldBeCalled();
+
+    $this->typedDataManager->clearCachedDefinitions()->shouldBeCalled();
+
+    $expected = array(
+      'apple' => array(
+        'apple' => array(
+          'label' => 'Apple',
+        ),
+      ),
+      'banana' => array(
+        'banana' => array(
+          'label' => 'Banana',
+        ),
+      ),
+    );
+    $bundle_info = $this->entityTypeBundleManager->getAllBundleInfo();
+    $this->assertSame($expected, $bundle_info);
+
+    $bundle_info = $this->entityTypeBundleManager->getAllBundleInfo();
+    $this->assertSame($expected, $bundle_info);
+
+    $this->entityTypeBundleManager->clearCachedBundles();
+
+    $bundle_info = $this->entityTypeBundleManager->getAllBundleInfo();
+    $this->assertSame('cached data', $bundle_info);
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityTypeManagerTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityTypeManagerTest.php
new file mode 100644
index 0000000..0023ca1
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Entity/EntityTypeManagerTest.php
@@ -0,0 +1,522 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Entity\EntityTypeManagerTest.
+ */
+
+namespace Drupal\Tests\Core\Entity {
+
+use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
+use Drupal\Component\Plugin\Exception\PluginNotFoundException;
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\Core\Entity\EntityHandlerBase;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\EntityTypeManager;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Drupal\Tests\UnitTestCase;
+use Prophecy\Argument;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * @coversDefaultClass \Drupal\Core\Entity\EntityTypeManager
+ * @group Entity
+ */
+class EntityTypeManagerTest extends UnitTestCase {
+
+  /**
+   * The entity type manager under test.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManager
+   */
+  protected $entityTypeManager;
+
+  /**
+   * The translation manager.
+   *
+   * @var \Drupal\Core\StringTranslation\TranslationInterface|\Prophecy\Prophecy\ProphecyInterface
+   */
+  protected $translationManager;
+
+  /**
+   * The plugin discovery.
+   *
+   * @var \Drupal\Component\Plugin\Discovery\DiscoveryInterface|\Prophecy\Prophecy\ProphecyInterface
+   */
+  protected $discovery;
+
+  /**
+   * The module handler.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface|\Prophecy\Prophecy\ProphecyInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * The cache backend.
+   *
+   * @var \Drupal\Core\Cache\CacheBackendInterface|\Prophecy\Prophecy\ProphecyInterface
+   */
+  protected $cacheBackend;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->moduleHandler = $this->prophesize(ModuleHandlerInterface::class);
+    $this->moduleHandler->getImplementations('entity_type_build')->willReturn([]);
+    $this->moduleHandler->alter('entity_type', Argument::type('array'))->willReturn(NULL);
+
+    $this->cacheBackend = $this->prophesize(CacheBackendInterface::class);
+    $this->translationManager = $this->prophesize(TranslationInterface::class);
+
+    $this->entityTypeManager = new TestEntityTypeManager(new \ArrayObject(), $this->moduleHandler->reveal(), $this->cacheBackend->reveal(), $this->translationManager->reveal(), $this->getClassResolverStub());
+    $this->discovery = $this->prophesize(DiscoveryInterface::class);
+    $this->entityTypeManager->setDiscovery($this->discovery->reveal());
+  }
+
+  /**
+   * Sets up the entity type manager to be tested.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeInterface[]|\Prophecy\Prophecy\ProphecyInterface[] $definitions
+   *   (optional) An array of entity type definitions.
+   */
+  protected function setUpEntityTypeDefinitions($definitions = []) {
+    $class = $this->getMockClass(EntityInterface::class);
+    foreach ($definitions as $key => $entity_type) {
+      // \Drupal\Core\Entity\EntityTypeInterface::getLinkTemplates() is called
+      // by \Drupal\Core\Entity\EntityManager::processDefinition() so it must
+      // always be mocked.
+      $entity_type->getLinkTemplates()->willReturn([]);
+
+      // Give the entity type a legitimate class to return.
+      $entity_type->getClass()->willReturn($class);
+
+      $definitions[$key] = $entity_type->reveal();
+    }
+
+    $this->discovery->getDefinition(Argument::cetera())
+      ->will(function ($args) use ($definitions) {
+        $entity_type_id = $args[0];
+        $exception_on_invalid = $args[1];
+        if (isset($definitions[$entity_type_id])) {
+          return $definitions[$entity_type_id];
+        }
+        elseif (!$exception_on_invalid) {
+          return NULL;
+        }
+        else throw new PluginNotFoundException($entity_type_id);
+      });
+    $this->discovery->getDefinitions()->willReturn($definitions);
+
+  }
+
+  /**
+   * Tests the hasHandler() method.
+   *
+   * @covers ::hasHandler
+   *
+   * @dataProvider providerTestHasHandler
+   */
+  public function testHasHandler($entity_type_id, $expected) {
+    $apple = $this->prophesize(EntityTypeInterface::class);
+    $apple->hasHandlerClass('storage')->willReturn(TRUE);
+
+    $banana = $this->prophesize(EntityTypeInterface::class);
+    $banana->hasHandlerClass('storage')->willReturn(FALSE);
+
+    $this->setUpEntityTypeDefinitions(array(
+      'apple' => $apple,
+      'banana' => $banana,
+    ));
+
+    $entity_type = $this->entityTypeManager->hasHandler($entity_type_id, 'storage');
+    $this->assertSame($expected, $entity_type);
+  }
+
+  /**
+   * Provides test data for testHasHandler().
+   *
+   * @return array
+   *   Test data.
+   */
+  public function providerTestHasHandler() {
+    return array(
+      array('apple', TRUE),
+      array('banana', FALSE),
+      array('pear', FALSE),
+    );
+  }
+
+  /**
+   * Tests the getStorage() method.
+   *
+   * @covers ::getStorage
+   */
+  public function testGetStorage() {
+    $class = $this->getTestHandlerClass();
+    $entity = $this->prophesize(EntityTypeInterface::class);
+    $entity->getHandlerClass('storage')->willReturn($class);
+    $this->setUpEntityTypeDefinitions(array('test_entity_type' => $entity));
+
+    $this->assertInstanceOf($class, $this->entityTypeManager->getStorage('test_entity_type'));
+  }
+
+  /**
+   * Tests the getListBuilder() method.
+   *
+   * @covers ::getListBuilder
+   */
+  public function testGetListBuilder() {
+    $class = $this->getTestHandlerClass();
+    $entity = $this->prophesize(EntityTypeInterface::class);
+    $entity->getHandlerClass('list_builder')->willReturn($class);
+    $this->setUpEntityTypeDefinitions(array('test_entity_type' => $entity));
+
+    $this->assertInstanceOf($class, $this->entityTypeManager->getListBuilder('test_entity_type'));
+  }
+
+  /**
+   * Tests the getViewBuilder() method.
+   *
+   * @covers ::getViewBuilder
+   */
+  public function testGetViewBuilder() {
+    $class = $this->getTestHandlerClass();
+    $entity = $this->prophesize(EntityTypeInterface::class);
+    $entity->getHandlerClass('view_builder')->willReturn($class);
+    $this->setUpEntityTypeDefinitions(array('test_entity_type' => $entity));
+
+    $this->assertInstanceOf($class, $this->entityTypeManager->getViewBuilder('test_entity_type'));
+  }
+
+  /**
+   * Tests the getAccessControlHandler() method.
+   *
+   * @covers ::getAccessControlHandler
+   */
+  public function testGetAccessControlHandler() {
+    $class = $this->getTestHandlerClass();
+    $entity = $this->prophesize(EntityTypeInterface::class);
+    $entity->getHandlerClass('access')->willReturn($class);
+    $this->setUpEntityTypeDefinitions(array('test_entity_type' => $entity));
+
+    $this->assertInstanceOf($class, $this->entityTypeManager->getAccessControlHandler('test_entity_type'));
+  }
+
+  /**
+   * Tests the getFormObject() method.
+   *
+   * @covers ::getFormObject
+   */
+  public function testGetFormObject() {
+    $entity_manager = $this->prophesize(EntityManagerInterface::class);
+    $container = $this->prophesize(ContainerInterface::class);
+    $container->get('entity.manager')->willReturn($entity_manager->reveal());
+    \Drupal::setContainer($container->reveal());
+
+    $apple = $this->prophesize(EntityTypeInterface::class);
+    $apple->getFormClass('default')->willReturn(TestEntityForm::class);
+
+    $banana = $this->prophesize(EntityTypeInterface::class);
+    $banana->getFormClass('default')->willReturn(TestEntityFormInjected::class);
+
+    $this->setUpEntityTypeDefinitions(array(
+      'apple' => $apple,
+      'banana' => $banana,
+    ));
+
+    $apple_form = $this->entityTypeManager->getFormObject('apple', 'default');
+    $this->assertInstanceOf(TestEntityForm::class, $apple_form);
+    $this->assertAttributeInstanceOf(ModuleHandlerInterface::class, 'moduleHandler', $apple_form);
+    $this->assertAttributeInstanceOf(TranslationInterface::class, 'stringTranslation', $apple_form);
+
+    $banana_form = $this->entityTypeManager->getFormObject('banana', 'default');
+    $this->assertInstanceOf(TestEntityFormInjected::class, $banana_form);
+    $this->assertAttributeEquals('yellow', 'color', $banana_form);
+
+  }
+
+  /**
+   * Tests the getFormObject() method with an invalid operation.
+   *
+   * @covers ::getFormObject
+   *
+   * @expectedException \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+   */
+  public function testGetFormObjectInvalidOperation() {
+    $entity = $this->prophesize(EntityTypeInterface::class);
+    $entity->getFormClass('edit')->willReturn('');
+    $this->setUpEntityTypeDefinitions(array('test_entity_type' => $entity));
+
+    $this->entityTypeManager->getFormObject('test_entity_type', 'edit');
+  }
+
+  /**
+   * Tests the getHandler() method.
+   *
+   * @covers ::getHandler
+   */
+  public function testGetHandler() {
+    $class = $this->getTestHandlerClass();
+    $apple = $this->prophesize(EntityTypeInterface::class);
+    $apple->getHandlerClass('storage')->willReturn($class);
+
+    $this->setUpEntityTypeDefinitions(array(
+      'apple' => $apple,
+    ));
+
+    $apple_controller = $this->entityTypeManager->getHandler('apple', 'storage');
+    $this->assertInstanceOf($class, $apple_controller);
+    $this->assertAttributeInstanceOf(ModuleHandlerInterface::class, 'moduleHandler', $apple_controller);
+    $this->assertAttributeInstanceOf(TranslationInterface::class, 'stringTranslation', $apple_controller);
+  }
+
+  /**
+   * Tests the getHandler() method when no controller is defined.
+   *
+   * @covers ::getHandler
+   *
+   * @expectedException \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+   */
+  public function testGetHandlerMissingHandler() {
+    $entity = $this->prophesize(EntityTypeInterface::class);
+    $entity->getHandlerClass('storage')->willReturn('');
+    $this->setUpEntityTypeDefinitions(array('test_entity_type' => $entity));
+    $this->entityTypeManager->getHandler('test_entity_type', 'storage');
+  }
+
+  /**
+   * @covers ::getRouteProviders
+   */
+  public function testGetRouteProviders() {
+    $apple = $this->prophesize(EntityTypeInterface::class);
+    $apple->getRouteProviderClasses()->willReturn(['default' => TestRouteProvider::class]);
+
+    $this->setUpEntityTypeDefinitions(array(
+      'apple' => $apple,
+    ));
+
+    $apple_route_provider = $this->entityTypeManager->getRouteProviders('apple');
+    $this->assertInstanceOf(TestRouteProvider::class, $apple_route_provider['default']);
+    $this->assertAttributeInstanceOf(ModuleHandlerInterface::class, 'moduleHandler', $apple_route_provider['default']);
+    $this->assertAttributeInstanceOf(TranslationInterface::class, 'stringTranslation', $apple_route_provider['default']);
+  }
+
+  /**
+   * Tests the processDefinition() method.
+   *
+   * @covers ::processDefinition
+   *
+   * @expectedException \Drupal\Core\Entity\Exception\InvalidLinkTemplateException
+   * @expectedExceptionMessage Link template 'canonical' for entity type 'apple' must start with a leading slash, the current link template is 'path/to/apple'
+   */
+  public function testProcessDefinition() {
+    $apple = $this->prophesize(EntityTypeInterface::class);
+    $this->setUpEntityTypeDefinitions(array('apple' => $apple));
+
+    $apple->getLinkTemplates()->willReturn(['canonical' => 'path/to/apple']);
+
+    $definition = $apple->reveal();
+    $this->entityTypeManager->processDefinition($definition, 'apple');
+  }
+
+  /**
+   * Tests the getDefinition() method.
+   *
+   * @covers ::getDefinition
+   *
+   * @dataProvider providerTestGetDefinition
+   */
+  public function testGetDefinition($entity_type_id, $expected) {
+    $entity = $this->prophesize(EntityTypeInterface::class);
+
+    $this->setUpEntityTypeDefinitions(array(
+      'apple' => $entity,
+      'banana' => $entity,
+    ));
+
+    $entity_type = $this->entityTypeManager->getDefinition($entity_type_id, FALSE);
+    if ($expected) {
+      $this->assertInstanceOf(EntityTypeInterface::class, $entity_type);
+    }
+    else {
+      $this->assertNull($entity_type);
+    }
+  }
+
+  /**
+   * Provides test data for testGetDefinition().
+   *
+   * @return array
+   *   Test data.
+   */
+  public function providerTestGetDefinition() {
+    return array(
+      array('apple', TRUE),
+      array('banana', TRUE),
+      array('pear', FALSE),
+    );
+  }
+
+  /**
+   * Tests the getDefinition() method with an invalid definition.
+   *
+   * @covers ::getDefinition
+   *
+   * @expectedException \Drupal\Component\Plugin\Exception\PluginNotFoundException
+   * @expectedExceptionMessage The "pear" entity type does not exist.
+   */
+  public function testGetDefinitionInvalidException() {
+    $this->setUpEntityTypeDefinitions();
+
+    $this->entityTypeManager->getDefinition('pear', TRUE);
+  }
+
+  /**
+   * Gets a mock controller class name.
+   *
+   * @return string
+   *   A mock controller class name.
+   */
+  protected function getTestHandlerClass() {
+    return get_class($this->getMockForAbstractClass(EntityHandlerBase::class));
+  }
+
+}
+
+class TestEntityTypeManager extends EntityTypeManager {
+
+  /**
+   * Sets the discovery for the manager.
+   *
+   * @param \Drupal\Component\Plugin\Discovery\DiscoveryInterface $discovery
+   *   The discovery object.
+   */
+  public function setDiscovery(DiscoveryInterface $discovery) {
+    $this->discovery = $discovery;
+  }
+
+}
+
+/**
+ * Provides a test entity form.
+ */
+class TestEntityForm extends EntityHandlerBase {
+
+  /**
+   * The entity manager.
+   *
+   * @var \Drupal\Core\Entity\EntityManagerInterface
+   */
+  protected $entityManager;
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getBaseFormId() {
+    return 'the_base_form_id';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'the_form_id';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setEntity(EntityInterface $entity) {
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setOperation($operation) {
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setEntityManager(EntityManagerInterface $entity_manager) {
+    $this->entityManager = $entity_manager;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setEntityTypeManager(EntityTypeManagerInterface $entity_type_manager) {
+    $this->entityTypeManager = $entity_type_manager;
+    return $this;
+  }
+
+}
+
+/**
+ * Provides a test entity form that uses injection.
+ */
+class TestEntityFormInjected extends TestEntityForm implements ContainerInjectionInterface {
+
+  /**
+   * The color of the entity type.
+   *
+   * @var string
+   */
+  protected $color;
+
+  /**
+   * Constructs a new TestEntityFormInjected.
+   *
+   * @param string $color
+   *   The color of the entity type.
+   */
+  public function __construct($color) {
+    $this->color = $color;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static('yellow');
+  }
+
+}
+
+/**
+ * Provides a test entity route provider.
+ */
+class TestRouteProvider extends EntityHandlerBase {
+
+}
+
+}
+
+namespace {
+
+  /**
+   * Implements hook_entity_type_build().
+   */
+  function entity_manager_test_module_entity_type_build() {
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityTypeRepositoryTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityTypeRepositoryTest.php
new file mode 100644
index 0000000..e835361
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Entity/EntityTypeRepositoryTest.php
@@ -0,0 +1,180 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Entity\EntityTypeRepositoryTest.
+ */
+
+namespace Drupal\Tests\Core\Entity;
+
+use Drupal\Component\Plugin\Exception\PluginNotFoundException;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Entity\EntityTypeRepository;
+use Drupal\Tests\UnitTestCase;
+use Prophecy\Argument;
+
+/**
+ * @coversDefaultClass \Drupal\Core\Entity\EntityTypeRepository
+ * @group Entity
+ */
+class EntityTypeRepositoryTest extends UnitTestCase {
+
+  /**
+   * The entity type repository under test.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeRepository
+   */
+  protected $entityTypeRepository;
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface|\Prophecy\Prophecy\ProphecyInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->entityTypeManager = $this->prophesize(EntityTypeManagerInterface::class);
+
+    $this->entityTypeRepository = new EntityTypeRepository($this->entityTypeManager->reveal());
+  }
+
+  /**
+   * Sets up the entity type manager to be tested.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeInterface[]|\Prophecy\Prophecy\ProphecyInterface[] $definitions
+   *   (optional) An array of entity type definitions.
+   */
+  protected function setUpEntityTypeDefinitions($definitions = []) {
+    $class = $this->getMockClass(EntityInterface::class);
+    foreach ($definitions as $key => $entity_type) {
+      // \Drupal\Core\Entity\EntityTypeInterface::getLinkTemplates() is called
+      // by \Drupal\Core\Entity\EntityManager::processDefinition() so it must
+      // always be mocked.
+      $entity_type->getLinkTemplates()->willReturn([]);
+
+      // Give the entity type a legitimate class to return.
+      $entity_type->getClass()->willReturn($class);
+
+      $definitions[$key] = $entity_type->reveal();
+    }
+
+    $this->entityTypeManager->getDefinition(Argument::cetera())
+      ->will(function ($args) use ($definitions) {
+        $entity_type_id = $args[0];
+        $exception_on_invalid = $args[1];
+        if (isset($definitions[$entity_type_id])) {
+          return $definitions[$entity_type_id];
+        }
+        elseif (!$exception_on_invalid) {
+          return NULL;
+        }
+        else throw new PluginNotFoundException($entity_type_id);
+      });
+    $this->entityTypeManager->getDefinitions()->willReturn($definitions);
+  }
+
+  /**
+   * Tests the getEntityTypeLabels() method.
+   *
+   * @covers ::getEntityTypeLabels
+   */
+  public function testGetEntityTypeLabels() {
+    $apple = $this->prophesize(EntityTypeInterface::class);
+    $apple->getLabel()->willReturn('Apple');
+    $apple->getBundleOf()->willReturn(NULL);
+
+    $banana = $this->prophesize(EntityTypeInterface::class);
+    $banana->getLabel()->willReturn('Banana');
+    $banana->getBundleOf()->willReturn(NULL);
+
+    $this->setUpEntityTypeDefinitions(array(
+      'apple' => $apple,
+      'banana' => $banana,
+    ));
+
+    $expected = array(
+      'apple' => 'Apple',
+      'banana' => 'Banana',
+    );
+    $this->assertSame($expected, $this->entityTypeRepository->getEntityTypeLabels());
+  }
+
+  /**
+   * @covers ::getEntityTypeFromClass
+   */
+  public function testGetEntityTypeFromClass() {
+    $apple = $this->prophesize(EntityTypeInterface::class);
+    $banana = $this->prophesize(EntityTypeInterface::class);
+
+    $this->setUpEntityTypeDefinitions(array(
+      'apple' => $apple,
+      'banana' => $banana,
+    ));
+
+    $apple->getOriginalClass()->willReturn('\Drupal\apple\Entity\Apple');
+
+    $banana->getOriginalClass()->willReturn('\Drupal\banana\Entity\Banana');
+    $banana->getClass()->willReturn('\Drupal\mango\Entity\Mango');
+    $banana->id()
+      ->willReturn('banana')
+      ->shouldBeCalledTimes(2);
+
+    $entity_type_id = $this->entityTypeRepository->getEntityTypeFromClass('\Drupal\banana\Entity\Banana');
+    $this->assertSame('banana', $entity_type_id);
+    $entity_type_id = $this->entityTypeRepository->getEntityTypeFromClass('\Drupal\mango\Entity\Mango');
+    $this->assertSame('banana', $entity_type_id);
+  }
+
+  /**
+   * @covers ::getEntityTypeFromClass
+   *
+   * @expectedException \Drupal\Core\Entity\Exception\NoCorrespondingEntityClassException
+   * @expectedExceptionMessage The \Drupal\pear\Entity\Pear class does not correspond to an entity type.
+   */
+  public function testGetEntityTypeFromClassNoMatch() {
+    $apple = $this->prophesize(EntityTypeInterface::class);
+    $banana = $this->prophesize(EntityTypeInterface::class);
+
+    $this->setUpEntityTypeDefinitions(array(
+      'apple' => $apple,
+      'banana' => $banana,
+    ));
+
+    $apple->getOriginalClass()->willReturn('\Drupal\apple\Entity\Apple');
+    $banana->getOriginalClass()->willReturn('\Drupal\banana\Entity\Banana');
+
+    $this->entityTypeRepository->getEntityTypeFromClass('\Drupal\pear\Entity\Pear');
+  }
+
+  /**
+   * @covers ::getEntityTypeFromClass
+   *
+   * @expectedException \Drupal\Core\Entity\Exception\AmbiguousEntityClassException
+   * @expectedExceptionMessage Multiple entity types found for \Drupal\apple\Entity\Apple.
+   */
+  public function testGetEntityTypeFromClassAmbiguous() {
+    $boskoop = $this->prophesize(EntityTypeInterface::class);
+    $boskoop->getOriginalClass()->willReturn('\Drupal\apple\Entity\Apple');
+    $boskoop->id()->willReturn('boskop');
+
+    $gala = $this->prophesize(EntityTypeInterface::class);
+    $gala->getOriginalClass()->willReturn('\Drupal\apple\Entity\Apple');
+    $gala->id()->willReturn('gala');
+
+    $this->setUpEntityTypeDefinitions(array(
+      'boskoop' => $boskoop,
+      'gala' => $gala,
+    ));
+
+    $this->entityTypeRepository->getEntityTypeFromClass('\Drupal\apple\Entity\Apple');
+  }
+
+}
