diff --git a/core/includes/config.inc b/core/includes/config.inc
index 9e2b6dd..a33c4fa 100644
--- a/core/includes/config.inc
+++ b/core/includes/config.inc
@@ -141,8 +141,8 @@ function config_context_leave() {
  * @param string $module
  *   The name of the module possibly providing config entities.
  *
- * @return array
- *   An associative array containing the entity info for any config entities
+ * @return \Drupal\Core\Entity\Annotation\EntityType[]
+ *   An associative array containing the entity type for any config entities
  *   provided by the requested module, keyed by the entity type.
  */
 function config_get_module_config_entities($module) {
@@ -151,7 +151,7 @@ function config_get_module_config_entities($module) {
   // once per module.
   $info = entity_get_info();
   return array_filter($info, function($entity_info) use ($module) {
-    return ($entity_info['module'] == $module) && is_subclass_of($entity_info['class'], 'Drupal\Core\Config\Entity\ConfigEntityInterface');
+    return ($entity_info->getModule() == $module) && is_subclass_of($entity_info->getClass(), 'Drupal\Core\Config\Entity\ConfigEntityInterface');
   });
 }
 
@@ -166,7 +166,7 @@ function config_get_module_config_entities($module) {
  */
 function config_get_entity_type_by_name($name) {
   $entities = array_filter(entity_get_info(), function($entity_info) use ($name) {
-    return (isset($entity_info['config_prefix']) && strpos($name, $entity_info['config_prefix'] . '.') === 0);
+    return ($entity_info->getConfigPrefix() && strpos($name, $entity_info->getConfigPrefix() . '.') === 0);
   });
   return key($entities);
 }
diff --git a/core/includes/entity.inc b/core/includes/entity.inc
index fe30114..6f6e9dc 100644
--- a/core/includes/entity.inc
+++ b/core/includes/entity.inc
@@ -17,7 +17,7 @@
  *   (optional) The entity type (e.g. 'node'). Leave NULL to retrieve
  *   information for all entity types.
  *
- * @return array
+ * @return \Drupal\Core\Entity\EntityType|\Drupal\Core\Entity\EntityType[]
  *   An array containing the entity type's definition, as retrieved with
  *   \Drupal\Core\Entity\EntityManager. If $entity_type is NULL, an associative
  *   array of all entity type definitions keyed by entity type is returned.
@@ -69,7 +69,7 @@ function entity_get_bundles($entity_type = NULL) {
       // If no bundles are provided, use the entity type name and label.
       foreach (entity_get_info() as $type => $entity_info) {
         if (!isset($bundles[$type])) {
-          $bundles[$type][$type]['label'] = $entity_info['label'];
+          $bundles[$type][$type]['label'] = $entity_info->getLabel();
         }
       }
       drupal_alter('entity_bundle_info', $bundles);
@@ -265,10 +265,10 @@ function entity_revision_delete($entity_type, $revision_id) {
  */
 function entity_load_by_uuid($entity_type, $uuid, $reset = FALSE) {
   $entity_info = entity_get_info($entity_type);
-  if (empty($entity_info['entity_keys']['uuid'])) {
+  if (!$entity_info->hasKey('uuid')) {
     throw new EntityStorageException("Entity type $entity_type does not support UUIDs.");
   }
-  $uuid_key = $entity_info['entity_keys']['uuid'];
+  $uuid_key = $entity_info->getKey('uuid');
 
   $controller = Drupal::entityManager()->getStorageController($entity_type);
   if ($reset) {
@@ -562,7 +562,7 @@ function entity_form_submit_build_entity($entity_type, $entity, $form, &$form_st
   // without changing existing entity properties that are not being edited by
   // this form. Copying field values must be done using
   // field_attach_extract_form_values().
-  $values_excluding_fields = $info['fieldable'] ? array_diff_key($form_state['values'], field_info_instances($entity_type, $entity->bundle())) : $form_state['values'];
+  $values_excluding_fields = $info->isFieldable() ? array_diff_key($form_state['values'], field_info_instances($entity_type, $entity->bundle())) : $form_state['values'];
   foreach ($values_excluding_fields as $key => $value) {
     $entity->set($key, $value);
   }
@@ -575,7 +575,7 @@ function entity_form_submit_build_entity($entity_type, $entity, $form, &$form_st
   }
 
   // Copy field values to the entity.
-  if ($info['fieldable']) {
+  if ($info->isFieldable()) {
     field_attach_extract_form_values($entity, $form, $form_state);
   }
 }
@@ -875,8 +875,8 @@ function entity_page_create_access($entity_type, $bundle = NULL) {
 
   // Pass in the entity bundle if given and required.
   $values = array();
-  if ($bundle && isset($definition['entity_keys']['bundle'])) {
-    $values[$definition['entity_keys']['bundle']] = $bundle;
+  if ($bundle && $definition->hasKey('bundle')) {
+    $values[$definition->getKey('bundle')] = $bundle;
   }
   $entity = Drupal::entityManager()
     ->getStorageController($entity_type)
diff --git a/core/includes/install.inc b/core/includes/install.inc
index e0e86cc..4635036 100644
--- a/core/includes/install.inc
+++ b/core/includes/install.inc
@@ -639,6 +639,7 @@ function drupal_install_system() {
 
   // Update the module list to include it.
   Drupal::moduleHandler()->setModuleList(array('system' => $system_path . '/system.module'));
+  Drupal::service('kernel')->updateModules(array('system' => $system_path . '/system.module'));
 
   config_install_default_config('module', 'system');
 
diff --git a/core/lib/Drupal/Core/AnnotationReader.php b/core/lib/Drupal/Core/AnnotationReader.php
new file mode 100644
index 0000000..7190fde
--- /dev/null
+++ b/core/lib/Drupal/Core/AnnotationReader.php
@@ -0,0 +1,168 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\AnnotationReader.
+ */
+
+namespace Drupal\Core;
+
+use Doctrine\Common\Annotations\AnnotationReader as DoctrineAnnotationReader;
+use Doctrine\Common\Annotations\AnnotationRegistry;
+use Doctrine\Common\Reflection\StaticReflectionParser;
+
+/**
+ */
+class AnnotationReader {
+
+  /**
+   * @var bool
+   */
+  protected $initialized;
+
+  /**
+   * @var \Doctrine\Common\Annotations\AnnotationReader
+   */
+  protected $reader;
+
+  /**
+   * @var object
+   */
+  protected $loader;
+
+  /**
+   * @param string $class_name
+   * @param string $annotation_name
+   *
+   * @return mixed
+   */
+  public function getClassAnnotation($class_name, $annotation_name) {
+    $annotations = $this->getClassAnnotations($class_name);
+    $filtered_annotations = array();
+    foreach ($annotations as $annotation) {
+      if ($annotation instanceOf $annotation_name) {
+        return $annotation;
+      }
+    }
+  }
+
+  /**
+   * @param string $class_name
+   *
+   * @return array
+   */
+  public function getClassAnnotations($class_name) {
+    return $this->getFromCache($class_name) ?: $this->doGetClassAnnotations($class_name);
+  }
+
+  /**
+   * @param string $class_name
+   *
+   * @return array
+   */
+  protected function doGetClassAnnotations($class_name) {
+    $this->initialize();
+
+    $annotations = array();
+    if (class_exists($class_name)) {
+      $reflection_class = new \ReflectionClass($class_name);
+      $annotations = $this->reader()->getClassAnnotations($reflection_class);
+      $this->setCache($class_name, $annotations);
+    }
+    return $annotations;
+  }
+
+  /**
+   * @param string $class_name
+   *
+   * @return array
+   */
+  protected function doGetClassAnnotationsStatic($class_name) {
+    $this->initialize();
+    $parser = new StaticReflectionParser($class_name, $this->getLoader());
+    $reflection_class = $parser->getReflectionClass();
+    $annotations = $this->reader()->getClassAnnotations($reflection_class);
+    $this->setCache($class_name, $annotations);
+    return $annotations;
+  }
+
+  /**
+   * @return DoctrineAnnotationReader
+   */
+  protected function reader() {
+    if (!isset($this->reader)) {
+      $this->reader = new DoctrineAnnotationReader();
+      foreach ($this->globalIgnoreNames() as $name) {
+        $this->reader->addGlobalIgnoredName($name);
+      }
+    }
+    return $this->reader;
+  }
+
+  /**
+   * @param $reader
+   */
+  public function setReader($reader) {
+    $this->reader = $reader;
+  }
+
+  /**
+   * @return array
+   */
+  protected function globalIgnoreNames() {
+    return array('endlink');
+  }
+
+  /**
+   * @param string $name
+   *
+   * @return bool
+   */
+  protected function getFromCache($name) {
+    return isset($this->cache[$name]) ? $this->cache[$name] : FALSE;
+  }
+
+  /**
+   * @param string $name
+   * @param mixed $value
+   */
+  protected function setCache($name, $value) {
+    $this->cache[$name] = $value;
+  }
+
+  /**
+   * @return \Symfony\Component\ClassLoader\ClassLoader
+   */
+  protected function getLoader() {
+    return $this->loader ?: $this->defaultLoader();
+  }
+
+  /**
+   * @param $loader
+   */
+  public function setLoader($loader) {
+    $this->loader = $loader;
+  }
+
+  /**
+   * @return \Symfony\Component\ClassLoader\ClassLoader
+   */
+  protected function defaultLoader() {
+    return drupal_classloader();
+  }
+
+  /**
+   */
+  protected function initialize() {
+    if (!$this->initialized) {
+      // If the default loader isn't already callable, it's probably an instance
+      // of the class loader, so make it callable with the right method here.
+      if (!is_callable($loader = $this->getLoader())) {
+        $loader = array($loader, 'loadClass');
+      }
+      AnnotationRegistry::registerLoader($loader);
+      $this->initialized = TRUE;
+    }
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityListController.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityListController.php
index 41c3ba6..0a91821 100644
--- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityListController.php
+++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityListController.php
@@ -20,7 +20,7 @@ class ConfigEntityListController extends EntityListController {
    */
   public function load() {
     $entities = parent::load();
-    uasort($entities, array($this->entityInfo['class'], 'sort'));
+    uasort($entities, array($this->entityInfo->getClass(), 'sort'));
     return $entities;
   }
 
@@ -38,7 +38,7 @@ public function getOperations(EntityInterface $entity) {
       $operations['edit']['href'] = $uri['path'];
     }
 
-    if (isset($this->entityInfo['entity_keys']['status'])) {
+    if ($this->entityInfo->hasKey('status')) {
       if (!$entity->status()) {
         $operations['enable'] = array(
           'title' => t('Enable'),
diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php b/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php
index 7fe3c49..193753d 100644
--- a/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php
+++ b/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php
@@ -9,6 +9,8 @@
 
 use Drupal\Component\Uuid\Uuid;
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityType;
+use Drupal\Core\Entity\EntityControllerInterface;
 use Drupal\Core\Entity\EntityMalformedException;
 use Drupal\Core\Entity\EntityStorageControllerBase;
 use Drupal\Core\Config\Config;
@@ -83,18 +85,11 @@ class ConfigStorageController extends EntityStorageControllerBase {
    * @param \Drupal\Core\Entity\Query\QueryFactory $entity_query_factory
    *   The entity query factory.
    */
-  public function __construct($entity_type, array $entity_info, ConfigFactory $config_factory, StorageInterface $config_storage, QueryFactory $entity_query_factory) {
+  public function __construct($entity_type, EntityType $entity_info, ConfigFactory $config_factory, StorageInterface $config_storage, QueryFactory $entity_query_factory) {
     parent::__construct($entity_type, $entity_info);
 
-    $this->idKey = $this->entityInfo['entity_keys']['id'];
-
-    if (isset($this->entityInfo['entity_keys']['status'])) {
-      $this->statusKey = $this->entityInfo['entity_keys']['status'];
-    }
-    else {
-      $this->statusKey = FALSE;
-    }
-
+    $this->idKey = $this->entityInfo->getKey('id');
+    $this->statusKey = $this->entityInfo->getKey('status');
     $this->configFactory = $config_factory;
     $this->configStorage = $config_storage;
     $this->entityQueryFactory = $entity_query_factory;
@@ -103,7 +98,7 @@ public function __construct($entity_type, array $entity_info, ConfigFactory $con
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, $entity_type, EntityType $entity_info) {
     return new static(
       $entity_type,
       $entity_info,
@@ -210,7 +205,7 @@ public function getQuery($conjunction = 'AND') {
    *   The full configuration prefix, for example 'views.view.'.
    */
   public function getConfigPrefix() {
-    return $this->entityInfo['config_prefix'] . '.';
+    return $this->entityInfo->getConfigPrefix() . '.';
   }
 
   /**
@@ -251,7 +246,7 @@ public static function getIDFromConfigName($config_name, $config_prefix) {
    *   A SelectQuery object for loading the entity.
    */
   protected function buildQuery($ids, $revision_id = FALSE) {
-    $config_class = $this->entityInfo['class'];
+    $config_class = $this->entityInfo->getClass();
     $prefix = $this->getConfigPrefix();
 
     // Get the names of the configuration entities we are going to load.
@@ -310,7 +305,7 @@ protected function attachLoad(&$queried_entities, $revision_id = FALSE) {
    * Implements Drupal\Core\Entity\EntityStorageControllerInterface::create().
    */
   public function create(array $values) {
-    $class = $this->entityInfo['class'];
+    $class = $this->entityInfo->getClass();
     $class::preCreate($this, $values);
 
     // Set default language to site default if not provided.
@@ -349,7 +344,7 @@ public function delete(array $entities) {
       return;
     }
 
-    $entity_class = $this->entityInfo['class'];
+    $entity_class = $this->entityInfo->getClass();
     $entity_class::preDelete($this, $entities);
     foreach ($entities as $id => $entity) {
       $this->invokeHook('predelete', $entity);
@@ -495,7 +490,7 @@ public function importCreate($name, Config $new_config, Config $old_config) {
    *   A configuration object containing the old configuration data.
    */
   public function importUpdate($name, Config $new_config, Config $old_config) {
-    $id = static::getIDFromConfigName($name, $this->entityInfo['config_prefix']);
+    $id = static::getIDFromConfigName($name, $this->entityInfo->getConfigPrefix());
     $entity = $this->load($id);
     $entity->original = clone $entity;
 
@@ -525,7 +520,7 @@ public function importUpdate($name, Config $new_config, Config $old_config) {
    *   A configuration object containing the old configuration data.
    */
   public function importDelete($name, Config $new_config, Config $old_config) {
-    $id = static::getIDFromConfigName($name, $this->entityInfo['config_prefix']);
+    $id = static::getIDFromConfigName($name, $this->entityInfo->getConfigPrefix());
     $entity = $this->load($id);
     $entity->delete();
     return TRUE;
diff --git a/core/lib/Drupal/Core/Config/Entity/Query/Query.php b/core/lib/Drupal/Core/Config/Entity/Query/Query.php
index c6320f3..eef292e 100644
--- a/core/lib/Drupal/Core/Config/Entity/Query/Query.php
+++ b/core/lib/Drupal/Core/Config/Entity/Query/Query.php
@@ -83,9 +83,10 @@ public function condition($property, $value = NULL, $operator = NULL, $langcode
   public function execute() {
     // Load all config files.
     $entity_info = $this->entityManager->getDefinition($this->getEntityType());
-    $prefix = $entity_info['config_prefix'] . '.';
+    $prefix = $entity_info->getConfigPrefix() . '.';
     $prefix_length = strlen($prefix);
     $names = $this->configStorage->listAll($prefix);
+
     $configs = array();
     foreach ($names as $name) {
       $configs[substr($name, $prefix_length)] = config($name)->get();
diff --git a/core/lib/Drupal/Core/Discovery/DiscoverableInterface.php b/core/lib/Drupal/Core/Discovery/DiscoverableInterface.php
new file mode 100644
index 0000000..58cfe5a
--- /dev/null
+++ b/core/lib/Drupal/Core/Discovery/DiscoverableInterface.php
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Discovery\DiscoverableInterface.
+ */
+
+namespace Drupal\Core\Discovery;
+
+/**
+ */
+interface DiscoverableInterface {
+
+  /**
+   * @return array
+   */
+  public function findAll();
+
+}
diff --git a/core/lib/Drupal/Core/Discovery/YamlDiscovery.php b/core/lib/Drupal/Core/Discovery/YamlDiscovery.php
new file mode 100644
index 0000000..040b6ab
--- /dev/null
+++ b/core/lib/Drupal/Core/Discovery/YamlDiscovery.php
@@ -0,0 +1,60 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Discovery\YamlDiscovery.
+ */
+
+namespace Drupal\Core\Discovery;
+
+use Symfony\Component\Yaml\Parser;
+
+/**
+ */
+class YamlDiscovery implements DiscoverableInterface {
+
+  /**
+   * @param string $name
+   * @param array $directories
+   */
+  public function __construct($name, array $directories) {
+    $this->name = $name;
+    $this->directories = $directories;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function findAll() {
+    $parser = $this->parser();
+    $all = array_map(function($file) use ($parser) {
+      return $parser->parse(file_get_contents($file));
+    }, $this->findFiles());
+    return $all;
+  }
+
+  /**
+   * @return \Symfony\Component\Yaml\Parser
+   */
+  protected function parser() {
+    if (!isset($this->parser)) {
+      $this->parser = new Parser();
+    }
+    return $this->parser;
+  }
+
+  /**
+   * @return array
+   */
+  protected function findFiles() {
+    $files = array();
+    foreach ($this->directories as $directory) {
+      $file = $directory . '/' . basename($directory) . '.' . $this->name . '.yml';
+      if (file_exists($file)) {
+        $files[] = $file;
+      }
+    }
+    return $files;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Entity/Annotation/EntityType.php b/core/lib/Drupal/Core/Entity/Annotation/EntityType.php
index b8e4061..a3ee2b7 100644
--- a/core/lib/Drupal/Core/Entity/Annotation/EntityType.php
+++ b/core/lib/Drupal/Core/Entity/Annotation/EntityType.php
@@ -7,294 +7,53 @@
 
 namespace Drupal\Core\Entity\Annotation;
 
-use Drupal\Component\Annotation\Plugin;
+use Drupal\Component\Annotation\AnnotationInterface;
+use Drupal\Component\Utility\NestedArray;
 
 /**
- * Defines an Entity type annotation object.
- *
  * @Annotation
  */
-class EntityType extends Plugin {
+class EntityType {
 
   /**
-   * The name of the module providing the type.
-   *
-   * @var string
+   * @param array $values
    */
-  public $module;
+  public function __construct(array $values) {
+    // Only keep actual default values by ignoring NULL values.
+    $this->definition = $this->parse(array_filter($values, function ($value) {
+      return $value !== NULL;
+    }));
+  }
 
   /**
-   * The name of the entity type class.
+   * Parses an annotation into its definition.
    *
-   * This is not provided manually, it will be added by the discovery mechanism.
+   * @param array $values
+   *   The annotation array.
    *
-   * @var string
+   * @return array
+   *  The parsed annotation as a definition.
    */
-  public $class;
+  protected function parse(array $values) {
+    $definitions = array();
+    foreach ($values as $key => $value) {
+      if ($value instanceof AnnotationInterface) {
+        $definitions[$key] = $value->get();
+      }
+      elseif (is_array($value)) {
+        $definitions[$key] = $this->parse($value);
+      }
+      else {
+        $definitions[$key] = $value;
+      }
+    }
+    return $definitions;
+  }
 
   /**
-   * The name of the entity type's base table.
-   *
-   * @todo This is only used by \Drupal\Core\Entity\DatabaseStorageController.
-   *
-   * @var string
    */
-  public $base_table;
-
-  /**
-   * An associative array where the keys are the names of different controller
-   * types (listed below) and the values are the names of the classes that
-   * implement that controller:
-   * - storage: The name of the class that is used to load the objects. The
-   *   class must implement \Drupal\Core\Entity\EntityStorageControllerInterface.
-   * - form: An associative array where the keys are the names of the different
-   *   form operations (such as 'create', 'edit', or 'delete') and the values
-   *   are the names of the controller classes for those operations. The name of
-   *   the operation is passed also to the form controller's constructor, so
-   *   that one class can be used for multiple entity forms when the forms are
-   *   similar. The classes must implement
-   *   \Drupal\Core\Entity\EntityFormControllerInterface
-   * - list: The name of the class that provides listings of the entities. The
-   *   class must implement \Drupal\Core\Entity\EntityListControllerInterface.
-   * - render: The name of the class that is used to render the entities. The
-   *   class must implement \Drupal\Core\Entity\EntityRenderControllerInterface.
-   * - access: The name of the class that is used for access checks. The class
-   *   must implement \Drupal\Core\Entity\EntityAccessControllerInterface.
-   *   Defaults to \Drupal\Core\Entity\EntityAccessController.
-   * - translation: The name of the controller class that should be used to
-   *   handle the translation process. The class must implement
-   *   \Drupal\content_translation\ContentTranslationControllerInterface.
-   *
-   * @todo Interfaces from outside \Drupal\Core or \Drupal\Component should not
-   *   be used here.
-   *
-   * @var array
-   */
-  public $controllers = array(
-    'access' => 'Drupal\Core\Entity\EntityAccessController',
-  );
-
-  /**
-   * Boolean indicating whether fields can be attached to entities of this type.
-   *
-   * @var bool (optional)
-   */
-  public $fieldable = FALSE;
-
-  /**
-   * Boolean indicating if the persistent cache of field data should be used.
-   *
-   * The persistent cache should usually only be disabled if a higher level
-   * persistent cache is available for the entity type. Defaults to TRUE.
-   *
-   * @var bool (optional)
-   */
-  public $field_cache = TRUE;
-
-  /**
-   * The human-readable name of the type.
-   *
-   * @ingroup plugin_translatable
-   *
-   * @var \Drupal\Core\Annotation\Translation
-   */
-  public $label;
-
-  /**
-   * The human-readable name of the entity bundles, e.g. Vocabulary.
-   *
-   * @ingroup plugin_translatable
-   *
-   * @var \Drupal\Core\Annotation\Translation
-   */
-  public $bundle_label;
-
-  /**
-   * The name of a function that returns the label of the entity.
-   *
-   * The function takes an entity and optional langcode argument, and returns
-   * the label of the entity. If langcode is omitted, the entity's default
-   * language is used. The entity label is the main string associated with an
-   * entity; for example, the title of a node or the subject of a comment. If
-   * there is an entity object property that defines the label, use the 'label'
-   * element of the 'entity_keys' return value component to provide this
-   * information (see below). If more complex logic is needed to determine the
-   * label of an entity, you can instead specify a callback function here, which
-   * will be called to determine the entity label. See also the
-   * \Drupal\Core\Entity\EntityInterface::label() method, which implements this
-   * logic.
-   *
-   * @var string (optional)
-   */
-  public $label_callback;
-
-  /**
-   * Boolean indicating whether entities should be statically cached during a page request.
-   *
-   * @todo This is only used by \Drupal\Core\Entity\DatabaseStorageController.
-   *
-   * @var bool (optional)
-   */
-  public $static_cache = TRUE;
-
-  /**
-   * Boolean indicating whether entities of this type have multilingual support.
-   *
-   * At an entity level, this indicates language support and at a bundle level
-   * this indicates translation support.
-   *
-   * @var bool (optional)
-   */
-  public $translatable = FALSE;
-
-  /**
-   * @todo content_translation_entity_info_alter() uses this but it is undocumented.
-   *
-   * @var array
-   */
-  public $translation = array();
-
-  /**
-   * An array describing how the Field API can extract certain information from
-   * objects of this entity type:
-   * - id: The name of the property that contains the primary ID of the entity.
-   *   Every entity object passed to the Field API must have this property and
-   *   its value must be numeric.
-   * - revision: (optional) The name of the property that contains the revision
-   *   ID of the entity. The Field API assumes that all revision IDs are unique
-   *   across all entities of a type. This entry can be omitted if the entities
-   *   of this type are not versionable.
-   * - bundle: (optional) The name of the property that contains the bundle name
-   *   for the entity. The bundle name defines which set of fields are attached
-   *   to the entity (e.g. what nodes call "content type"). This entry can be
-   *   omitted if this entity type exposes a single bundle (such that all
-   *   entities have the same collection of fields). The name of this single
-   *   bundle will be the same as the entity type.
-   * - label: The name of the property that contains the entity label. For
-   *   example, if the entity's label is located in $entity->subject, then
-   *   'subject' should be specified here. If complex logic is required to build
-   *   the label, a 'label_callback' should be defined instead (see the
-   *   $label_callback block above for details).
-   * - uuid (optional): The name of the property that contains the universally
-   *   unique identifier of the entity, which is used to distinctly identify an
-   *   entity across different systems.
-   *
-   * @var array
-   */
-  public $entity_keys = array(
-    'revision' => '',
-    'bundle' => '',
-  );
-
-  /**
-   * An array describing how the Field API can extract the information it needs
-   * from the bundle objects for this type (e.g Vocabulary objects for terms;
-   * not applicable for nodes):
-   * - bundle: The name of the property that contains the name of the bundle
-   *   object.
-   *
-   * This entry can be omitted if this type's bundles do not exist as standalone
-   * objects.
-   *
-   * @var array
-   */
-  public $bundle_keys;
-
-  /**
-   * The base router path for the entity type's field administration page.
-   *
-   * If the entity type has a bundle, include {bundle} in the path.
-   *
-   * For example, the node entity type specifies
-   * "admin/structure/types/manage/{bundle}" as its base field admin path.
-   *
-   * @var string (optional)
-   */
-  public $route_base_path;
-
-  /**
-   * The prefix for the bundles of this entity type.
-   *
-   * For example, the comment bundle is prefixed with 'comment_node_'.
-   *
-   * @var string (optional)
-   */
-  public $bundle_prefix;
-
-  /**
-   * The base menu router path to which the entity admin user interface responds.
-   *
-   * It can be used to generate UI links and to attach additional router items
-   * to the entity UI in a generic fashion.
-   *
-   * @var string (optional)
-   */
-  public $menu_base_path;
-
-  /**
-   * The menu router path to be used to view the entity.
-   *
-   * @var string (optional)
-   */
-  public $menu_view_path;
-
-  /**
-   * The menu router path to be used to edit the entity.
-   *
-   * @var string (optional)
-   */
-  public $menu_edit_path;
-
-  /**
-   * A string identifying the menu loader in the router path.
-   *
-   * @var string (optional)
-   */
-  public $menu_path_wildcard;
-
-  /**
-   * Link templates using the URI template syntax.
-   *
-   * Links are an array of standard link relations to the URI template that
-   * should be used for them. Where possible, link relationships should use
-   * established IANA relationships rather than custom relationships.
-   *
-   * Every entity type should, at minimum, define "canonical", which is the
-   * pattern for URIs to that entity. Even if the entity will have no HTML page
-   * exposed to users it should still have a canonical URI in order to be
-   * compatible with web services. Entities that will be user-editable via an
-   * HTML page must also define an "edit-form" relationship.
-   *
-   * By default, the following placeholders are supported:
-   * - entityType: The machine name of the entity type.
-   * - bundle: The bundle machine name of the entity.
-   * - id: The unique ID of the entity.
-   * - uuid: The UUID of the entity.
-   * - [entityType]: The entity type itself will also be a valid token for the
-   *   ID of the entity. For instance, a placeholder of {node} used on the Node
-   *   class would have the same value as {id}. This is generally preferred
-   *   over "id" for better self-documentation.
-   *
-   * Specific entity types may also expand upon this list by overriding the
-   * uriPlaceholderReplacements() method.
-   *
-   * @link http://www.iana.org/assignments/link-relations/link-relations.xml @endlink
-   * @link http://tools.ietf.org/html/rfc6570 @endlink
-   *
-   * @var array
-   */
-  public $links = array(
-    'canonical' => '/entity/{entityType}/{id}',
-  );
-
-  /**
-   * Specifies whether a module exposing permissions for the current entity type
-   * should use entity-type level granularity, bundle level granularity or just
-   * skip this entity. The allowed values are respectively "entity_type",
-   * "bundle" or FALSE.
-   *
-   * @var string|bool (optional)
-   */
-  public $permission_granularity = 'entity_type';
+  public function get() {
+    return $this->definition;
+  }
 
 }
diff --git a/core/lib/Drupal/Core/Entity/DatabaseStorageController.php b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php
index e2dc1b4..60a5d79 100644
--- a/core/lib/Drupal/Core/Entity/DatabaseStorageController.php
+++ b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php
@@ -11,6 +11,7 @@
 use PDO;
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Entity\Query\QueryInterface;
+use Drupal\Core\Entity\EntityType;
 use Drupal\Component\Uuid\Uuid;
 use Drupal\Component\Utility\NestedArray;
 use Drupal\Core\Database\Connection;
@@ -61,7 +62,7 @@ class DatabaseStorageController extends EntityStorageControllerBase {
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, $entity_type, EntityType $entity_info) {
     return new static(
       $entity_type,
       $entity_info,
@@ -79,31 +80,20 @@ public static function createInstance(ContainerInterface $container, $entity_typ
    * @param \Drupal\Core\Database\Connection $database
    *   The database connection to be used.
    */
-  public function __construct($entity_type, array $entity_info, Connection $database) {
+  public function __construct($entity_type, EntityType $entity_info, Connection $database) {
     parent::__construct($entity_type, $entity_info);
 
     $this->database = $database;
 
     // Check if the entity type supports IDs.
-    if (isset($this->entityInfo['entity_keys']['id'])) {
-      $this->idKey = $this->entityInfo['entity_keys']['id'];
-    }
-    else {
-      $this->idKey = FALSE;
-    }
-
+    $this->idKey = $this->entityInfo->getKey('id');
     // Check if the entity type supports UUIDs.
-    if (!empty($this->entityInfo['entity_keys']['uuid'])) {
-      $this->uuidKey = $this->entityInfo['entity_keys']['uuid'];
-    }
-    else {
-      $this->uuidKey = FALSE;
-    }
+    $this->uuidKey = $this->entityInfo->getKey('uuid');
 
     // Check if the entity type supports revisions.
-    if (!empty($this->entityInfo['entity_keys']['revision'])) {
-      $this->revisionKey = $this->entityInfo['entity_keys']['revision'];
-      $this->revisionTable = $this->entityInfo['revision_table'];
+    if ($this->entityInfo->hasKey('revision')) {
+      $this->revisionKey = $this->entityInfo->getKey('revision');
+      $this->revisionTable = $this->entityInfo->getRevisionTable();
     }
     else {
       $this->revisionKey = FALSE;
@@ -139,11 +129,12 @@ public function loadMultiple(array $ids = NULL) {
       // Build and execute the query.
       $query_result = $this->buildQuery($ids)->execute();
 
-      if (!empty($this->entityInfo['class'])) {
+      $entity_class = $this->getRecordClass();
+      if (!empty($entity_class)) {
         // We provide the necessary arguments for PDO to create objects of the
         // specified entity class.
         // @see Drupal\Core\Entity\EntityInterface::__construct()
-        $query_result->setFetchMode(PDO::FETCH_CLASS, $this->entityInfo['class'], array(array(), $this->entityType));
+        $query_result->setFetchMode(PDO::FETCH_CLASS, $entity_class, array(array(), $this->entityType));
       }
       $queried_entities = $query_result->fetchAllAssoc($this->idKey);
     }
@@ -192,11 +183,11 @@ public function loadRevision($revision_id) {
     // Build and execute the query.
     $query_result = $this->buildQuery(array(), $revision_id)->execute();
 
-    if (!empty($this->entityInfo['class'])) {
+    if ($entity_class = $this->entityInfo->getClass()) {
       // We provide the necessary arguments for PDO to create objects of the
       // specified entity class.
       // @see Drupal\Core\Entity\EntityInterface::__construct()
-      $query_result->setFetchMode(PDO::FETCH_CLASS, $this->entityInfo['class'], array(array(), $this->entityType));
+      $query_result->setFetchMode(PDO::FETCH_CLASS, $entity_class, array(array(), $this->entityType));
     }
     $queried_entities = $query_result->fetchAllAssoc($this->idKey);
 
@@ -274,7 +265,8 @@ protected function buildPropertyQuery(QueryInterface $entity_query, array $value
    *   A SelectQuery object for loading the entity.
    */
   protected function buildQuery($ids, $revision_id = FALSE) {
-    $query = $this->database->select($this->entityInfo['base_table'], 'base');
+    $base_table = $this->entityInfo->getBaseTable();
+    $query = $this->database->select($this->entityInfo->getBaseTable(), 'base');
 
     $query->addTag($this->entityType . '_load_multiple');
 
@@ -286,11 +278,11 @@ protected function buildQuery($ids, $revision_id = FALSE) {
     }
 
     // Add fields from the {entity} table.
-    $entity_fields = drupal_schema_fields_sql($this->entityInfo['base_table']);
+    $entity_fields = drupal_schema_fields_sql($base_table);
 
     if ($this->revisionKey) {
       // Add all fields from the {entity_revision} table.
-      $entity_revision_fields = drupal_map_assoc(drupal_schema_fields_sql($this->entityInfo['revision_table']));
+      $entity_revision_fields = drupal_map_assoc(drupal_schema_fields_sql($this->entityInfo->getRevisionTable()));
       // The id field is provided by entity, so remove it.
       unset($entity_revision_fields[$this->idKey]);
 
@@ -336,7 +328,7 @@ protected function buildQuery($ids, $revision_id = FALSE) {
    */
   protected function attachLoad(&$queried_entities, $load_revision = FALSE) {
     // Attach fields.
-    if ($this->entityInfo['fieldable']) {
+    if ($this->entityInfo->isFieldable()) {
       if ($load_revision) {
         field_attach_load_revision($this->entityType, $queried_entities);
       }
@@ -363,7 +355,7 @@ protected function attachLoad(&$queried_entities, $load_revision = FALSE) {
    * Implements \Drupal\Core\Entity\EntityStorageControllerInterface::create().
    */
   public function create(array $values) {
-    $entity_class = $this->entityInfo['class'];
+    $entity_class = $this->entityInfo->getClass();
     $entity_class::preCreate($this, $values);
 
     $entity = new $entity_class($values, $this->entityType);
@@ -393,14 +385,14 @@ public function delete(array $entities) {
     $transaction = $this->database->startTransaction();
 
     try {
-      $entity_class = $this->entityInfo['class'];
+      $entity_class = $this->entityInfo->getClass();
       $entity_class::preDelete($this, $entities);
       foreach ($entities as $id => $entity) {
         $this->invokeHook('predelete', $entity);
       }
       $ids = array_keys($entities);
 
-      $this->database->delete($this->entityInfo['base_table'])
+      $this->database->delete($this->entityInfo->getBaseTable())
         ->condition($this->idKey, $ids, 'IN')
         ->execute();
 
@@ -445,7 +437,7 @@ public function save(EntityInterface $entity) {
 
       if (!$entity->isNew()) {
         if ($entity->isDefaultRevision()) {
-          $return = drupal_write_record($this->entityInfo['base_table'], $entity, $this->idKey);
+          $return = drupal_write_record($this->entityInfo->getBaseTable(), $entity, $this->idKey);
         }
         else {
           // @todo, should a different value be returned when saving an entity
@@ -461,7 +453,7 @@ public function save(EntityInterface $entity) {
         $this->invokeHook('update', $entity);
       }
       else {
-        $return = drupal_write_record($this->entityInfo['base_table'], $entity);
+        $return = drupal_write_record($this->entityInfo->getBaseTable(), $entity);
         if ($this->revisionKey) {
           $this->saveRevision($entity);
         }
@@ -513,7 +505,7 @@ protected function saveRevision(EntityInterface $entity) {
     if ($entity->isNewRevision()) {
       drupal_write_record($this->revisionTable, $record);
       if ($entity->isDefaultRevision()) {
-        $this->database->update($this->entityInfo['base_table'])
+        $this->database->update($this->entityInfo->getBaseTable())
           ->fields(array($this->revisionKey => $record[$this->revisionKey]))
           ->condition($this->idKey, $entity->id())
           ->execute();
@@ -543,7 +535,7 @@ protected function invokeHook($hook, EntityInterface $entity) {
     if ($function == 'field_attach_revision_delete') {
       $function = 'field_attach_delete_revision';
     }
-    if (!empty($this->entityInfo['fieldable']) && function_exists($function)) {
+    if ($this->entityInfo->isFieldable() && function_exists($function)) {
       $function($entity);
     }
     // Invoke the hook.
@@ -566,4 +558,15 @@ public function baseFieldDefinitions() {
   public function getQueryServiceName() {
     return 'entity.query.sql';
   }
+
+  /**
+   * Returns the class which should be used in the load class as conversion.
+   *
+   * @return string
+   *   Returns the class or an empty string to disable the conversion.
+   */
+  protected function getRecordClass() {
+    return $this->entityInfo->getClass();
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php b/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php
index 143a2ab..d9d588b 100644
--- a/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php
+++ b/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php
@@ -12,6 +12,7 @@
 
 use Drupal\Core\Entity\Query\QueryInterface;
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityType;
 use Drupal\Core\Entity\DatabaseStorageController;
 use Drupal\Core\Entity\EntityStorageException;
 use Drupal\Component\Uuid\Uuid;
@@ -53,21 +54,21 @@ class DatabaseStorageControllerNG extends DatabaseStorageController {
   /**
    * Overrides DatabaseStorageController::__construct().
    */
-  public function __construct($entity_type, array $entity_info, Connection $database) {
+  public function __construct($entity_type, EntityType $entity_info, Connection $database) {
     parent::__construct($entity_type,$entity_info, $database);
-    $this->bundleKey = !empty($this->entityInfo['entity_keys']['bundle']) ? $this->entityInfo['entity_keys']['bundle'] : FALSE;
-    $this->entityClass = $this->entityInfo['class'];
+    $this->bundleKey = $this->entityInfo->getKey('bundle');
+    $this->entityClass = $this->entityInfo->getClass();
 
     // Check if the entity type has a dedicated table for properties.
-    if (!empty($this->entityInfo['data_table'])) {
-      $this->dataTable = $this->entityInfo['data_table'];
+    if ($data_table = $this->entityInfo->getDataTable()) {
+      $this->dataTable = $data_table;
     }
 
     // Work-a-round to let load() get stdClass storage records without having to
     // override it. We map storage records to entities in
     // DatabaseStorageControllerNG:: mapFromStorageRecords().
     // @todo: Remove this once this is moved in the main controller.
-    unset($this->entityInfo['class']);
+    $this->entityInfo->setClass(NULL);
   }
 
   /**
@@ -165,7 +166,7 @@ protected function buildPropertyQuery(QueryInterface $entity_query, array $value
    * {@inheritdoc}
    */
   protected function buildQuery($ids, $revision_id = FALSE) {
-    $query = $this->database->select($this->entityInfo['base_table'], 'base');
+    $query = $this->database->select($this->entityInfo->getBaseTable(), 'base');
     $is_revision_query = $this->revisionKey && ($revision_id || !$this->dataTable);
 
     $query->addTag($this->entityType . '_load_multiple');
@@ -178,11 +179,11 @@ protected function buildQuery($ids, $revision_id = FALSE) {
     }
 
     // Add fields from the {entity} table.
-    $entity_fields = drupal_schema_fields_sql($this->entityInfo['base_table']);
+    $entity_fields = drupal_schema_fields_sql($this->entityInfo->getBaseTable());
 
     if ($is_revision_query) {
       // Add all fields from the {entity_revision} table.
-      $entity_revision_fields = drupal_map_assoc(drupal_schema_fields_sql($this->entityInfo['revision_table']));
+      $entity_revision_fields = drupal_map_assoc(drupal_schema_fields_sql($this->entityInfo->getRevisionTable()));
       // The ID field is provided by entity, so remove it.
       unset($entity_revision_fields[$this->idKey]);
 
@@ -290,10 +291,10 @@ protected function attachPropertyData(array &$entities, $revision_id = FALSE) {
       $field_definition = \Drupal::entityManager()->getFieldDefinitions($this->entityType);
       $translations = array();
       if ($this->revisionTable) {
-        $data_fields = array_flip(array_diff(drupal_schema_fields_sql($this->entityInfo['revision_table']), drupal_schema_fields_sql($this->entityInfo['base_table'])));
+        $data_fields = array_flip(array_diff(drupal_schema_fields_sql($this->entityInfo->getRevisionTable()), drupal_schema_fields_sql($this->entityInfo->getBaseTable())));
       }
       else {
-        $data_fields = array_flip(drupal_schema_fields_sql($this->entityInfo['data_table']));
+        $data_fields = array_flip(drupal_schema_fields_sql($this->entityInfo->getDataTable()));
       }
 
       foreach ($data as $values) {
@@ -348,10 +349,10 @@ public function save(EntityInterface $entity) {
 
       // Create the storage record to be saved.
       $record = $this->mapToStorageRecord($entity);
-
+      $base_table = $this->entityInfo->getBaseTable();
       if (!$entity->isNew()) {
         if ($entity->isDefaultRevision()) {
-          $return = drupal_write_record($this->entityInfo['base_table'], $record, $this->idKey);
+          $return = drupal_write_record($base_table, $record, $this->idKey);
         }
         else {
           // @todo, should a different value be returned when saving an entity
@@ -373,7 +374,7 @@ public function save(EntityInterface $entity) {
         }
       }
       else {
-        $return = drupal_write_record($this->entityInfo['base_table'], $record);
+        $return = drupal_write_record($base_table, $record);
         $entity->{$this->idKey}->value = $record->{$this->idKey};
         if ($this->revisionKey) {
           $record->{$this->revisionKey} = $this->saveRevision($entity);
@@ -444,7 +445,7 @@ protected function saveRevision(EntityInterface $entity) {
       if ($entity->isNewRevision()) {
         drupal_write_record($this->revisionTable, $record);
         if ($entity->isDefaultRevision()) {
-          $this->database->update($this->entityInfo['base_table'])
+          $this->database->update($this->entityInfo->getBaseTable())
             ->fields(array($this->revisionKey => $record->{$this->revisionKey}))
             ->condition($this->idKey, $record->{$this->idKey})
             ->execute();
@@ -500,7 +501,7 @@ protected function savePropertyData(EntityInterface $entity) {
    */
   protected function mapToStorageRecord(EntityInterface $entity) {
     $record = new \stdClass();
-    foreach (drupal_schema_fields_sql($this->entityInfo['base_table']) as $name) {
+    foreach (drupal_schema_fields_sql($this->entityInfo->getBaseTable()) as $name) {
       $record->$name = $entity->$name->value;
     }
     return $record;
@@ -518,7 +519,7 @@ protected function mapToStorageRecord(EntityInterface $entity) {
   protected function mapToRevisionStorageRecord(EntityInterface $entity) {
     $record = new \stdClass();
     $definitions = $entity->getPropertyDefinitions();
-    foreach (drupal_schema_fields_sql($this->entityInfo['revision_table']) as $name) {
+    foreach (drupal_schema_fields_sql($this->entityInfo->getRevisionTable()) as $name) {
       if (isset($definitions[$name]) && isset($entity->$name->value)) {
         $record->$name = $entity->$name->value;
       }
@@ -543,10 +544,10 @@ protected function mapToDataStorageRecord(EntityInterface $entity, $langcode) {
     // non-translatable properties are replicated for each language.
     $translation = $entity->getTranslation($langcode);
     $definitions = $translation->getPropertyDefinitions();
-    $schema = drupal_get_schema($this->entityInfo['data_table']);
+    $schema = drupal_get_schema($this->entityInfo->getDataTable());
 
     $record = new \stdClass();
-    foreach (drupal_schema_fields_sql($this->entityInfo['data_table']) as $name) {
+    foreach (drupal_schema_fields_sql($this->entityInfo->getDataTable()) as $name) {
       $info = $schema['fields'][$name];
       $value = isset($definitions[$name]) && isset($translation->$name->value) ? $translation->$name->value : NULL;
       $record->$name = drupal_schema_get_field_value($info, $value);
@@ -581,7 +582,7 @@ public function delete(array $entities) {
       }
       $ids = array_keys($entities);
 
-      $this->database->delete($this->entityInfo['base_table'])
+      $this->database->delete($this->entityInfo->getBaseTable())
         ->condition($this->idKey, $ids)
         ->execute();
 
@@ -614,4 +615,13 @@ public function delete(array $entities) {
       throw new EntityStorageException($e->getMessage(), $e->getCode(), $e);
     }
   }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getRecordClass() {
+    // Override to disable the record conversion.
+    return '';
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Entity/Entity.php b/core/lib/Drupal/Core/Entity/Entity.php
index 33a62ce..b80d00e 100644
--- a/core/lib/Drupal/Core/Entity/Entity.php
+++ b/core/lib/Drupal/Core/Entity/Entity.php
@@ -102,7 +102,7 @@ public function isNew() {
    */
   public function isNewRevision() {
     $info = $this->entityInfo();
-    return $this->newRevision || (!empty($info['entity_keys']['revision']) && !$this->getRevisionId());
+    return $this->newRevision || ($info->getKey('revision') && !$this->getRevisionId());
   }
 
   /**
@@ -139,11 +139,15 @@ public function bundle() {
   public function label($langcode = NULL) {
     $label = NULL;
     $entity_info = $this->entityInfo();
-    if (isset($entity_info['label_callback']) && function_exists($entity_info['label_callback'])) {
-      $label = $entity_info['label_callback']($this->entityType, $this, $langcode);
+    $label_callback = $entity_info->getLabelCallback();
+    if (function_exists($label_callback)) {
+      $label = $label_callback($this->entityType, $this, $langcode);
     }
-    elseif (!empty($entity_info['entity_keys']['label']) && isset($this->{$entity_info['entity_keys']['label']})) {
-      $label = $this->{$entity_info['entity_keys']['label']};
+    elseif ($entity_info->hasKey('label')) {
+      $label_key = $entity_info->getKey('label');
+      if (isset($this->{$label_key})) {
+        $label = $this->$label_key;
+      }
     }
     return $label;
   }
@@ -160,8 +164,8 @@ public function uri() {
     if (isset($bundles[$bundle]['uri_callback'])) {
       $uri_callback = $bundles[$bundle]['uri_callback'];
     }
-    elseif (isset($entity_info['uri_callback'])) {
-      $uri_callback = $entity_info['uri_callback'];
+    elseif ($entity_info->getUriCallback()) {
+      $uri_callback = $entity_info->getUriCallback();
     }
 
     // Invoke the callback to get the URI. If there is no callback, use the
@@ -191,7 +195,7 @@ public function uri() {
    */
   public function uriRelationships() {
     $entity_info = $this->entityInfo();
-    return isset($entity_info['links']) ? array_keys($entity_info['links']) : array();
+    return isset($entity_info->links) ? array_keys($entity_info->links) : array();
   }
 
   /**
@@ -324,7 +328,7 @@ public function getTranslationLanguages($include_default = TRUE) {
     $languages = array($default_language->id => $default_language);
     $entity_info = $this->entityInfo();
 
-    if ($entity_info['fieldable']) {
+    if ($entity_info->isFieldable()) {
       // Go through translatable properties and determine all languages for
       // which translated values are available.
       foreach (field_info_instances($this->entityType, $this->bundle()) as $field_name => $instance) {
@@ -367,18 +371,18 @@ public function delete() {
   public function createDuplicate() {
     $duplicate = clone $this;
     $entity_info = $this->entityInfo();
-    $duplicate->{$entity_info['entity_keys']['id']} = NULL;
+    $duplicate->{$entity_info->getKey('id')} = NULL;
 
     // Check if the entity type supports UUIDs and generate a new one if so.
-    if (!empty($entity_info['entity_keys']['uuid'])) {
+    if ($entity_info->hasKey('uuid')) {
       $uuid = new Uuid();
-      $duplicate->{$entity_info['entity_keys']['uuid']} = $uuid->generate();
+      $duplicate->{$entity_info->getKey('uuid')} = $uuid->generate();
     }
     return $duplicate;
   }
 
   /**
-   * Implements \Drupal\Core\Entity\EntityInterface::entityInfo().
+   * {@inheritdoc}
    */
   public function entityInfo() {
     return \Drupal::entityManager()->getDefinition($this->entityType());
diff --git a/core/lib/Drupal/Core/Entity/EntityControllerInterface.php b/core/lib/Drupal/Core/Entity/EntityControllerInterface.php
index 79232c1..8fa95c5 100644
--- a/core/lib/Drupal/Core/Entity/EntityControllerInterface.php
+++ b/core/lib/Drupal/Core/Entity/EntityControllerInterface.php
@@ -8,6 +8,7 @@
 namespace Drupal\Core\Entity;
 
 use Drupal\Core\Entity\EntityStorageControllerInterface;
+use Drupal\Core\Entity\EntityType;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -38,6 +39,6 @@
    * @return static
    *   A new instance of the entity controller.
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info);
+  public static function createInstance(ContainerInterface $container, $entity_type, EntityType $entity_info);
 
 }
diff --git a/core/lib/Drupal/Core/Entity/EntityCreateAccessCheck.php b/core/lib/Drupal/Core/Entity/EntityCreateAccessCheck.php
index 2cce92a..cab019e 100644
--- a/core/lib/Drupal/Core/Entity/EntityCreateAccessCheck.php
+++ b/core/lib/Drupal/Core/Entity/EntityCreateAccessCheck.php
@@ -65,7 +65,7 @@ public function access(Route $route, Request $request) {
   /**
    * Prepare the values passed into the storage controller.
    *
-   * @param array $definition
+   * @param \Drupal\Core\Entity\EntityType $definition
    *   The entity type definition.
    * @param \Symfony\Component\HttpFoundation\Request $request
    *   The request object.
@@ -75,10 +75,10 @@ public function access(Route $route, Request $request) {
    * @return array
    *   An array of values to be used when creating the entity.
    */
-  protected function prepareEntityValues(array $definition, Request $request, $bundle = NULL) {
+  protected function prepareEntityValues(EntityType $definition, Request $request, $bundle = NULL) {
     $values = array();
-    if ($bundle && isset($definition['entity_keys']['bundle'])) {
-      $values[$definition['entity_keys']['bundle']] = $bundle;
+    if ($bundle && $definition->hasKey('bundle')) {
+      $values[$definition->getKey('bundle')] = $bundle;
     }
     return $values;
   }
diff --git a/core/lib/Drupal/Core/Entity/EntityFormController.php b/core/lib/Drupal/Core/Entity/EntityFormController.php
index 36fb318..f3b913c 100644
--- a/core/lib/Drupal/Core/Entity/EntityFormController.php
+++ b/core/lib/Drupal/Core/Entity/EntityFormController.php
@@ -54,7 +54,7 @@ public function __construct(ModuleHandlerInterface $module_handler) {
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, $entity_type, EntityType $entity_info) {
     return new static(
       $container->get('module_handler')
     );
@@ -180,7 +180,7 @@ public function form(array $form, array &$form_state) {
     // @todo Exploit the Field API to generate the default widgets for the
     // entity properties.
     $info = $entity->entityInfo();
-    if (!empty($info['fieldable'])) {
+    if ($info->isfieldable()) {
       field_attach_form($entity, $form, $form_state, $this->getFormLangcode($form_state));
     }
 
diff --git a/core/lib/Drupal/Core/Entity/EntityFormControllerNG.php b/core/lib/Drupal/Core/Entity/EntityFormControllerNG.php
index 3b064f6..832067c 100644
--- a/core/lib/Drupal/Core/Entity/EntityFormControllerNG.php
+++ b/core/lib/Drupal/Core/Entity/EntityFormControllerNG.php
@@ -27,7 +27,7 @@ public function form(array $form, array &$form_state) {
     // @todo Exploit the Field API to generate the default widgets for the
     // entity fields.
     $info = $entity->entityInfo();
-    if (!empty($info['fieldable'])) {
+    if ($info->isFieldable()) {
       field_attach_form($entity, $form, $form_state, $this->getFormLangcode($form_state));
     }
 
@@ -60,7 +60,7 @@ public function buildEntity(array $form, array &$form_state) {
     // field API without changing existing entity fields that are not being
     // edited by this form. Values of fields handled by field API are copied
     // by field_attach_extract_form_values() below.
-    $values_excluding_fields = $info['fieldable'] ? array_diff_key($form_state['values'], field_info_instances($entity_type, $entity->bundle())) : $form_state['values'];
+    $values_excluding_fields = $info->isFieldable() ? array_diff_key($form_state['values'], field_info_instances($entity_type, $entity->bundle())) : $form_state['values'];
     $definitions = $entity->getPropertyDefinitions();
     foreach ($values_excluding_fields as $key => $value) {
       if (isset($definitions[$key])) {
@@ -76,7 +76,7 @@ public function buildEntity(array $form, array &$form_state) {
     }
 
     // Invoke field API for copying field values.
-    if ($info['fieldable']) {
+    if ($info->isFieldable()) {
       field_attach_extract_form_values($entity, $form, $form_state);
     }
     return $entity;
diff --git a/core/lib/Drupal/Core/Entity/EntityInterface.php b/core/lib/Drupal/Core/Entity/EntityInterface.php
index e965b8e..f29f810 100644
--- a/core/lib/Drupal/Core/Entity/EntityInterface.php
+++ b/core/lib/Drupal/Core/Entity/EntityInterface.php
@@ -254,7 +254,7 @@ public function createDuplicate();
   /**
    * Returns the info of the type of the entity.
    *
-   * @see entity_get_info()
+   * @return \Drupal\Core\Entity\EntityType
    */
   public function entityInfo();
 
diff --git a/core/lib/Drupal/Core/Entity/EntityListController.php b/core/lib/Drupal/Core/Entity/EntityListController.php
index 88b2207..c82c3bf 100644
--- a/core/lib/Drupal/Core/Entity/EntityListController.php
+++ b/core/lib/Drupal/Core/Entity/EntityListController.php
@@ -39,7 +39,7 @@ class EntityListController implements EntityListControllerInterface, EntityContr
   /**
    * The entity info array.
    *
-   * @var array
+   * @var \Drupal\Core\Entity\Annotation\EntityType
    *
    * @see entity_get_info()
    */
@@ -48,7 +48,7 @@ class EntityListController implements EntityListControllerInterface, EntityContr
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, $entity_type, EntityType $entity_info) {
     return new static(
       $entity_type,
       $entity_info,
@@ -62,14 +62,14 @@ public static function createInstance(ContainerInterface $container, $entity_typ
    *
    * @param string $entity_type
    *   The type of entity to be listed.
-   * @param array $entity_info
+   * @param \Drupal\Core\Entity\EntityType $entity_info
    *   An array of entity info for the entity type.
    * @param \Drupal\Core\Entity\EntityStorageControllerInterface $storage
    *   The entity storage controller class.
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
    *   The module handler to invoke hooks on.
    */
-  public function __construct($entity_type, array $entity_info, EntityStorageControllerInterface $storage, ModuleHandlerInterface $module_handler) {
+  public function __construct($entity_type, EntityType $entity_info, EntityStorageControllerInterface $storage, ModuleHandlerInterface $module_handler) {
     $this->entityType = $entity_type;
     $this->storage = $storage;
     $this->entityInfo = $entity_info;
@@ -186,7 +186,7 @@ public function render() {
       '#theme' => 'table',
       '#header' => $this->buildHeader(),
       '#rows' => array(),
-      '#empty' => t('There is no @label yet.', array('@label' => $this->entityInfo['label'])),
+      '#empty' => t('There is no @label yet.', array('@label' => $this->entityInfo->getLabel())),
     );
     foreach ($this->load() as $entity) {
       if ($row = $this->buildRow($entity)) {
diff --git a/core/lib/Drupal/Core/Entity/EntityManager.php b/core/lib/Drupal/Core/Entity/EntityManager.php
index 4766a91..2ee7a80 100644
--- a/core/lib/Drupal/Core/Entity/EntityManager.php
+++ b/core/lib/Drupal/Core/Entity/EntityManager.php
@@ -7,16 +7,12 @@
 
 namespace Drupal\Core\Entity;
 
-use Drupal\Component\Plugin\PluginManagerBase;
-use Drupal\Component\Plugin\Factory\DefaultFactory;
+use Drupal\Core\AnnotationReader;
+use Drupal\Core\Entity\EntityTypes;
 use Drupal\Component\Utility\NestedArray;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Language\LanguageManager;
 use Drupal\Core\Language\Language;
-use Drupal\Core\Plugin\Discovery\AlterDecorator;
-use Drupal\Core\Plugin\Discovery\CacheDecorator;
-use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
-use Drupal\Core\Plugin\Discovery\InfoHookDecorator;
 use Drupal\Core\Cache\CacheBackendInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -34,7 +30,7 @@
  * @see entity_get_info()
  * @see hook_entity_info_alter()
  */
-class EntityManager extends PluginManagerBase {
+class EntityManager {
 
   /**
    * The injection container that should be passed into the controller factory.
@@ -44,6 +40,13 @@ class EntityManager extends PluginManagerBase {
   protected $container;
 
   /**
+   * Stores all entity types objects.
+   *
+   * @var \Drupal\Core\Entity\EntityType[]
+   */
+  protected $entity_types;
+
+  /**
    * Contains instantiated controllers keyed by controller type and entity type.
    *
    * @var array
@@ -112,12 +115,7 @@ public function __construct(\Traversable $namespaces, ContainerInterface $contai
     $this->cache = $cache;
     $this->languageManager = $language_manager;
 
-    $this->discovery = new AnnotatedClassDiscovery('Core/Entity', $namespaces, $annotation_namespaces, 'Drupal\Core\Entity\Annotation\EntityType');
-    $this->discovery = new InfoHookDecorator($this->discovery, 'entity_info');
-    $this->discovery = new AlterDecorator($this->discovery, 'entity_info');
-    $this->discovery = new CacheDecorator($this->discovery, 'entity_info:' . $this->languageManager->getLanguage(Language::TYPE_INTERFACE)->id, 'cache', CacheBackendInterface::CACHE_PERMANENT, array('entity_info' => TRUE));
-
-    $this->factory = new DefaultFactory($this->discovery);
+    $this->annotation_reader = new AnnotationReader('Core/Entity', $namespaces, $annotation_namespaces, 'Drupal\Core\Entity\Annotation\EntityType');
     $this->container = $container;
   }
 
@@ -133,8 +131,10 @@ public function __construct(\Traversable $namespaces, ContainerInterface $contai
    *   Returns TRUE if the entity type has the controller, else FALSE.
    */
   public function hasController($entity_type, $controller_type) {
-    $definition = $this->getDefinition($entity_type);
-    return !empty($definition['controllers'][$controller_type]);
+    if (!$definition = $this->getDefinition($entity_type)) {
+      return FALSE;
+    }
+    return $definition->hasController($controller_type);
   }
 
   /**
@@ -148,18 +148,17 @@ public function hasController($entity_type, $controller_type) {
    *   (optional) If this controller definition is nested, the name of the key.
    *   Defaults to NULL.
    *
+   * @throws \InvalidArgumentException
+   *
    * @return string
    *   The class name for this controller instance.
    */
   public function getControllerClass($entity_type, $controller_type, $nested = NULL) {
-    $definition = $this->getDefinition($entity_type);
-    $definition = $definition['controllers'];
-    if (empty($definition[$controller_type])) {
+    if (!$this->hasController($entity_type, $controller_type)) {
       throw new \InvalidArgumentException(sprintf('The entity (%s) did not specify a %s.', $entity_type, $controller_type));
     }
 
-    $class = $definition[$controller_type];
-
+    $class = $this->getDefinition($entity_type)->getController($controller_type);
     // Some class definitions can be nested.
     if (isset($nested)) {
       if (empty($class[$nested])) {
@@ -199,16 +198,7 @@ public function getStorageController($entity_type) {
    *   A list controller instance.
    */
   public function getListController($entity_type) {
-    if (!isset($this->controllers['listing'][$entity_type])) {
-      $class = $this->getControllerClass($entity_type, 'list');
-      if (in_array('Drupal\Core\Entity\EntityControllerInterface', class_implements($class))) {
-        $this->controllers['listing'][$entity_type] = $class::createInstance($this->container, $entity_type, $this->getDefinition($entity_type));
-      }
-      else {
-        $this->controllers['listing'][$entity_type] = new $class($entity_type, $this->getStorageController($entity_type));
-      }
-    }
-    return $this->controllers['listing'][$entity_type];
+    return $this->getController($entity_type, 'list');
   }
 
   /**
@@ -328,13 +318,13 @@ public function getAdminPath($entity_type, $bundle) {
     $admin_path = '';
     $entity_info = $this->getDefinition($entity_type);
     // Check for an entity type's admin base path.
-    if (isset($entity_info['route_base_path'])) {
+    if ($route_base_path = $entity_info->getRouteBasePath()) {
       // If the entity type has a bundle prefix, strip it out of the path.
-      if (isset($entity_info['bundle_prefix'])) {
-        $bundle = str_replace($entity_info['bundle_prefix'], '', $bundle);
+      if ($bundle_prefix = $entity_info->getBundlePrefix()) {
+        $bundle = str_replace($bundle_prefix, '', $bundle);
       }
       // Replace any dynamic 'bundle' portion of the path with the actual bundle.
-      $admin_path = str_replace('{bundle}', $bundle, $entity_info['route_base_path']);
+      $admin_path = str_replace('{bundle}', $bundle, $route_base_path);
     }
 
     return $admin_path;
@@ -454,4 +444,44 @@ public function clearCachedFieldDefinitions() {
     $this->cache->deleteTags(array('entity_field_info' => TRUE));
   }
 
+  public function getDefinitions() {
+    return $this->entityTypes()->findAll();
+  }
+
+  /**
+   * Returns the entity Type object for a given entity type.
+   *
+   * @param string $name
+   *   The name of the entity type.
+   *
+   * @return \Drupal\Core\Entity\EntityType
+   */
+  public function getDefinition($name) {
+    try {
+      return $this->entityTypes()->findByName($name);
+    }
+    catch (\RuntimeException $e) {
+      return FALSE;
+    }
+  }
+
+  public function clearCachedDefinitions() {
+    $this->cache->deleteTags(array('entity_info' => TRUE));
+    unset($this->entity_types);
+  }
+
+  protected function entityTypes() {
+    if (!isset($this->entity_types)) {
+      $this->entity_types = new EntityTypes();
+    }
+    return $this->entity_types;
+  }
+
+  protected function getAnnotationReader() {
+    if (!isset($this->annotation_reader)) {
+      $this->annotation_reader = new \Drupal\Core\AnnotationReader();
+    }
+    return $this->annotation_reader;
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Entity/EntityNG.php b/core/lib/Drupal/Core/Entity/EntityNG.php
index a6768e8..1830349 100644
--- a/core/lib/Drupal/Core/Entity/EntityNG.php
+++ b/core/lib/Drupal/Core/Entity/EntityNG.php
@@ -222,7 +222,7 @@ public function uuid() {
   public function uri($rel = 'canonical') {
     $entity_info = $this->entityInfo();
 
-    $link_templates = isset($entity_info['links']) ? $entity_info['links'] : array();
+    $link_templates = isset($entity_info->links) ? $entity_info->links : array();
 
     if (isset($link_templates[$rel])) {
       $template = $link_templates[$rel];
@@ -581,7 +581,7 @@ public function addTranslation($langcode, array $values = array()) {
     // Instantiate a new empty entity so default values will be populated in the
     // specified language.
     $info = $this->entityInfo();
-    $default_values = array($info['entity_keys']['bundle'] => $this->bundle, 'langcode' => $langcode);
+    $default_values = array($info->getKey('bundle') => $this->bundle, 'langcode' => $langcode);
     $entity = \Drupal::entityManager()
       ->getStorageController($this->entityType())
       ->create($default_values);
@@ -780,16 +780,16 @@ public function createDuplicate() {
 
     $duplicate = clone $this;
     $entity_info = $this->entityInfo();
-    $duplicate->{$entity_info['entity_keys']['id']}->value = NULL;
+    $duplicate->{$entity_info->getKey('id')}->value = NULL;
 
     // Check if the entity type supports UUIDs and generate a new one if so.
-    if (!empty($entity_info['entity_keys']['uuid'])) {
-      $duplicate->{$entity_info['entity_keys']['uuid']}->applyDefaultValue();
+    if ($entity_info->hasKey('uuid')) {
+      $duplicate->{$entity_info->getKey('uuid')}->applyDefaultValue();
     }
 
     // Check whether the entity type supports revisions and initialize it if so.
-    if (!empty($entity_info['entity_keys']['revision'])) {
-      $duplicate->{$entity_info['entity_keys']['revision']}->value = NULL;
+    if ($entity_info->hasKey('revision')) {
+      $duplicate->{$entity_info->getKey('revision')}->value = NULL;
     }
 
     return $duplicate;
@@ -823,11 +823,14 @@ public function label($langcode = NULL) {
     if (!isset($langcode)) {
       $langcode = $this->activeLangcode;
     }
-    if (isset($entity_info['label_callback']) && function_exists($entity_info['label_callback'])) {
-      $label = $entity_info['label_callback']($this->entityType, $this, $langcode);
+    if (($label_callback = $entity_info->getLabelCallback()) && function_exists($label_callback)) {
+      $label = $label_callback($this->entityType, $this, $langcode);
     }
-    elseif (!empty($entity_info['entity_keys']['label']) && isset($this->{$entity_info['entity_keys']['label']})) {
-      $label = $this->{$entity_info['entity_keys']['label']}->value;
+    elseif ($entity_info->hasKey('label')) {
+      $label_key = $entity_info->getKey('label');
+      if (isset($this->{$label_key})) {
+        $label = $this->{$label_key}->value;
+      }
     }
     return $label;
   }
diff --git a/core/lib/Drupal/Core/Entity/EntityStorageControllerBase.php b/core/lib/Drupal/Core/Entity/EntityStorageControllerBase.php
index ea4e06c..bbbb499 100644
--- a/core/lib/Drupal/Core/Entity/EntityStorageControllerBase.php
+++ b/core/lib/Drupal/Core/Entity/EntityStorageControllerBase.php
@@ -38,7 +38,7 @@
   /**
    * Array of information about the entity.
    *
-   * @var array
+   * @var \Drupal\Core\Entity\EntityType
    *
    * @see entity_get_info()
    */
@@ -74,14 +74,14 @@
    *
    * @param string $entity_type
    *   The entity type for which the instance is created.
-   * @param array $entity_info
+   * @param \Drupal\Core\Entity\EntityType $entity_info
    *   An array of entity info for the entity type.
    */
   public function __construct($entity_type, $entity_info) {
     $this->entityType = $entity_type;
-    $this->entityInfo = $entity_info;
+    $this->entityInfo = clone $entity_info;
     // Check if the entity type supports static caching of loaded entities.
-    $this->cache = !empty($this->entityInfo['static_cache']);
+    $this->cache = $this->entityInfo->staticCacheable();
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Entity/EntityType.php b/core/lib/Drupal/Core/Entity/EntityType.php
new file mode 100644
index 0000000..ffe85cb
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/EntityType.php
@@ -0,0 +1,813 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\EntityType.
+ */
+
+namespace Drupal\Core\Entity;
+
+/**
+ */
+class EntityType implements \ArrayAccess {
+
+  /**
+   * The name of the entity type.
+   *
+   * @var string
+   */
+  public $id;
+
+  /**
+   * The name of the module providing the type.
+   *
+   * @var string
+   */
+  public $module;
+
+  /**
+   * The name of the entity type class.
+   *
+   * This is not provided manually, it will be added by the discovery mechanism.
+   *
+   * @var string
+   */
+  public $class;
+
+  /**
+   * The name of the entity type's base table.
+   *
+   * @todo This is only used by \Drupal\Core\Entity\DatabaseStorageController.
+   *
+   * @var string
+   */
+  public $base_table;
+
+  /**
+   * An associative array where the keys are the names of different controller
+   * types (listed below) and the values are the names of the classes that
+   * implement that controller:
+   * - storage: The name of the class that is used to load the objects. The
+   *   class must implement \Drupal\Core\Entity\EntityStorageControllerInterface.
+   * - form: An associative array where the keys are the names of the different
+   *   form operations (such as 'create', 'edit', or 'delete') and the values
+   *   are the names of the controller classes for those operations. The name of
+   *   the operation is passed also to the form controller's constructor, so
+   *   that one class can be used for multiple entity forms when the forms are
+   *   similar. The classes must implement
+   *   \Drupal\Core\Entity\EntityFormControllerInterface
+   * - list: The name of the class that provides listings of the entities. The
+   *   class must implement \Drupal\Core\Entity\EntityListControllerInterface.
+   * - render: The name of the class that is used to render the entities. The
+   *   class must implement \Drupal\Core\Entity\EntityRenderControllerInterface.
+   * - access: The name of the class that is used for access checks. The class
+   *   must implement \Drupal\Core\Entity\EntityAccessControllerInterface.
+   *   Defaults to \Drupal\Core\Entity\EntityAccessController.
+   * - translation: The name of the controller class that should be used to
+   *   handle the translation process. The class must implement
+   *   \Drupal\translation_entity\EntityTranslationControllerInterface.
+   *
+   * @todo Interfaces from outside \Drupal\Core or \Drupal\Component should not
+   *   be used here.
+   *
+   * @var array
+   */
+  public $controllers = array(
+    'access' => 'Drupal\Core\Entity\EntityAccessController',
+  );
+
+  /**
+   * Boolean indicating whether fields can be attached to entities of this type.
+   *
+   * @var bool (optional)
+   */
+  public $fieldable = FALSE;
+
+  /**
+   * Boolean indicating if the persistent cache of field data should be used.
+   *
+   * The persistent cache should usually only be disabled if a higher level
+   * persistent cache is available for the entity type. Defaults to TRUE.
+   *
+   * @var bool (optional)
+   */
+  public $field_cache = TRUE;
+
+  /**
+   * The human-readable name of the type.
+   *
+   * @ingroup plugin_translatable
+   *
+   * @var \Drupal\Core\Annotation\Translation
+   */
+  public $label;
+
+  /**
+   * The human-readable name of the entity bundles, e.g. Vocabulary.
+   *
+   * @ingroup plugin_translatable
+   *
+   * @var \Drupal\Core\Annotation\Translation
+   */
+  public $bundle_label;
+
+  /**
+   * The name of a function that returns the label of the entity.
+   *
+   * The function takes an entity and optional langcode argument, and returns
+   * the label of the entity. If langcode is omitted, the entity's default
+   * language is used. The entity label is the main string associated with an
+   * entity; for example, the title of a node or the subject of a comment. If
+   * there is an entity object property that defines the label, use the 'label'
+   * element of the 'entity_keys' return value component to provide this
+   * information (see below). If more complex logic is needed to determine the
+   * label of an entity, you can instead specify a callback function here, which
+   * will be called to determine the entity label. See also the
+   * \Drupal\Core\Entity\EntityInterface::label() method, which implements this
+   * logic.
+   *
+   * @var string (optional)
+   */
+  public $label_callback;
+
+  /**
+   * Boolean indicating whether entities should be statically cached during a page request.
+   *
+   * @todo This is only used by \Drupal\Core\Entity\DatabaseStorageController.
+   *
+   * @var bool (optional)
+   */
+  public $static_cache = TRUE;
+
+  /**
+   * Boolean indicating whether entities of this type have multilingual support.
+   *
+   * At an entity level, this indicates language support and at a bundle level
+   * this indicates translation support.
+   *
+   * @var bool (optional)
+   */
+  public $translatable = FALSE;
+
+  /**
+   * @todo content_translation_entity_info_alter() uses this but it is undocumented.
+   *
+   * @var array
+   */
+  public $translation = array();
+
+  /**
+   * An array describing how the Field API can extract certain information from
+   * objects of this entity type:
+   * - id: The name of the property that contains the primary ID of the entity.
+   *   Every entity object passed to the Field API must have this property and
+   *   its value must be numeric.
+   * - revision: (optional) The name of the property that contains the revision
+   *   ID of the entity. The Field API assumes that all revision IDs are unique
+   *   across all entities of a type. This entry can be omitted if the entities
+   *   of this type are not versionable.
+   * - bundle: (optional) The name of the property that contains the bundle name
+   *   for the entity. The bundle name defines which set of fields are attached
+   *   to the entity (e.g. what nodes call "content type"). This entry can be
+   *   omitted if this entity type exposes a single bundle (such that all
+   *   entities have the same collection of fields). The name of this single
+   *   bundle will be the same as the entity type.
+   * - label: The name of the property that contains the entity label. For
+   *   example, if the entity's label is located in $entity->subject, then
+   *   'subject' should be specified here. If complex logic is required to build
+   *   the label, a 'label_callback' should be defined instead (see the
+   *   $label_callback block above for details).
+   * - uuid (optional): The name of the property that contains the universally
+   *   unique identifier of the entity, which is used to distinctly identify an
+   *   entity across different systems.
+   *
+   * @var array
+   */
+  public $entity_keys = array(
+    'revision' => '',
+    'bundle' => '',
+  );
+
+  /**
+   * An array describing how the Field API can extract the information it needs
+   * from the bundle objects for this type (e.g Vocabulary objects for terms;
+   * not applicable for nodes):
+   * - bundle: The name of the property that contains the name of the bundle
+   *   object.
+   *
+   * This entry can be omitted if this type's bundles do not exist as standalone
+   * objects.
+   *
+   * @var array
+   */
+  public $bundle_keys;
+
+  /**
+   * The base router path for the entity type's field administration page.
+   *
+   * If the entity type has a bundle, include {bundle} in the path.
+   *
+   * For example, the node entity type specifies
+   * "admin/structure/types/manage/{bundle}" as its base field admin path.
+   *
+   * @var string (optional)
+   */
+  public $route_base_path;
+
+  /**
+   * The prefix for the bundles of this entity type.
+   *
+   * For example, the comment bundle is prefixed with 'comment_node_'.
+   *
+   * @var string (optional)
+   */
+  public $bundle_prefix;
+
+  /**
+   * The base menu router path to which the entity admin user interface responds.
+   *
+   * It can be used to generate UI links and to attach additional router items
+   * to the entity UI in a generic fashion.
+   *
+   * @var string (optional)
+   */
+  public $menu_base_path;
+
+  /**
+   * The menu router path to be used to view the entity.
+   *
+   * @var string (optional)
+   */
+  public $menu_view_path;
+
+  /**
+   * The menu router path to be used to edit the entity.
+   *
+   * @var string (optional)
+   */
+  public $menu_edit_path;
+
+  /**
+   * A string identifying the menu loader in the router path.
+   *
+   * @var string (optional)
+   */
+  public $menu_path_wildcard;
+
+  /**
+   * Link templates using the URI template syntax.
+   *
+   * Links are an array of standard link relations to the URI template that
+   * should be used for them. Where possible, link relationships should use
+   * established IANA relationships rather than custom relationships.
+   *
+   * Every entity type should, at minimum, define "canonical", which is the
+   * pattern for URIs to that entity. Even if the entity will have no HTML page
+   * exposed to users it should still have a canonical URI in order to be
+   * compatible with web services. Entities that will be user-editable via an
+   * HTML page must also define an "edit-form" relationship.
+   *
+   * By default, the following placeholders are supported:
+   * - entityType: The machine name of the entity type.
+   * - bundle: The bundle machine name of the entity.
+   * - id: The unique ID of the entity.
+   * - uuid: The UUID of the entity.
+   * - [entityType]: The entity type itself will also be a valid token for the
+   *   ID of the entity. For instance, a placeholder of {node} used on the Node
+   *   class would have the same value as {id}. This is generally preferred
+   *   over "id" for better self-documentation.
+   *
+   * Specific entity types may also expand upon this list by overriding the
+   * uriPlaceholderReplacements() method.
+   *
+   * @link http://www.iana.org/assignments/link-relations/link-relations.xml @endlink
+   * @link http://tools.ietf.org/html/rfc6570 @endlink
+   *
+   * @var array
+   */
+  public $links = array(
+    'canonical' => '/entity/{entityType}/{id}',
+  );
+
+  /**
+   * The default operation for a form controller.
+   *
+   * @var string
+   */
+  public $default_operation = 'default';
+
+  /**
+   * @var callable
+   */
+  public $uri_callback;
+
+  /**
+   * Specifies whether a module exposing permissions for the current entity type
+   * should use entity-type level granularity, bundle level granularity or just
+   * skip this entity. The allowed values are respectively "entity_type",
+   * "bundle" or FALSE.
+   *
+   * @var string|bool (optional)
+   */
+  public $permission_granularity = 'entity_type';
+
+  /**
+   * @todo
+   */
+  public $config_prefix;
+
+  /**
+   * Constructs a EntityType object.
+   *
+   * Builds up the plugin definition and invokes the get() method for any
+   * classed annotations that were used.
+   */
+  public function __construct($values) {
+    foreach ($values as $key => $value) {
+      $this->{$key} = $value;
+    }
+  }
+
+  /**
+   * Returns the name of the module providing the entity type.
+   *
+   * @return string
+   */
+  public function getModule() {
+    return $this->module;
+  }
+
+  /**
+   * Returns the name of the entity type class.
+   *
+   * This is not provided manually, it will be added by the discovery mechanism.
+   *
+   * @return string
+   */
+  public function getClass() {
+    return $this->class;
+  }
+
+  /**
+   * Sets the name of the entity type class.
+   *
+   * @param string $class
+   *   The class + full namespace of the entity type.
+   *
+   * @return string
+   */
+  public function setClass($class) {
+    return $this->class = $class;
+  }
+
+  /**
+   * The name of the entity type's base table.
+   *
+   * @todo This is only used by \Drupal\Core\Entity\DatabaseStorageController.
+   *
+   * @return string
+   */
+  public function getBaseTable() {
+    return $this->base_table;
+  }
+
+  /**
+   * Whether the entity type has a base table
+   *
+   * @return bool
+   *   Returns TRUE if the entity type has a base table else FALSE.
+   */
+  public function hasBaseTable() {
+    return isset($this->base_table);
+  }
+
+  /**
+   * The data table of the entity type.
+   *
+   * @return string|FALSE
+   *   Returns the data table else FALSE.
+   */
+  public function getDataTable() {
+    return $this->hasDataTable() ? $this->data_table : FALSE;
+  }
+
+  /**
+   * Whether the entity type has a data table.
+   *
+   * @return bool
+   *   Returns TRUE if the entity type has a data table else FALSE.
+   */
+  public function hasDataTable() {
+    return isset($this->data_table);
+  }
+
+  /**
+   * Whether the entity type has a revision table.
+   *
+   * @return bool
+   *   Returns TRUE if the entity type has a revision table else FALSE.
+   */
+  public function hasRevisionTable() {
+    return isset($this->revision_table);
+  }
+
+  /**
+   * The revision table of the entity type.
+   *
+   * @return string|FALSE
+   *   Returns the revision table else FALSE.
+   */
+  public function getRevisionTable() {
+    return $this->hasRevisionTable() ? $this->revision_table : FALSE;
+  }
+
+  /**
+   * An associative array where the keys are the names of different controller
+   * types (listed below) and the values are the names of the classes that
+   * implement that controller:
+   * - storage: The name of the class that is used to load the objects. The
+   *   class must implement \Drupal\Core\Entity\EntityStorageControllerInterface.
+   * - form: An associative array where the keys are the names of the different
+   *   form operations (such as 'create', 'edit', or 'delete') and the values
+   *   are the names of the controller classes for those operations. The name of
+   *   the operation is passed also to the form controller's constructor, so
+   *   that one class can be used for multiple entity forms when the forms are
+   *   similar. The classes must implement
+   *   \Drupal\Core\Entity\EntityFormControllerInterface
+   * - list: The name of the class that provides listings of the entities. The
+   *   class must implement \Drupal\Core\Entity\EntityListControllerInterface.
+   * - render: The name of the class that is used to render the entities. The
+   *   class must implement \Drupal\Core\Entity\EntityRenderControllerInterface.
+   * - access: The name of the class that is used for access checks. The class
+   *   must implement \Drupal\Core\Entity\EntityAccessControllerInterface.
+   *   Defaults to \Drupal\Core\Entity\EntityAccessController.
+   * - translation: The name of the controller class that should be used to
+   *   handle the translation process. The class must implement
+   *   \Drupal\translation_entity\EntityTranslationControllerInterface.
+   *
+   * @todo Interfaces from outside \Drupal\Core or \Drupal\Component should not
+   *   be used here.
+   *
+   * @return array
+   */
+  public function getControllers() {
+    return $this->controllers + array(
+      'form' => array(),
+      'access' => 'Drupal\Core\Entity\EntityAccessController',
+    );
+  }
+
+  /**
+   * @param string $controller
+   *
+   * @return string|array
+   */
+  public function getController($controller) {
+    $controllers = $this->getControllers();
+    return $controllers[$controller];
+  }
+
+  /**
+   * @param string $controller
+   * @param string|array $value
+   */
+  public function setController($controller, $value) {
+    $this->controllers[$controller] = $value;
+  }
+
+  /**
+   * @param string $controller
+   *
+   * @return bool
+   */
+  public function hasController($controller) {
+    $controllers = $this->getControllers();
+    return isset($controllers[$controller]);
+  }
+
+  /**
+   * Boolean indicating whether fields can be attached to entities of this type.
+   *
+   * @return bool (optional)
+   *   Returns TRUE if the entity type can has fields, otherwise FALSE.
+   */
+  public function isFieldable() {
+    return isset($this->fieldable) ? $this->fieldable : FALSE;
+  }
+
+  /**
+   * Boolean indicating if the persistent cache of field data should be used.
+   *
+   * The persistent cache should usually only be disabled if a higher level
+   * persistent cache is available for the entity type. Defaults to TRUE.
+   *
+   * @return bool (optional)
+   *   Returns TRUE if fields on this entity type can be cached, otherwise FALSE.
+   */
+  public function fieldsCacheable() {
+    return isset($this->field_cache) ? $this->field_cache : TRUE;
+  }
+
+  /**
+   * The human-readable name of the type.
+   */
+  public function getLabel() {
+    return $this->label;
+  }
+
+  /**
+   * The human-readable name of the entity bundles, e.g. Vocabulary.
+   */
+  public function getBundleLable() {
+    return $this->bundle_label;
+  }
+
+  /**
+   * The name of a function that returns the label of the entity.
+   *
+   * The function takes an entity and optional langcode argument, and returns
+   * the label of the entity. If langcode is omitted, the entity's default
+   * language is used. The entity label is the main string associated with an
+   * entity; for example, the title of a node or the subject of a comment. If
+   * there is an entity object property that defines the label, use the 'label'
+   * element of the 'entity_keys' return value component to provide this
+   * information (see below). If more complex logic is needed to determine the
+   * label of an entity, you can instead specify a callback function here, which
+   * will be called to determine the entity label. See also the
+   * \Drupal\Core\Entity\EntityInterface::label() method, which implements this
+   * logic.
+   *
+   * @return callable
+   *   A callback for the label.
+   */
+  public function getLabelCallback() {
+    return isset($this->label_callback) ? $this->label_callback : '';
+  }
+
+  /**
+   * @return callable
+   */
+  public function getUriCallback() {
+    return $this->uri_callback;
+  }
+
+  /**
+   * @param callable $callback
+   */
+  public function setUriCallback($callback) {
+    $this->uri_callback = $callback;
+  }
+
+  /**
+   * Boolean indicating whether entities should be statically cached during a page request.
+   *
+   * @todo This is only used by \Drupal\Core\Entity\DatabaseStorageController.
+   *
+   * @return bool (optional)
+   *   Returns TRUE if the entity type should be statically cached, otherwise FALSE.
+   */
+  public function staticCacheable() {
+    return isset($this->static_cache) ? $this->static_cache : TRUE;
+  }
+
+  /**
+   * An array describing how the Field API can extract certain information from
+   * objects of this entity type:
+   * - id: The name of the property that contains the primary ID of the entity.
+   *   Every entity object passed to the Field API must have this property and
+   *   its value must be numeric.
+   * - revision: (optional) The name of the property that contains the revision
+   *   ID of the entity. The Field API assumes that all revision IDs are unique
+   *   across all entities of a type. This entry can be omitted if the entities
+   *   of this type are not versionable.
+   * - bundle: (optional) The name of the property that contains the bundle name
+   *   for the entity. The bundle name defines which set of fields are attached
+   *   to the entity (e.g. what nodes call "content type"). This entry can be
+   *   omitted if this entity type exposes a single bundle (such that all
+   *   entities have the same collection of fields). The name of this single
+   *   bundle will be the same as the entity type.
+   * - label: The name of the property that contains the entity label. For
+   *   example, if the entity's label is located in $entity->subject, then
+   *   'subject' should be specified here. If complex logic is required to build
+   *   the label, a 'label_callback' should be defined instead (see the
+   *   $label_callback block above for details).
+   * - uuid (optional): The name of the property that contains the universally
+   *   unique identifier of the entity, which is used to distinctly identify an
+   *   entity across different systems.
+   *
+   * @var array
+   */
+  public function getKeys() {
+    return $this->entity_keys + array('revision' => '', 'bundle' => '');
+  }
+
+  /**
+   * @param $key
+   *
+   * @return bool
+   */
+  public function getKey($key) {
+    $keys = $this->getKeys();
+    return isset($keys[$key]) ? $keys[$key] : FALSE ;
+  }
+
+  /**
+   * @param $key
+   *
+   * @return bool
+   */
+  public function hasKey($key) {
+    $keys = $this->getKeys();
+    return !empty($keys[$key]);
+  }
+
+  /**
+   * Get all bundle keys defined on the annotation.
+   *
+   * @return array
+   *   An array describing how the Field API can extract the information it needs
+   *   from the bundle objects for this type (e.g Vocabulary objects for terms;
+   *   not applicable for nodes):
+   *   - bundle: The name of the property that contains the name of the bundle
+   *     object.
+   */
+  public function getBundleKeys() {
+    return isset($this->bundle_keys) ? $this->bundle_keys : array();
+  }
+
+  /**
+   * Returns a single bundle key.
+   *
+   * @param string $name
+   *   The name of the bundle key.
+   *
+   * @return string
+   *   The value of the bundle key.
+   */
+  public function getBundleKey($name) {
+    return isset($this->bundle_keys[$name]) ? $this->bundle_keys[$name] : '';
+  }
+
+  /**
+   * @return string
+   */
+  public function getConfigPrefix() {
+    return isset($this->config_prefix) ? $this->config_prefix : '';
+  }
+
+  /**
+   * Get the base router path for the entity type's field administration page.
+   *
+   * @return string
+   *   The router base path.
+   *
+   * @see \Drupal\Core\Entity\Annotation\EntityType::$route_base_path
+   */
+  public function getRouteBasePath() {
+    return isset($this->route_base_path) ? $this->route_base_path : NULL;
+  }
+
+  /**
+   * Get the prefix for the bundles of this entity type.
+   *
+   * @return string
+   *   The prefix.
+   *
+   * @see \Drupal\Core\Entity\Annotation\EntityType::$bundle_prefix
+   */
+  public function getBundlePrefix() {
+    return isset($this->bundle_prefix) ? $this->bundle_prefix : '';
+  }
+
+  /**
+   * Get the base menu router path for entity admin user interface paths.
+   *
+   * @return string
+   *   The base menu router path.
+   *
+   * @see \Drupal\Core\Entity\Annotation\EntityType::$menu_base_path
+   */
+  public function getMenuBasePath() {
+    return isset($this->menu_base_path) ? $this->menu_base_path : '';
+  }
+
+  /**
+   * Get the menu router path to be used to view the entity.
+   *
+   * @return string
+   *   A menu router path.
+   *
+   * @see \Drupal\Core\Entity\Annotation\EntityType::$menu_view_path
+   */
+  public function getMenuViewPath() {
+    return isset($this->menu_view_path) ? $this->menu_view_path : '';
+  }
+
+  /**
+   * Get the menu router path to be uesd to edit the entity.
+   *
+   * @return string
+   *   A menu router path.
+   *
+   * @see \Drupal\Core\Entity\Annotation\EntityType::$menu_edit_path
+   */
+  public function getMenuEditPath() {
+    return isset($this->menu_edit_path) ? $this->menu_edit_path : '';
+  }
+
+  /**
+   * Get the identifier in the router path.
+   *
+   * @return string
+   *   A identifier.
+   *
+   * @see \Drupal\Core\Entity\Annotation\EntityType::$menu_path_wildcard
+   */
+  public function getMenuPathWildcard() {
+    return isset($this->menu_path_wildcard) ? $this->menu_path_wildcard : '';
+  }
+
+  /**
+   * Returns the granularity of the permissions.
+   *
+   * @return string|FALSE
+   *   Returns either "entity_type", "bundle" or FALSE.
+   *
+   * @see \Drupal\Core\Entity\Annotation\EntityType::$permission_granularity
+   */
+  public function getPermissionGranularity() {
+    return $this->permission_granularity;
+  }
+
+  /**
+   * Returns whether the entity type is translatable.
+   *
+   * @return bool
+   *   Returns TRUE if the entity type is translatable, otherwise FALSE.
+   *
+   * @see \Drupal\Core\Entity\Annotation\\Drupal\Core\Entity\Annotation\EntityType::$translatable
+   */
+  public function isTranslatable() {
+    return isset($this->translatable) ? (bool) $this->translatable : FALSE;
+  }
+
+  public function setTranslatable($bool = TRUE) {
+    $this->translatable = $bool;
+  }
+
+  /**
+   * @todo
+   *
+   * @see \Drupal\Core\Entity\Annotation\\Drupal\Core\Entity\Annotation\EntityType::$translation
+   */
+  public function getTranslation() {
+    return isset($this->translation) ? $this->translation : array();
+  }
+
+  /**
+   * @return string
+   */
+  public function getDefaultOperation() {
+    return isset($this->default_operation) ? $this->default_operation : 'default';
+  }
+
+  /**
+   * Returns an arbitrary value of the entity type definition.
+   *
+   * @param string $name
+   *   The name of the entity type definition.
+   *
+   * @return mixed
+   *   The value of the entity type definition.
+   */
+  public function get($name) {
+    return isset($this->values[$name]) ? $this->values[$name] : NULL;
+  }
+
+  /**
+   */
+  public function offsetExists($offset) {
+    return isset($this->{$offset});
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function &offsetGet($offset) {
+    return $this->{$offset};
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function offsetSet($offset, $value) {
+    $this->{$offset} = $value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function offsetUnset($offset) {
+    unset($this->{$offset});
+  }
+}
diff --git a/core/lib/Drupal/Core/Entity/EntityTypes.php b/core/lib/Drupal/Core/Entity/EntityTypes.php
new file mode 100644
index 0000000..ac25a4d
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/EntityTypes.php
@@ -0,0 +1,169 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\EntityTypes
+ */
+
+namespace Drupal\Core\Entity;
+
+use Drupal\Core\Discovery\YamlDiscovery;
+use Drupal\Core\AnnotationReader;
+
+/**
+ * Defines an object which generates entity type domain objects.
+ */
+class EntityTypes {
+
+  /**
+   * @var \Drupal\Core\Entity\EntityType[]
+   */
+  protected $types = array();
+
+  /**
+   * @var bool
+   */
+  protected $preloaded = FALSE;
+
+  /**
+   * @var array
+   */
+  protected $class_mapping = array();
+
+  /**
+   * @var \Drupal\Core\Discovery\YamlDiscovery
+   */
+  protected $file_finder;
+
+  /**
+   * Returns all entity types defined in yml files.
+   *
+   * @return \Drupal\Core\Entity\Annotation\EntityType[]
+   */
+  public function findAll() {
+    if (!$this->preloaded) {
+      $class_mappings = $this->loadMappingsFromFiles();
+      foreach ($class_mappings as $name => $class) {
+        $this->types[$name] = $this->factory($class);
+      }
+      $this->invokeHooks();
+      $this->preloaded = TRUE;
+    }
+    return $this->types;
+  }
+
+  /**
+   * @param string $name
+   *
+   * @return \Drupal\Core\Entity\EntityType
+   * @throws \RuntimeException
+   */
+  public function findByName($name) {
+    if (isset($this->types[$name])) {
+      return $this->types[$name];
+    }
+
+    if ($class = $this->getClassFromEntityName($name)) {
+      $this->types[$name] = $this->factory($class);
+      $this->invokeHooks();
+      return $this->types[$name];
+    }
+    else {
+      throw new \RuntimeException(sprintf("No such entity type %s!", $name));
+    }
+  }
+
+  /**
+   * @param string $class
+   *
+   * @return \Drupal\Core\Entity\EntityType
+   */
+  protected function factory($class) {
+    $values = $this->getAnnotation($class) + array('class' => $class);
+    return new EntityType($values);
+  }
+
+  /**
+   */
+  protected function invokeHooks() {
+    foreach ($this->moduleHandler()->getImplementations('entity_info') as $module) {
+      $function = $module . '_entity_info';
+      $function($this->types);
+    }
+    $this->moduleHandler()->alter('entity_info', $this->types);
+  }
+
+  /**
+   * @param string $name
+   *
+   * @return bool
+   */
+  protected function getClassFromEntityName($name) {
+    $class_mappings = $this->loadMappingsFromFiles();
+    return isset($class_mappings[$name]) ? $class_mappings[$name] : FALSE;
+  }
+
+  /**
+   * @return array
+   */
+  protected function loadMappingsFromFiles() {
+    if (empty($this->class_mapping)) {
+      $all = $this->fileFinder()->findAll();
+      $this->class_mapping = call_user_func_array("array_merge", $all);
+    }
+    return $this->class_mapping;
+  }
+
+  /**
+   * @return string
+   */
+  protected function name() {
+    return 'entity';
+  }
+
+  /**
+   * @param string $classname
+   *
+   * @return mixed
+   */
+  protected function getAnnotation($classname) {
+    $entity_type_annotation = $this->getAnnotationReader()
+      ->getClassAnnotation($classname, 'Drupal\Core\Entity\Annotation\EntityType');
+    return $entity_type_annotation->get();
+  }
+
+  /**
+   * @return \Drupal\Core\Discovery\YamlDiscovery
+   */
+  protected function fileFinder() {
+    if (!isset($this->file_finder)) {
+      $this->file_finder = new YamlDiscovery($this->name(), $this->directories());
+    }
+    return $this->file_finder;
+  }
+
+  /**
+   * @return array
+   */
+  protected function directories() {
+    return $this->moduleHandler()->getModuleDirectories();
+  }
+
+  /**
+   * @return \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected function moduleHandler() {
+    return \Drupal::moduleHandler();
+  }
+
+  /**
+   * @return AnnotationReader
+   */
+  protected function getAnnotationReader() {
+    if (!isset($this->annotation_reader)) {
+      $this->annotation_reader = new AnnotationReader();
+    }
+    return $this->annotation_reader;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Entity/Plugin/DataType/Deriver/EntityDeriver.php b/core/lib/Drupal/Core/Entity/Plugin/DataType/Deriver/EntityDeriver.php
index 67ebea6..bf753c8 100644
--- a/core/lib/Drupal/Core/Entity/Plugin/DataType/Deriver/EntityDeriver.php
+++ b/core/lib/Drupal/Core/Entity/Plugin/DataType/Deriver/EntityDeriver.php
@@ -43,8 +43,8 @@ public function getDerivativeDefinitions(array $base_plugin_definition) {
     // Add definitions for each entity type and bundle.
     foreach (entity_get_info() as $entity_type => $info) {
       $this->derivatives[$entity_type] = array(
-        'label' => $info['label'],
-        'class' => $info['class'],
+        'label' => $info->getLabel(),
+        'class' => $info->getClass(),
         'constraints' => array('EntityType' => $entity_type),
       ) + $base_plugin_definition;
 
@@ -53,7 +53,7 @@ public function getDerivativeDefinitions(array $base_plugin_definition) {
         if ($bundle !== $entity_type) {
           $this->derivatives[$entity_type . ':' . $bundle] = array(
             'label' => $bundle_info['label'],
-            'class' => $info['class'],
+            'class' => $info->getClass(),
             'constraints' => array(
               'EntityType' => $entity_type,
               'Bundle' => $bundle,
diff --git a/core/lib/Drupal/Core/Entity/Query/Sql/Query.php b/core/lib/Drupal/Core/Entity/Query/Sql/Query.php
index 6fbae92..e786285 100644
--- a/core/lib/Drupal/Core/Entity/Query/Sql/Query.php
+++ b/core/lib/Drupal/Core/Entity/Query/Sql/Query.php
@@ -21,7 +21,7 @@ class Query extends QueryBase implements QueryInterface {
   /**
    * Contains the entity info for the entity type of that query.
    *
-   * @var array
+   * @var \Drupal\Core\Entity\Annotation\EntityType
    *
    * @see \Drupal\Core\Entity\EntityManager
    */
@@ -117,19 +117,19 @@ public function execute() {
   protected function prepare() {
     $entity_type = $this->entityType;
     $this->entityInfo = $this->entityManager->getDefinition($entity_type);
-    if (!isset($this->entityInfo['base_table'])) {
+    if (!($base_table = $this->entityInfo->getBaseTable())) {
       throw new QueryException("No base table, invalid query.");
     }
-    $base_table = $this->entityInfo['base_table'];
+    $base_table = $base_table;
     $simple_query = TRUE;
-    if (isset($this->entityInfo['data_table'])) {
+    if ($this->entityInfo->getDataTable()) {
       $simple_query = FALSE;
     }
     $this->sqlQuery = $this->connection->select($base_table, 'base_table', array('conjunction' => $this->conjunction));
     $this->sqlQuery->addMetaData('entity_type', $entity_type);
-    $id_field = $this->entityInfo['entity_keys']['id'];
+    $id_field = $this->entityInfo->getKey('id');
     // Add the key field for fetchAllKeyed().
-    if (empty($this->entityInfo['entity_keys']['revision'])) {
+    if (!$this->entityInfo->hasKey('revision')) {
       // When there is no revision support, the key field is the entity key.
       $this->sqlFields["base_table.$id_field"] = array('base_table', $id_field);
       // Now add the value column for fetchAllKeyed(). This is always the
@@ -138,7 +138,7 @@ protected function prepare() {
     }
     else {
       // When there is revision support, the key field is the revision key.
-      $revision_field = $this->entityInfo['entity_keys']['revision'];
+      $revision_field = $this->entityInfo->getKey('revision');
       $this->sqlFields["base_table.$revision_field"] = array('base_table', $revision_field);
       // Now add the value column for fetchAllKeyed(). This is always the
       // entity id.
diff --git a/core/lib/Drupal/Core/Entity/Query/Sql/Tables.php b/core/lib/Drupal/Core/Entity/Query/Sql/Tables.php
index 2f7156a..6b5da10 100644
--- a/core/lib/Drupal/Core/Entity/Query/Sql/Tables.php
+++ b/core/lib/Drupal/Core/Entity/Query/Sql/Tables.php
@@ -86,16 +86,16 @@ function addField($field, $type, $langcode) {
     for ($key = 0; $key <= $count; $key ++) {
       // If there is revision support and only the current revision is being
       // queried then use the revision id. Otherwise, the entity id will do.
-      if (!empty($entity_info['entity_keys']['revision']) && $age == FIELD_LOAD_CURRENT) {
+      if ($entity_info->hasKey('revision') && $age == FIELD_LOAD_CURRENT) {
         // This contains the relevant SQL field to be used when joining entity
         // tables.
-        $entity_id_field = $entity_info['entity_keys']['revision'];
+        $entity_id_field = $entity_info->getKey('revision');
         // This contains the relevant SQL field to be used when joining field
         // tables.
         $field_id_field = 'revision_id';
       }
       else {
-        $entity_id_field = $entity_info['entity_keys']['id'];
+        $entity_id_field = $entity_info->getKey('id');
         $field_id_field = 'entity_id';
       }
       // This can either be the name of an entity property (non-configurable
@@ -166,11 +166,13 @@ function addField($field, $type, $langcode) {
         // finds the property first. The data table is prefered, which is why
         // it gets added before the base table.
         $entity_tables = array();
-        if (isset($entity_info['data_table'])) {
+        if ($entity_info->hasDataTable()) {
           $this->sqlQuery->addMetaData('simple_query', FALSE);
-          $entity_tables[$entity_info['data_table']] = drupal_get_schema($entity_info['data_table']);
+          $data_table = $entity_info->getDataTable();
+          $entity_tables[$data_table] = drupal_get_schema($data_table);
         }
-        $entity_tables[$entity_info['base_table']] = drupal_get_schema($entity_info['base_table']);
+        $entity_base_table = $entity_info->getBaseTable();
+        $entity_tables[$entity_base_table] = drupal_get_schema($entity_base_table);
         $sql_column = $specifier;
         $table = $this->ensureEntityTable($index_prefix, $specifier, $type, $langcode, $base_table, $entity_id_field, $entity_tables);
       }
@@ -184,8 +186,8 @@ function addField($field, $type, $langcode) {
           $values = array();
           // If there are bundles, pick one. It does not matter which,
           // properties exist on all bundles.
-          if (!empty($entity_info['entity keys']['bundle'])) {
-            $values[$entity_info['entity keys']['bundle']] = key(entity_get_bundles('node'));
+          if ($entity_info->hasKey('bundle')) {
+            $values[$entity_info->getKey('bundle')] = key(entity_get_bundles('node'));
           }
           $entity = entity_create($entity_type, $values);
           $propertyDefinitions = $entity->$specifier->getPropertyDefinitions();
@@ -198,8 +200,8 @@ function addField($field, $type, $langcode) {
           $entity_type = $propertyDefinitions[$relationship_specifier]['constraints']['EntityType'];
           $entity_info = entity_get_info($entity_type);
           // Add the new entity base table using the table and sql column.
-          $join_condition= '%alias.' . $entity_info['entity_keys']['id'] . " = $table.$sql_column";
-          $base_table = $this->sqlQuery->leftJoin($entity_info['base_table'], NULL, $join_condition);
+          $join_condition= '%alias.' . $entity_info->getKey('id') . " = $table.$sql_column";
+          $base_table = $this->sqlQuery->leftJoin($entity_info->getBaseTable(), NULL, $join_condition);
           $propertyDefinitions = array();
           $key++;
           $index_prefix .= "$next_index_prefix.";
diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php
index c510192..da68a94 100644
--- a/core/lib/Drupal/Core/Extension/ModuleHandler.php
+++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php
@@ -900,4 +900,14 @@ public function uninstall($module_list = array(), $uninstall_dependents = TRUE)
     return TRUE;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getModuleDirectories() {
+    $dirs = array();
+    foreach ($this->getModuleList() as $module => $filename) {
+      $dirs[$module] = dirname($filename);
+    }
+    return $dirs;
+  }
 }
diff --git a/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php b/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php
index 44284b9..0cee914 100644
--- a/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php
+++ b/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php
@@ -335,4 +335,9 @@ public function disable($module_list, $disable_dependents = TRUE);
    */
   public function uninstall($module_list = array(), $uninstall_dependents = TRUE);
 
+  /**
+   * @return array
+   */
+  public function getModuleDirectories();
+
 }
diff --git a/core/lib/Drupal/Core/ParamConverter/EntityConverter.php b/core/lib/Drupal/Core/ParamConverter/EntityConverter.php
index 50b7d7c..a8f0ff2 100644
--- a/core/lib/Drupal/Core/ParamConverter/EntityConverter.php
+++ b/core/lib/Drupal/Core/ParamConverter/EntityConverter.php
@@ -71,8 +71,6 @@ public function process(array &$variables, Route $route, array &$converted) {
     $options = $route->getOptions();
     $configuredTypes = isset($options['converters']) ? $options['converters'] : array();
 
-    $entityTypes = array_keys($this->entityManager->getDefinitions());
-
     foreach ($variable_names as $name) {
       // Do not process this variable if it's already marked as converted.
       if (in_array($name, $converted)) {
@@ -88,7 +86,7 @@ public function process(array &$variables, Route $route, array &$converted) {
         $type = $name;
       }
 
-      if (in_array($type, $entityTypes)) {
+      if ($this->entityManager->getDefinition($type)) {
         $value = $variables[$name];
 
         $storageController = $this->entityManager->getStorageController($type);
diff --git a/core/modules/action/action.module b/core/modules/action/action.module
index 58bb4a8..8778a68 100644
--- a/core/modules/action/action.module
+++ b/core/modules/action/action.module
@@ -71,8 +71,12 @@ function action_menu() {
  * Implements hook_entity_info().
  */
 function action_entity_info(&$entity_info) {
-  $entity_info['action']['controllers']['form']['add'] = 'Drupal\action\ActionAddFormController';
-  $entity_info['action']['controllers']['form']['edit'] = 'Drupal\action\ActionEditFormController';
-  $entity_info['action']['controllers']['form']['delete'] = 'Drupal\action\Form\ActionDeleteForm';
-  $entity_info['action']['controllers']['list'] = 'Drupal\action\ActionListController';
+  if (isset($entity_info['action'])) {
+    $controllers = $entity_info['action']->getController('form');
+    $controllers['add'] = 'Drupal\action\ActionAddFormController';
+    $controllers['edit'] = 'Drupal\action\ActionEditFormController';
+    $controllers['delete'] = 'Drupal\action\Form\ActionDeleteForm';
+    $entity_info['action']->setController('form', $controllers);
+    $entity_info['action']->setController('list', 'Drupal\action\ActionListController');
+  }
 }
diff --git a/core/modules/action/lib/Drupal/action/ActionAddFormController.php b/core/modules/action/lib/Drupal/action/ActionAddFormController.php
index 56d7bbf..1249db2 100644
--- a/core/modules/action/lib/Drupal/action/ActionAddFormController.php
+++ b/core/modules/action/lib/Drupal/action/ActionAddFormController.php
@@ -11,6 +11,7 @@
 use Drupal\Core\Action\ActionManager;
 use Drupal\Core\Entity\EntityControllerInterface;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
+use Drupal\Core\Entity\EntityType;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -45,7 +46,7 @@ public function __construct(ModuleHandlerInterface $module_handler, EntityStorag
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, $entity_type, EntityType $entity_info) {
     return new static(
       $container->get('module_handler'),
       $container->get('plugin.manager.entity')->getStorageController($entity_type),
diff --git a/core/modules/action/lib/Drupal/action/ActionFormControllerBase.php b/core/modules/action/lib/Drupal/action/ActionFormControllerBase.php
index ff48164..3828ce0 100644
--- a/core/modules/action/lib/Drupal/action/ActionFormControllerBase.php
+++ b/core/modules/action/lib/Drupal/action/ActionFormControllerBase.php
@@ -12,6 +12,7 @@
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Action\ConfigurableActionInterface;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
+use Drupal\Core\Entity\EntityType;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -50,7 +51,7 @@ public function __construct(ModuleHandlerInterface $module_handler, EntityStorag
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, $entity_type, EntityType $entity_info) {
     return new static(
       $container->get('module_handler'),
       $container->get('plugin.manager.entity')->getStorageController($entity_type)
diff --git a/core/modules/action/lib/Drupal/action/ActionListController.php b/core/modules/action/lib/Drupal/action/ActionListController.php
index f3d5848..8a80d88 100644
--- a/core/modules/action/lib/Drupal/action/ActionListController.php
+++ b/core/modules/action/lib/Drupal/action/ActionListController.php
@@ -13,6 +13,7 @@
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Config\Entity\ConfigEntityListController;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
+use Drupal\Core\Entity\EntityType;
 use \Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\action\Form\ActionAdminManageForm;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -39,7 +40,7 @@ class ActionListController extends ConfigEntityListController implements EntityC
    *
    * @param string $entity_type
    *   The entity type.
-   * @param array $entity_info
+   * @param \Drupal\Core\Entity\EntityType $entity_info
    *   An array of entity info for the entity type.
    * @param \Drupal\Core\Entity\EntityStorageControllerInterface $storage
    *   The action storage controller.
@@ -48,7 +49,7 @@ class ActionListController extends ConfigEntityListController implements EntityC
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
    *   The module handler to invoke hooks on.
    */
-  public function __construct($entity_type, array $entity_info, EntityStorageControllerInterface $storage, ActionManager $action_manager, ModuleHandlerInterface $module_handler) {
+  public function __construct($entity_type, EntityType $entity_info, EntityStorageControllerInterface $storage, ActionManager $action_manager, ModuleHandlerInterface $module_handler) {
     parent::__construct($entity_type, $entity_info, $storage, $module_handler);
 
     $this->actionManager = $action_manager;
@@ -57,7 +58,7 @@ public function __construct($entity_type, array $entity_info, EntityStorageContr
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, $entity_type, EntityType $entity_info) {
     return new static(
       $entity_type,
       $entity_info,
diff --git a/core/modules/aggregator/aggregator.entity.yml b/core/modules/aggregator/aggregator.entity.yml
new file mode 100644
index 0000000..619c7f7
--- /dev/null
+++ b/core/modules/aggregator/aggregator.entity.yml
@@ -0,0 +1,2 @@
+aggregator_feed: Drupal\aggregator\Plugin\Core\Entity\Feed
+aggregator_item: Drupal\aggregator\Plugin\Core\Entity\Item
diff --git a/core/modules/block/block.entity.yml b/core/modules/block/block.entity.yml
new file mode 100644
index 0000000..a574c3f
--- /dev/null
+++ b/core/modules/block/block.entity.yml
@@ -0,0 +1 @@
+block: Drupal\block\Plugin\Core\Entity\Block
diff --git a/core/modules/block/custom_block/custom_block.entity.yml b/core/modules/block/custom_block/custom_block.entity.yml
new file mode 100644
index 0000000..f633ff4
--- /dev/null
+++ b/core/modules/block/custom_block/custom_block.entity.yml
@@ -0,0 +1,2 @@
+custom_block: Drupal\custom_block\Plugin\Core\Entity\CustomBlock
+custom_block_type: Drupal\custom_block\Plugin\Core\Entity\CustomBlockType
diff --git a/core/modules/block/custom_block/custom_block.module b/core/modules/block/custom_block/custom_block.module
index 4e55a85..6913ca4 100644
--- a/core/modules/block/custom_block/custom_block.module
+++ b/core/modules/block/custom_block/custom_block.module
@@ -146,8 +146,8 @@ function custom_block_load($id) {
  */
 function custom_block_entity_info_alter(&$types) {
   // Add a translation handler for fields if the language module is enabled.
-  if (module_exists('language')) {
-    $types['custom_block']['translation']['custom_block'] = TRUE;
+  if (isset($types['custom_block']) && module_exists('language')) {
+    $types['custom_block']->translation['custom_block'] = TRUE;
   }
 }
 
diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/Controller/CustomBlockController.php b/core/modules/block/custom_block/lib/Drupal/custom_block/Controller/CustomBlockController.php
index 03edb7f..9b220ae 100644
--- a/core/modules/block/custom_block/lib/Drupal/custom_block/Controller/CustomBlockController.php
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/Controller/CustomBlockController.php
@@ -9,6 +9,7 @@
 
 use Drupal\Component\Plugin\PluginManagerInterface;
 use Drupal\Core\Controller\ControllerInterface;
+use Drupal\Core\Entity\EntityManager;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
 use Drupal\custom_block\CustomBlockTypeInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -19,7 +20,7 @@ class CustomBlockController implements ControllerInterface {
   /**
    * The entity manager.
    *
-   * @var \Drupal\Component\Plugin\PluginManagerInterface
+   * @var \Drupal\Core\Entity\EntityManager
    */
   protected $entityManager;
 
@@ -52,14 +53,14 @@ public static function create(ContainerInterface $container) {
   /**
    * Constructs a CustomBlock object.
    *
-   * @param \Drupal\Component\Plugin\PluginManagerInterface $entity_manager
+   * @param \Drupal\Core\Entity\EntityManager $entity_manager
    *   The entity manager.
    * @param \Drupal\Core\Entity\EntityStorageControllerInterface $custom_block_storage
    *   The custom block storage controller.
    * @param \Drupal\Core\Entity\EntityStorageControllerInterface $custom_block_type_storage
    *   The custom block type storage controller.
    */
-  public function __construct(PluginManagerInterface $entity_manager, EntityStorageControllerInterface $custom_block_storage, EntityStorageControllerInterface $custom_block_type_storage) {
+  public function __construct(EntityManager $entity_manager, EntityStorageControllerInterface $custom_block_storage, EntityStorageControllerInterface $custom_block_type_storage) {
     $this->customBlockStorage = $custom_block_storage;
     $this->customBlockTypeStorage = $custom_block_type_storage;
     $this->entityManager = $entity_manager;
diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/Form/CustomBlockTypeDeleteForm.php b/core/modules/block/custom_block/lib/Drupal/custom_block/Form/CustomBlockTypeDeleteForm.php
index 7e595d2..5c89b97 100644
--- a/core/modules/block/custom_block/lib/Drupal/custom_block/Form/CustomBlockTypeDeleteForm.php
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/Form/CustomBlockTypeDeleteForm.php
@@ -9,6 +9,7 @@
 
 use Drupal\Core\Entity\EntityConfirmFormBase;
 use Drupal\Core\Entity\EntityControllerInterface;
+use Drupal\Core\Entity\EntityType;
 use Drupal\Core\Entity\Query\QueryFactory;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -42,7 +43,7 @@ public function __construct(ModuleHandlerInterface $module_handler, QueryFactory
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, $entity_type, EntityType $entity_info) {
     return new static(
       $container->get('module_handler'),
       $container->get('entity.query')
diff --git a/core/modules/breakpoint/breakpoint.entity.yml b/core/modules/breakpoint/breakpoint.entity.yml
new file mode 100644
index 0000000..278b03f
--- /dev/null
+++ b/core/modules/breakpoint/breakpoint.entity.yml
@@ -0,0 +1,2 @@
+breakpoint: Drupal\breakpoint\Plugin\Core\Entity\Breakpoint
+breakpoint_group: Drupal\breakpoint\Plugin\Core\Entity\BreakpointGroup
diff --git a/core/modules/breakpoint/breakpoint.module b/core/modules/breakpoint/breakpoint.module
index ce3c894..b3daa6c 100644
--- a/core/modules/breakpoint/breakpoint.module
+++ b/core/modules/breakpoint/breakpoint.module
@@ -192,7 +192,7 @@ function _breakpoint_delete_breakpoints($list, $source_type) {
 
   // Remove the breakpoint.breakpoint part of the breakpoint identifier.
   foreach ($ids as &$id) {
-    $id = ConfigStorageController::getIDFromConfigName($id, $entity_info['config_prefix']);
+    $id = ConfigStorageController::getIDFromConfigName($id, $entity_info->getConfigPrefix());
   }
   $breakpoint_groups = entity_load_multiple('breakpoint_group', $ids);
 
@@ -207,7 +207,7 @@ function _breakpoint_delete_breakpoints($list, $source_type) {
 
       // Remove the breakpoint.breakpoint part of the breakpoint identifier.
       foreach ($breakpoint_ids as &$breakpoint_id) {
-        $breakpoint_id = ConfigStorageController::getIDFromConfigName($breakpoint_id, $entity_info['config_prefix']);
+        $breakpoint_id = ConfigStorageController::getIDFromConfigName($breakpoint_id, $entity_info->getConfigPrefix());
       }
       $breakpoints = entity_load_multiple('breakpoint', $breakpoint_ids);
 
diff --git a/core/modules/comment/comment.entity.yml b/core/modules/comment/comment.entity.yml
new file mode 100644
index 0000000..014f640
--- /dev/null
+++ b/core/modules/comment/comment.entity.yml
@@ -0,0 +1 @@
+comment: Drupal\comment\Plugin\Core\Entity\Comment
diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentTranslationUITest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentTranslationUITest.php
index 49d6814..20da786 100644
--- a/core/modules/comment/lib/Drupal/comment/Tests/CommentTranslationUITest.php
+++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentTranslationUITest.php
@@ -93,10 +93,10 @@ protected function getNewEntityValues($langcode) {
   }
 
   /**
-   * Overrides \Drupal\content_translation\Tests\ContentTranslationUITest::assertPublishedStatus().
+   * Overrides \Drupal\content_translation\Tests\ContentTranslationUITest::doPublishedStatus().
    */
-  protected function assertPublishedStatus() {
-    parent::assertPublishedStatus();
+  protected function doPublishedStatus() {
+    parent::doPublishedStatus();
     $entity = entity_load($this->entityType, $this->entityId);
     $user = $this->drupalCreateUser(array('access comments'));
     $this->drupalLogin($user);
diff --git a/core/modules/comment/tests/modules/comment_test/comment_test.module b/core/modules/comment/tests/modules/comment_test/comment_test.module
index 8e903db..47831ca 100644
--- a/core/modules/comment/tests/modules/comment_test/comment_test.module
+++ b/core/modules/comment/tests/modules/comment_test/comment_test.module
@@ -10,8 +10,8 @@
  * Implements hook_entity_info_alter().
  */
 function comment_test_entity_info_alter(&$info) {
-  if (language_multilingual()) {
+  if (isset($info['comment']) && language_multilingual()) {
     // Enable language handling for comment fields.
-    $info['comment']['translation']['comment_test'] = TRUE;
+    $info['comment']->translation['comment_test'] = TRUE;
   }
 }
diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigEntityUnitTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigEntityUnitTest.php
index 8be1055..cb2e8e0 100644
--- a/core/modules/config/lib/Drupal/config/Tests/ConfigEntityUnitTest.php
+++ b/core/modules/config/lib/Drupal/config/Tests/ConfigEntityUnitTest.php
@@ -36,13 +36,13 @@ public function testStorageControllerMethods() {
     $controller = $this->container->get('plugin.manager.entity')->getStorageController('config_test');
     $info = entity_get_info('config_test');
 
-    $expected = $info['config_prefix'] . '.';
+    $expected = $info->getConfigPrefix() . '.';
     $this->assertIdentical($controller->getConfigPrefix(), $expected);
 
     // Test the static extractID() method.
     $expected_id = 'test_id';
-    $config_name = $info['config_prefix'] . '.' . $expected_id;
-    $this->assertIdentical($controller::getIDFromConfigName($config_name, $info['config_prefix']), $expected_id);
+    $config_name = $info->getConfigPrefix() . '.' . $expected_id;
+    $this->assertIdentical($controller::getIDFromConfigName($config_name, $info->getConfigPrefix()), $expected_id);
 
     // Create three entities, two with the same style.
     $style = $this->randomName(8);
diff --git a/core/modules/config/tests/config_test/config_test.entity.yml b/core/modules/config/tests/config_test/config_test.entity.yml
new file mode 100644
index 0000000..2bd7f23
--- /dev/null
+++ b/core/modules/config/tests/config_test/config_test.entity.yml
@@ -0,0 +1,2 @@
+config_query_test: Drupal\config_test\Plugin\Core\Entity\ConfigQueryTest
+config_test: Drupal\config_test\Plugin\Core\Entity\ConfigTest
diff --git a/core/modules/config/tests/config_test/config_test.module b/core/modules/config/tests/config_test/config_test.module
index 41e7c62..03af72e 100644
--- a/core/modules/config/tests/config_test/config_test.module
+++ b/core/modules/config/tests/config_test/config_test.module
@@ -88,13 +88,17 @@ function config_test_config_test_create(ConfigTest $config_test) {
  * Implements hook_entity_info_alter().
  */
 function config_test_entity_info_alter(&$entity_info) {
+  if (!isset($entity_info['config_test'])) {
+    return;
+  }
+
   // The 'translatable' entity key is not supposed to change over time. In this
   // case we can safely do it because we set it once and we do not change it for
   // all the duration of the test session.
-  $entity_info['config_test']['translatable'] = Drupal::service('state')->get('config_test.translatable');
+  $entity_info['config_test']->translatable = Drupal::service('state')->get('config_test.translatable');
 
   // Create a clone of config_test that does not have a status.
-  $entity_info['config_test_no_status'] = $entity_info['config_test'];
-  unset($entity_info['config_test_no_status']['entity_keys']['status']);
-  $entity_info['config_test_no_status']['config_prefix'] = 'config_test.no_status';
+  $entity_info['config_test_no_status'] = clone $entity_info['config_test'];
+  unset($entity_info['config_test_no_status']->entity_keys['status']);
+  $entity_info['config_test_no_status']->config_prefix = 'config_test.no_status';
 }
diff --git a/core/modules/contact/contact.entity.yml b/core/modules/contact/contact.entity.yml
new file mode 100644
index 0000000..2e5325f
--- /dev/null
+++ b/core/modules/contact/contact.entity.yml
@@ -0,0 +1,2 @@
+contact_category: Drupal\contact\Plugin\Core\Entity\Category
+contact_message: Drupal\contact\Plugin\Core\Entity\Message
diff --git a/core/modules/content_translation/content_translation.admin.inc b/core/modules/content_translation/content_translation.admin.inc
index 63e71fc..f932658 100644
--- a/core/modules/content_translation/content_translation.admin.inc
+++ b/core/modules/content_translation/content_translation.admin.inc
@@ -84,7 +84,7 @@ function _content_translation_form_language_content_settings_form_alter(array &$
 
       // Only show the checkbox to enable translation if the bundles in the
       // entity might have fields and if there are fields to translate.
-      if (!empty($entity_info['fieldable'])) {
+      if ($entity_info->fieldsCacheable()) {
         $fields = field_info_instances($entity_type, $bundle);
         if ($fields) {
           $form['settings'][$entity_type][$bundle]['translatable'] = array(
@@ -446,7 +446,7 @@ function content_translation_translatable_batch($translatable, $field_name, &$co
     $query = Drupal::entityQuery($entity_type);
     $result = $query
       ->exists($query_field)
-      ->sort($info['entity_keys']['id'])
+      ->sort($info->getKey('id'))
       ->range($offset, 10)
       ->execute();
 
diff --git a/core/modules/content_translation/content_translation.module b/core/modules/content_translation/content_translation.module
index a6988cf..03a78fd 100644
--- a/core/modules/content_translation/content_translation.module
+++ b/core/modules/content_translation/content_translation.module
@@ -75,38 +75,44 @@ function content_translation_language_types_info_alter(array &$language_types) {
  */
 function content_translation_entity_info_alter(array &$entity_info) {
   // Provide defaults for translation info.
-  foreach ($entity_info as $entity_type => &$info) {
-    if (empty($info['translatable'])) {
+  /** @var $entity_info \Drupal\Core\Entity\EntityType[] */
+  foreach ($entity_info as $entity_type => $info) {
+    if (!$info->isTranslatable()) {
       continue;
     }
 
-    if (!isset($info['translation']['content_translation'])) {
-      $info['translation']['content_translation'] = array();
+    if (!isset($info->translation['content_translation'])) {
+      $info->translation['content_translation'] = array();
     }
 
     // Every fieldable entity type must have a translation controller class, no
     // matter if it is enabled for translation or not. As a matter of fact we
     // might need it to correctly switch field translatability when a field is
     // shared accross different entities.
-    $info['controllers'] += array('translation' => 'Drupal\content_translation\ContentTranslationController');
+    $info->setController('translation', 'Drupal\content_translation\ContentTranslationController');
 
     // If no menu base path is provided we default to the usual
     // "entity_type/%entity_type" pattern.
-    if (!isset($info['menu_base_path'])) {
+    if (!$info->getMenuBasePath()) {
       $path = "$entity_type/%$entity_type";
-      $info['menu_base_path'] = $path;
+      $info->menu_base_path = $path;
     }
 
-    $path = $info['menu_base_path'];
+    $path = $info->getMenuBasePath();
 
-    $info += array(
+    $default_info = array(
       'menu_view_path' => $path,
       'menu_edit_path' => "$path/edit",
       'menu_path_wildcard' => "%$entity_type",
     );
+    foreach ($default_info as $key => $value) {
+      if (!isset($info->{$key})) {
+        $info->{$key} = $value;
+      }
+    }
 
     $entity_position = count(explode('/', $path)) - 1;
-    $info['translation']['content_translation'] += array(
+    $info->translation['content_translation'] += array(
       'access_callback' => 'content_translation_translate_access',
       'access_arguments' => array($entity_position),
     );
@@ -135,10 +141,10 @@ function content_translation_menu() {
   foreach (entity_get_info() as $entity_type => $info) {
     // Provide the translation UI only for enabled types.
     if (content_translation_enabled($entity_type)) {
-      $path = $info['menu_base_path'];
+      $path = $info->getMenuBasePath();
       $entity_position = count(explode('/', $path)) - 1;
       $keys = array_flip(array('theme_callback', 'theme_arguments', 'access_callback', 'access_arguments', 'load_arguments'));
-      $menu_info = array_intersect_key($info['translation']['content_translation'], $keys) + array('file' => 'content_translation.pages.inc');
+      $menu_info = array_intersect_key($info->translation['content_translation'], $keys) + array('file' => 'content_translation.pages.inc');
       $item = array();
 
       // Plugin annotations cannot contain spaces, thus we need to restore them
@@ -216,7 +222,7 @@ function content_translation_menu_alter(array &$items) {
   // Check that the declared menu base paths are actually valid.
   foreach (entity_get_info() as $entity_type => $info) {
     if (content_translation_enabled($entity_type)) {
-      $path = $info['menu_base_path'];
+      $path = $info->getMenuBasePath();
 
       // If the base path is not defined we cannot provide the translation UI
       // for this entity type. In some cases the actual base path might not have
@@ -229,12 +235,11 @@ function content_translation_menu_alter(array &$items) {
           $items["$path/translations/add/%language/%language"],
           $items["$path/translations/delete/%language"]
         );
-        $t_args = array('@entity_type' => isset($info['label']) ? $info['label'] : $entity_type);
+        $t_args = array('@entity_type' => $info->getLabel() ?: $entity_type);
         watchdog('content translation', 'The entities of type @entity_type do not define a valid base path: it will not be possible to translate them.', $t_args, WATCHDOG_WARNING);
       }
       else {
-        $entity_position = count(explode('/', $path)) - 1;
-        $edit_path = $info['menu_edit_path'];
+        $edit_path = $info->getMenuEditPath();
 
         if (isset($items[$edit_path])) {
           // If the edit path is a default local task we need to find the parent
@@ -450,7 +455,7 @@ function content_translation_enabled($entity_type, $bundle = NULL) {
   $enabled = FALSE;
   $info = entity_get_info($entity_type);
 
-  if (!empty($info['translatable'])) {
+  if ($info->isTranslatable()) {
     $bundles = !empty($bundle) ? array($bundle) : array_keys(entity_get_bundles($entity_type));
     foreach ($bundles as $bundle) {
       if (content_translation_get_config($entity_type, $bundle, 'enabled')) {
@@ -481,7 +486,7 @@ function content_translation_types_translatable() {
       // Check whether the required paths are defined. We need to strip out the
       // menu loader and replace it with a plain "%" as router items have no
       // menu loader in them.
-      $path = _content_translation_menu_strip_loaders($info['menu_base_path']);
+      $path = _content_translation_menu_strip_loaders($info->getMenuBasePath());
       if (!empty($items[$path]) && !empty($items[$path . '/translations'])) {
         $entity_types[$entity_type] = $entity_type;
       }
@@ -503,7 +508,10 @@ function content_translation_types_translatable() {
 function content_translation_controller($entity_type) {
   $entity_info = entity_get_info($entity_type);
   // @todo Throw an exception if the key is missing.
-  return new $entity_info['controllers']['translation']($entity_type, $entity_info);
+  if ($entity_info->hasController('translation')) {
+    $translation_controller = $entity_info->getController('translation');
+    return new $translation_controller($entity_type, $entity_info);
+  }
 }
 
 /**
@@ -565,10 +573,10 @@ function content_translation_permission() {
   // Create a translate permission for each enabled entity type and (optionally)
   // bundle.
   foreach (entity_get_info() as $entity_type => $info) {
-    if (!empty($info['permission_granularity'])) {
-      $t_args = array('@entity_label' => drupal_strtolower(t($info['label'])));
+    if ($permission_granularity = $info->getPermissionGranularity()) {
+      $t_args = array('@entity_label' => drupal_strtolower(t($info->getLabel())));
 
-      switch ($info['permission_granularity']) {
+      switch ($permission_granularity) {
         case 'bundle':
           foreach (entity_get_bundles($entity_type) as $bundle => $bundle_info) {
             if (content_translation_enabled($entity_type, $bundle)) {
diff --git a/core/modules/content_translation/content_translation.pages.inc b/core/modules/content_translation/content_translation.pages.inc
index e277791..7c98e1e 100644
--- a/core/modules/content_translation/content_translation.pages.inc
+++ b/core/modules/content_translation/content_translation.pages.inc
@@ -195,8 +195,7 @@ function content_translation_add_page(EntityInterface $entity, Language $source
   $target = !empty($target) ? $target : language(Language::TYPE_CONTENT);
   // @todo Exploit the upcoming hook_entity_prepare() when available.
   content_translation_prepare_translation($entity, $source, $target);
-  $info = $entity->entityInfo();
-  $operation = isset($info['default_operation']) ? $info['default_operation'] : 'default';
+  $operation = $entity->entityInfo()->getDefaultOperation();
   $form_state['langcode'] = $target->id;
   $form_state['content_translation']['source'] = $source;
   $form_state['content_translation']['target'] = $target;
@@ -219,8 +218,7 @@ function content_translation_add_page(EntityInterface $entity, Language $source
  */
 function content_translation_edit_page(EntityInterface $entity, Language $language = NULL) {
   $language = !empty($language) ? $language : language(Language::TYPE_CONTENT);
-  $info = $entity->entityInfo();
-  $operation = isset($info['default_operation']) ? $info['default_operation'] : 'default';
+  $operation = $entity->entityInfo()->getDefaultOperation();
   $form_state['langcode'] = $language->id;
   $form_state['content_translation']['translation_form'] = TRUE;
   return Drupal::entityManager()->getForm($entity, $operation, $form_state);
diff --git a/core/modules/content_translation/lib/Drupal/content_translation/ContentTranslationController.php b/core/modules/content_translation/lib/Drupal/content_translation/ContentTranslationController.php
index 6948642..c288ea9 100644
--- a/core/modules/content_translation/lib/Drupal/content_translation/ContentTranslationController.php
+++ b/core/modules/content_translation/lib/Drupal/content_translation/ContentTranslationController.php
@@ -73,6 +73,7 @@ public function retranslate(EntityInterface $entity, $langcode = NULL) {
    * Implements ContentTranslationControllerInterface::getBasePath().
    */
   public function getBasePath(EntityInterface $entity) {
+    // @fixme How to replace this.
     return $this->getPathInstance($this->entityInfo['menu_base_path'], $entity->id());
   }
 
@@ -80,6 +81,7 @@ public function getBasePath(EntityInterface $entity) {
    * Implements ContentTranslationControllerInterface::getEditPath().
    */
   public function getEditPath(EntityInterface $entity) {
+    // @fixme How to replace this.
     return isset($this->entityInfo['menu_edit_path']) ? $this->getPathInstance($this->entityInfo['menu_edit_path'], $entity->id()) : FALSE;
   }
 
@@ -87,6 +89,7 @@ public function getEditPath(EntityInterface $entity) {
    * Implements ContentTranslationControllerInterface::getViewPath().
    */
   public function getViewPath(EntityInterface $entity) {
+    // @fixme How to replace this.
     return isset($this->entityInfo['menu_view_path']) ? $this->getPathInstance($this->entityInfo['menu_view_path'], $entity->id()) : FALSE;
   }
 
@@ -100,8 +103,8 @@ public function getTranslationAccess(EntityInterface $entity, $op) {
     $translate_permission = TRUE;
     // If no permission granularity is defined this entity type does not need an
     // explicit translate permission.
-    if (!user_access('translate any entity') && !empty($info['permission_granularity'])) {
-      $translate_permission = user_access($info['permission_granularity'] == 'bundle' ? "translate {$entity->bundle()} {$entity->entityType()}" : "translate {$entity->entityType()}");
+    if (!user_access('translate any entity') && $info->getPermissionGranularity()) {
+      $translate_permission = user_access($info->getPermissionGranularity() == 'bundle' ? "translate {$entity->bundle()} {$entity->entityType()}" : "translate {$entity->entityType()}");
     }
     return $translate_permission && user_access("$op content translations");
   }
@@ -512,6 +515,7 @@ protected function entityFormTitle(EntityInterface $entity) {
    *   The instantiated path.
    */
   protected function getPathInstance($path, $entity_id) {
+    // @fixme How to replace this.
     $wildcard = $this->entityInfo['menu_path_wildcard'];
     return str_replace($wildcard, $entity_id, $path);
   }
diff --git a/core/modules/content_translation/lib/Drupal/content_translation/Tests/ContentTranslationTestBase.php b/core/modules/content_translation/lib/Drupal/content_translation/Tests/ContentTranslationTestBase.php
index b635c45..6129fd5 100644
--- a/core/modules/content_translation/lib/Drupal/content_translation/Tests/ContentTranslationTestBase.php
+++ b/core/modules/content_translation/lib/Drupal/content_translation/Tests/ContentTranslationTestBase.php
@@ -110,9 +110,8 @@ protected function getTranslatorPermissions() {
    * Returns the translate permissions for the current entity and bundle.
    */
   protected function getTranslatePermission() {
-    $info = entity_get_info($this->entityType);
-    if (!empty($info['permission_granularity'])) {
-      return $info['permission_granularity'] == 'bundle' ? "translate {$this->bundle} {$this->entityType}" : "translate {$this->entityType}";
+    if (($info = entity_get_info($this->entityType)) && $info->getPermissionGranularity()) {
+      return $info->getPermissionGranularity() == 'bundle' ? "translate {$this->bundle} {$this->entityType}" : "translate {$this->entityType}";
     }
   }
 
@@ -199,8 +198,8 @@ protected function createEntity($values, $langcode, $bundle_name = NULL) {
     $entity_values = $values;
     $entity_values['langcode'] = $langcode;
     $info = entity_get_info($this->entityType);
-    if (!empty($info['entity_keys']['bundle'])) {
-      $entity_values[$info['entity_keys']['bundle']] = $bundle_name ?: $this->bundle;
+    if ($info->hasKey('bundle')) {
+      $entity_values[$info->getKey('bundle')] = $bundle_name ?: $this->bundle;
     }
     $controller = $this->container->get('plugin.manager.entity')->getStorageController($this->entityType);
     if (!($controller instanceof DatabaseStorageControllerNG)) {
diff --git a/core/modules/content_translation/lib/Drupal/content_translation/Tests/ContentTranslationUITest.php b/core/modules/content_translation/lib/Drupal/content_translation/Tests/ContentTranslationUITest.php
index 1e21d53..ccdc1bc 100644
--- a/core/modules/content_translation/lib/Drupal/content_translation/Tests/ContentTranslationUITest.php
+++ b/core/modules/content_translation/lib/Drupal/content_translation/Tests/ContentTranslationUITest.php
@@ -34,17 +34,17 @@
    * Tests the basic translation UI.
    */
   function testTranslationUI() {
-    $this->assertBasicTranslation();
-    $this->assertOutdatedStatus();
-    $this->assertPublishedStatus();
-    $this->assertAuthoringInfo();
-    $this->assertTranslationDeletion();
+    $this->doBasicTranslation();
+    $this->doOutdatedStatus();
+    $this->doPublishedStatus();
+    $this->doAuthoringInfo();
+    $this->doTranslationDeletion();
   }
 
   /**
    * Tests the basic translation workflow.
    */
-  protected function assertBasicTranslation() {
+  protected function doBasicTranslation() {
     // Create a new test entity with original values in the default language.
     $default_langcode = $this->langcodes[0];
     $values[$default_langcode] = $this->getNewEntityValues($default_langcode);
@@ -101,7 +101,7 @@ protected function assertBasicTranslation() {
   /**
    * Tests up-to-date status tracking.
    */
-  protected function assertOutdatedStatus() {
+  protected function doOutdatedStatus() {
     $entity = entity_load($this->entityType, $this->entityId, TRUE);
     $langcode = 'fr';
     $default_langcode = $this->langcodes[0];
@@ -134,7 +134,7 @@ protected function assertOutdatedStatus() {
   /**
    * Tests the translation publishing status.
    */
-  protected function assertPublishedStatus() {
+  protected function doPublishedStatus() {
     $entity = entity_load($this->entityType, $this->entityId, TRUE);
     $path = $this->controller->getEditPath($entity);
 
@@ -156,7 +156,7 @@ protected function assertPublishedStatus() {
   /**
    * Tests the translation authoring information.
    */
-  protected function assertAuthoringInfo() {
+  protected function doAuthoringInfo() {
     $entity = entity_load($this->entityType, $this->entityId, TRUE);
     $path = $this->controller->getEditPath($entity);
     $values = array();
@@ -198,7 +198,7 @@ protected function assertAuthoringInfo() {
   /**
    * Tests translation deletion.
    */
-  protected function assertTranslationDeletion() {
+  protected function doTranslationDeletion() {
     // Confirm and delete a translation.
     $langcode = 'fr';
     $entity = entity_load($this->entityType, $this->entityId, TRUE);
diff --git a/core/modules/editor/editor.entity.yml b/core/modules/editor/editor.entity.yml
new file mode 100644
index 0000000..371e17e
--- /dev/null
+++ b/core/modules/editor/editor.entity.yml
@@ -0,0 +1 @@
+editor: Drupal\editor\Plugin\Core\Entity\Editor
diff --git a/core/modules/entity/entity.entity.yml b/core/modules/entity/entity.entity.yml
new file mode 100644
index 0000000..3dbedf5
--- /dev/null
+++ b/core/modules/entity/entity.entity.yml
@@ -0,0 +1,4 @@
+entity_display: Drupal\entity\Plugin\Core\Entity\EntityDisplay
+entity_form_display: Drupal\entity\Plugin\Core\Entity\EntityFormDisplay
+form_mode: Drupal\entity\Plugin\Core\Entity\EntityFormMode
+view_mode: Drupal\entity\Plugin\Core\Entity\EntityViewMode
diff --git a/core/modules/entity/entity.module b/core/modules/entity/entity.module
index ad9d17b..b25816b 100644
--- a/core/modules/entity/entity.module
+++ b/core/modules/entity/entity.module
@@ -19,7 +19,7 @@ function entity_entity_bundle_rename($entity_type, $bundle_old, $bundle_new) {
   if ($bundle_old !== $bundle_new) {
     $ids = config_get_storage_names_with_prefix('entity.display.' . $entity_type . '.' . $bundle_old);
     foreach ($ids as $id) {
-      $id = ConfigStorageController::getIDFromConfigName($id, $entity_info['config_prefix']);
+      $id = ConfigStorageController::getIDFromConfigName($id, $entity_info->getConfigPrefix());
       $display = entity_load('entity_display', $id);
       $new_id = $entity_type . '.' . $bundle_new . '.' . $display->mode;
       $display->id = $new_id;
@@ -33,7 +33,7 @@ function entity_entity_bundle_rename($entity_type, $bundle_old, $bundle_new) {
   if ($bundle_old !== $bundle_new) {
     $ids = config_get_storage_names_with_prefix('entity.form_display.' . $entity_type . '.' . $bundle_old);
     foreach ($ids as $id) {
-      $id = ConfigStorageController::getIDFromConfigName($id, $entity_info['config_prefix']);
+      $id = ConfigStorageController::getIDFromConfigName($id, $entity_info->getConfigPrefix());
       $form_display = entity_load('entity_form_display', $id);
       $new_id = $entity_type . '.' . $bundle_new . '.' . $form_display->mode;
       $form_display->id = $new_id;
@@ -51,7 +51,7 @@ function entity_entity_bundle_delete($entity_type, $bundle) {
   $entity_info = entity_get_info('entity_display');
   $ids = config_get_storage_names_with_prefix('entity.display.' . $entity_type . '.' . $bundle);
   foreach ($ids as &$id) {
-    $id = ConfigStorageController::getIDFromConfigName($id, $entity_info['config_prefix']);
+    $id = ConfigStorageController::getIDFromConfigName($id, $entity_info->getConfigPrefix());
   }
   entity_delete_multiple('entity_display', $ids);
 
@@ -59,7 +59,7 @@ function entity_entity_bundle_delete($entity_type, $bundle) {
   $entity_info = entity_get_info('entity_form_display');
   $ids = config_get_storage_names_with_prefix('entity.form_display.' . $entity_type . '.' . $bundle);
   foreach ($ids as &$id) {
-    $id = ConfigStorageController::getIDFromConfigName($id, $entity_info['config_prefix']);
+    $id = ConfigStorageController::getIDFromConfigName($id, $entity_info->getConfigPrefix());
   }
   entity_delete_multiple('entity_form_display', $ids);
 }
diff --git a/core/modules/entity_reference/entity_reference.install b/core/modules/entity_reference/entity_reference.install
index c40d3ea..954098b 100644
--- a/core/modules/entity_reference/entity_reference.install
+++ b/core/modules/entity_reference/entity_reference.install
@@ -31,12 +31,12 @@ function entity_reference_field_schema($field) {
   );
 
   // Create a foreign key to the target entity type base type.
-  $entity_manager = Drupal::service('plugin.manager.entity');
+  $entity_manager = Drupal::entityManager();
   if (is_subclass_of($entity_manager->getControllerClass($field['settings']['target_type'], 'storage'), 'Drupal\Core\Entity\DatabaseStorageController')) {
     $entity_info = $entity_manager->getDefinition($field['settings']['target_type']);
 
-    $base_table = $entity_info['base_table'];
-    $id_column = $entity_info['entity_keys']['id'];
+    $base_table = $entity_info->getBaseTable();
+    $id_column = $entity_info->getKey('id');
 
     $schema['foreign keys'][$base_table] = array(
       'table' => $base_table,
diff --git a/core/modules/entity_reference/entity_reference.module b/core/modules/entity_reference/entity_reference.module
index 54a7bac..18e456e 100644
--- a/core/modules/entity_reference/entity_reference.module
+++ b/core/modules/entity_reference/entity_reference.module
@@ -139,8 +139,9 @@ function entity_reference_field_settings_form($field, $instance) {
     // @todo As the database schema can currently only store numeric IDs of
     // referenced entities and configuration entities have string IDs, prevent
     // configuration entities from being referenced.
-    if (!is_subclass_of($entity_info['class'], '\Drupal\Core\Config\Entity\ConfigEntityInterface')) {
-      $entity_type_options[$entity_type] = $entity_info['label'];
+
+    if (!is_subclass_of($entity_info->getClass(), '\Drupal\Core\Config\Entity\ConfigEntityInterface')) {
+      $entity_type_options[$entity_type] = $entity_info->getLabel();
     }
   }
 
diff --git a/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/Derivative/SelectionBase.php b/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/Derivative/SelectionBase.php
index a1d7991..5c22acc 100644
--- a/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/Derivative/SelectionBase.php
+++ b/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/Derivative/SelectionBase.php
@@ -27,7 +27,7 @@ public function getDerivativeDefinitions(array $base_plugin_definition) {
     foreach (entity_get_info() as $entity_type => $info) {
       if (!in_array($entity_type, $supported_entities)) {
         $this->derivatives[$entity_type] = $base_plugin_definition;
-        $this->derivatives[$entity_type]['label'] = t('@entity_type selection', array('@entity_type' => $info['label']));
+        $this->derivatives[$entity_type]['label'] = t('@entity_type selection', array('@entity_type' => $info->getLabel()));
       }
     }
     return parent::getDerivativeDefinitions($base_plugin_definition);
diff --git a/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/entity_reference/selection/SelectionBase.php b/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/entity_reference/selection/SelectionBase.php
index de4729c..6aa28e9 100644
--- a/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/entity_reference/selection/SelectionBase.php
+++ b/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/entity_reference/selection/SelectionBase.php
@@ -70,7 +70,7 @@ public static function settingsForm(&$field, &$instance) {
       'auto_create' => FALSE,
     );
 
-    if (!empty($entity_info['entity_keys']['bundle'])) {
+    if ($entity_info->hasKey('bundle')) {
       $bundle_options = array();
       foreach ($bundles as $bundle_name => $bundle_info) {
         $bundle_options[$bundle_name] = $bundle_info['label'];
@@ -105,7 +105,7 @@ public static function settingsForm(&$field, &$instance) {
 
     // @todo Use Entity::getPropertyDefinitions() when all entity types are
     // converted to the new Field API.
-    $fields = drupal_map_assoc(drupal_schema_fields_sql($entity_info['base_table']));
+    $fields = drupal_map_assoc(drupal_schema_fields_sql($entity_info->getBaseTable()));
     foreach (field_info_instances($field['settings']['target_type']) as $bundle_instances) {
       foreach ($bundle_instances as $instance_name => $instance_info) {
         $field_info = field_info_field($instance_name);
@@ -200,7 +200,7 @@ public function validateReferenceableEntities(array $ids) {
       $entity_info = entity_get_info($target_type);
       $query = $this->buildEntityQuery();
       $result = $query
-        ->condition($entity_info['entity_keys']['id'], $ids, 'IN')
+        ->condition($entity_info->getKey('id'), $ids, 'IN')
         ->execute();
     }
 
@@ -261,12 +261,13 @@ public function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS') {
     $entity_info = entity_get_info($target_type);
 
     $query = \Drupal::entityQuery($target_type);
+
     if (!empty($handler_settings['target_bundles'])) {
-      $query->condition($entity_info['entity_keys']['bundle'], $handler_settings['target_bundles'], 'IN');
+      $query->condition($entity_info->getKey('bundle'), $handler_settings['target_bundles'], 'IN');
     }
 
-    if (isset($match) && isset($entity_info['entity_keys']['label'])) {
-      $query->condition($entity_info['entity_keys']['label'], $match, $match_operator);
+    if (isset($match) && $entity_info->hasKey('label')) {
+      $query->condition($entity_info->getKey('label'), $match, $match_operator);
     }
 
     // Add entity-access tag.
diff --git a/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/field/widget/AutocompleteWidgetBase.php b/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/field/widget/AutocompleteWidgetBase.php
index 9798a53..2b8dd38 100644
--- a/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/field/widget/AutocompleteWidgetBase.php
+++ b/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/field/widget/AutocompleteWidgetBase.php
@@ -164,8 +164,8 @@ protected function createNewEntity($label, $uid) {
     }
 
     $entity_info = $entity_manager->getDefinition($target_type);
-    $bundle_key = $entity_info['entity_keys']['bundle'];
-    $label_key = $entity_info['entity_keys']['label'];
+    $bundle_key = $entity_info->getKey('bundle');
+    $label_key = $entity_info->getKey('label');
 
     return $entity_manager->getStorageController($target_type)->create(array(
       $label_key => $label,
diff --git a/core/modules/field/field.attach.inc b/core/modules/field/field.attach.inc
index 5ed17ff..3974bfa 100644
--- a/core/modules/field/field.attach.inc
+++ b/core/modules/field/field.attach.inc
@@ -584,7 +584,7 @@ function field_attach_load($entity_type, $entities, $age = FIELD_LOAD_CURRENT, $
   $info = entity_get_info($entity_type);
   // Only the most current revision of non-deleted fields for cacheable entity
   // types can be cached.
-  $cache_read = $load_current && $info['field_cache'] && !$load_deleted;
+  $cache_read = $load_current && $info->fieldsCacheable() && !$load_deleted;
   // In addition, do not write to the cache when loading a single field.
   $cache_write = $cache_read && !isset($options['instance']);
 
@@ -926,8 +926,7 @@ function field_attach_update(EntityInterface $entity) {
     module_invoke($storage_info['module'], 'field_storage_write', $entity, FIELD_STORAGE_UPDATE, $fields);
   }
 
-  $entity_info = $entity->entityInfo();
-  if ($entity_info['field_cache']) {
+  if ($entity->entityInfo()->fieldsCacheable()) {
     cache('field')->delete('field:' . $entity->entityType() . ':' . $entity->id());
   }
 }
@@ -959,8 +958,7 @@ function field_attach_delete(EntityInterface $entity) {
     module_invoke($storage_info['module'], 'field_storage_delete', $entity, $fields);
   }
 
-  $entity_info = $entity->entityInfo();
-  if ($entity_info['field_cache']) {
+  if ($entity->entityInfo()->fieldsCacheable()) {
     cache('field')->delete('field:' . $entity->entityType() . ':' . $entity->id());
   }
 }
diff --git a/core/modules/field/field.crud.inc b/core/modules/field/field.crud.inc
index ed129df..b3eeb2d 100644
--- a/core/modules/field/field.crud.inc
+++ b/core/modules/field/field.crud.inc
@@ -260,8 +260,8 @@ function field_purge_batch($batch_size) {
       ->condition('id:' . $field['uuid'] . '.deleted', 1)
       ->range(0, $batch_size);
     // If there's no bundle key, all results will have the same bundle.
-    if (!empty($info[$entity_type]['entity_keys']['bundle'])) {
-      $query->condition($info[$entity_type]['entity_keys']['bundle'], $ids->bundle);
+    if ($info[$entity_type]->hasKey('bundle')) {
+      $query->condition($info[$entity_type]->getKey('bundle'), $ids->bundle);
     }
     $results = $query->execute();
 
diff --git a/core/modules/field/field.entity.yml b/core/modules/field/field.entity.yml
new file mode 100644
index 0000000..fb86419
--- /dev/null
+++ b/core/modules/field/field.entity.yml
@@ -0,0 +1,2 @@
+field_entity: Drupal\field\Plugin\Core\Entity\Field
+field_instance: Drupal\field\Plugin\Core\Entity\FieldInstance
diff --git a/core/modules/field/field.module b/core/modules/field/field.module
index 92a2c45..1a68427 100644
--- a/core/modules/field/field.module
+++ b/core/modules/field/field.module
@@ -234,7 +234,7 @@ function field_system_info_alter(&$info, $file, $type) {
  */
 function field_entity_create(EntityInterface $entity) {
   $info = $entity->entityInfo();
-  if (!empty($info['fieldable'])) {
+  if ($info->isFieldable()) {
     field_populate_default_values($entity, $entity->language()->id);
   }
 }
@@ -910,8 +910,8 @@ function field_extract_bundle($entity_type, $bundle) {
   }
 
   $info = entity_get_info($entity_type);
-  if (is_object($bundle) && isset($info['bundle_keys']['bundle']) && isset($bundle->{$info['bundle_keys']['bundle']})) {
-    return $bundle->{$info['bundle_keys']['bundle']};
+  if (is_object($bundle) && $info->getBundleKey('bundle') && isset($bundle->{$info->getBundleKey('bundle')})) {
+    return $bundle->{$info->getBundleKey('bundle')};
   }
 }
 
@@ -1103,14 +1103,14 @@ function theme_field($variables) {
 function _field_create_entity_from_ids($ids) {
   $id_properties = array();
   $info = entity_get_info($ids->entity_type);
-  if (isset($info['entity_keys']['id'])) {
-    $id_properties[$info['entity_keys']['id']] = $ids->entity_id;
+  if ($info->hasKey('id')) {
+    $id_properties[$info->getKey('id')] = $ids->entity_id;
   }
-  if (!empty($info['entity_keys']['revision']) && isset($ids->revision_id)) {
-    $id_properties[$info['entity_keys']['revision']] = $ids->revision_id;
+  if ($info->hasKey('revision') && isset($ids->revision_id)) {
+    $id_properties[$info->getKey('revision')] = $ids->revision_id;
   }
-  if (!empty($info['entity_keys']['bundle']) && isset($ids->bundle)) {
-    $id_properties[$info['entity_keys']['bundle']] = $ids->bundle;
+  if ($info->hasKey('bundle') && isset($ids->bundle)) {
+    $id_properties[$info->getKey('bundle')] = $ids->bundle;
   }
   return entity_create($ids->entity_type, $id_properties);
 }
diff --git a/core/modules/field/field.multilingual.inc b/core/modules/field/field.multilingual.inc
index ca33a31..b2a18bb 100644
--- a/core/modules/field/field.multilingual.inc
+++ b/core/modules/field/field.multilingual.inc
@@ -196,7 +196,7 @@ function field_is_translatable($entity_type, $field) {
  */
 function field_has_translation_handler($entity_type, $handler = NULL) {
   $info = entity_get_info($entity_type);
-  return !empty($info['translatable']);
+  return $info->isTranslatable();
 }
 
 /**
diff --git a/core/modules/field/field.views.inc b/core/modules/field/field.views.inc
index 5a1f4f3..bc9ddb7 100644
--- a/core/modules/field/field.views.inc
+++ b/core/modules/field/field.views.inc
@@ -115,7 +115,11 @@ function field_views_field_default_views_data($field) {
   // Build the relationships between the field table and the entity tables.
   foreach ($field['bundles'] as $entity => $bundles) {
     $entity_info = entity_get_info($entity);
-    $groups[$entity] = $entity_info['label'];
+    if (!$entity_info) {
+      continue;
+    }
+
+    $groups[$entity] = $entity_info->getLabel();
 
     // Override Node to Content.
     if ($groups[$entity] == t('Node')) {
@@ -127,18 +131,21 @@ function field_views_field_default_views_data($field) {
       $group_name = $groups[$entity];
     }
 
-    if (!isset($entity_info['base_table'])) {
+    if (!$entity_info->hasBaseTable()) {
       continue;
     }
-    $entity_tables[$entity_info['base_table']] = $entity;
-    $current_tables[$entity] = $entity_info['base_table'];
-    if (isset($entity_info['revision_table'])) {
-      $entity_tables[$entity_info['revision_table']] = $entity;
-      $revision_tables[$entity] = $entity_info['revision_table'];
+    $base_table = $entity_info->getBaseTable();
+    $entity_tables[$base_table] = $entity;
+    $current_tables[$entity] = $base_table;
+
+    if ($entity_info->hasRevisionTable()) {
+      $entity_revision_table = $entity_info->getRevisionTable();
+      $entity_tables[$entity_revision_table] = $entity;
+      $revision_tables[$entity] = $entity_revision_table;
     }
 
-    $data[$current_table]['table']['join'][$entity_info['base_table']] = array(
-      'left_field' => $entity_info['entity_keys']['id'],
+    $data[$current_table]['table']['join'][$base_table] = array(
+      'left_field' => $entity_info->getKey('id'),
       'field' => 'entity_id',
       'extra' => array(
         array('field' => 'entity_type', 'value' => $entity),
@@ -146,9 +153,9 @@ function field_views_field_default_views_data($field) {
       ),
     );
 
-    if (!empty($entity_info['entity_keys']['revision']) && !empty($entity_info['revision_table'])) {
-      $data[$revision_table]['table']['join'][$entity_info['revision_table']] = array(
-        'left_field' => $entity_info['entity_keys']['revision'],
+    if ($entity_info->hasKey('revision') && !empty($entity_revision_table)) {
+      $data[$revision_table]['table']['join'][$entity_revision_table] = array(
+        'left_field' => $entity_info->getKey('revision'),
         'field' => 'revision_id',
         'extra' => array(
           array('field' => 'entity_type', 'value' => $entity),
diff --git a/core/modules/field/lib/Drupal/field/FieldInstanceStorageController.php b/core/modules/field/lib/Drupal/field/FieldInstanceStorageController.php
index f9ce249..244016b 100644
--- a/core/modules/field/lib/Drupal/field/FieldInstanceStorageController.php
+++ b/core/modules/field/lib/Drupal/field/FieldInstanceStorageController.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Config\Config;
 use Drupal\Core\Config\Entity\ConfigStorageController;
 use Drupal\Core\Entity\Query\QueryFactory;
+use Drupal\Core\Entity\EntityType;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Drupal\Core\Config\ConfigFactory;
 use Drupal\Core\Config\StorageInterface;
@@ -66,7 +67,7 @@ class FieldInstanceStorageController extends ConfigStorageController {
    * @param \Drupal\Core\KeyValueStore\KeyValueStoreInterface $state
    *   The state key value store.
    */
-  public function __construct($entity_type, array $entity_info, ConfigFactory $config_factory, StorageInterface $config_storage, QueryFactory $entity_query_factory, EntityManager $entity_manager, ModuleHandler $module_handler, KeyValueStoreInterface $state) {
+  public function __construct($entity_type, EntityType $entity_info, ConfigFactory $config_factory, StorageInterface $config_storage, QueryFactory $entity_query_factory, EntityManager $entity_manager, ModuleHandler $module_handler, KeyValueStoreInterface $state) {
     parent::__construct($entity_type, $entity_info, $config_factory, $config_storage, $entity_query_factory);
     $this->entityManager = $entity_manager;
     $this->moduleHandler = $module_handler;
@@ -76,7 +77,7 @@ public function __construct($entity_type, array $entity_info, ConfigFactory $con
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, $entity_type, EntityType $entity_info) {
     return new static(
       $entity_type,
       $entity_info,
diff --git a/core/modules/field/lib/Drupal/field/FieldStorageController.php b/core/modules/field/lib/Drupal/field/FieldStorageController.php
index 41efe60..1043e16 100644
--- a/core/modules/field/lib/Drupal/field/FieldStorageController.php
+++ b/core/modules/field/lib/Drupal/field/FieldStorageController.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Config\Config;
 use Drupal\Core\Config\Entity\ConfigStorageController;
 use Drupal\Core\Entity\Query\QueryFactory;
+use Drupal\Core\Entity\EntityType;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Drupal\Core\Config\ConfigFactory;
 use Drupal\Core\Config\StorageInterface;
@@ -61,7 +62,7 @@ class FieldStorageController extends ConfigStorageController {
    * @param \Drupal\Core\KeyValueStore\KeyValueStoreInterface $state
    *   The state key value store.
    */
-  public function __construct($entity_type, array $entity_info, ConfigFactory $config_factory, StorageInterface $config_storage, QueryFactory $entity_query_factory, EntityManager $entity_manager, ModuleHandler $module_handler, KeyValueStoreInterface $state) {
+  public function __construct($entity_type, EntityType $entity_info, ConfigFactory $config_factory, StorageInterface $config_storage, QueryFactory $entity_query_factory, EntityManager $entity_manager, ModuleHandler $module_handler, KeyValueStoreInterface $state) {
     parent::__construct($entity_type, $entity_info, $config_factory, $config_storage, $entity_query_factory);
 
     $this->entityManager = $entity_manager;
@@ -72,7 +73,7 @@ public function __construct($entity_type, array $entity_info, ConfigFactory $con
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, $entity_type, EntityType $entity_info) {
     return new static(
       $entity_type,
       $entity_info,
diff --git a/core/modules/field/lib/Drupal/field/Plugin/Core/Entity/Field.php b/core/modules/field/lib/Drupal/field/Plugin/Core/Entity/Field.php
index 1bb1ae4..4bac2ba 100644
--- a/core/modules/field/lib/Drupal/field/Plugin/Core/Entity/Field.php
+++ b/core/modules/field/lib/Drupal/field/Plugin/Core/Entity/Field.php
@@ -346,7 +346,7 @@ protected function saveNew() {
     // collisions with existing entity properties, but some is better than
     // none.
     foreach ($entity_manager->getDefinitions() as $type => $info) {
-      if (in_array($this->id, $info['entity_keys'])) {
+      if ($info->hasKey($this->id)) {
         throw new FieldException(format_string('Attempt to create field %id which is reserved by entity type %type.', array('%id' => $this->id, '%type' => $type)));
       }
     }
@@ -741,7 +741,7 @@ public function hasData() {
     foreach ($this->getBundles() as $entity_type => $bundle) {
       // Entity Query throws an exception if there is no base table.
       $entity_info = \Drupal::entityManager()->getDefinition($entity_type);
-      if (!isset($entity_info['base_table'])) {
+      if (!$entity_info->hasBaseTable()) {
         continue;
       }
       $query = $factory->get($entity_type);
diff --git a/core/modules/field/lib/Drupal/field/Tests/TranslationTest.php b/core/modules/field/lib/Drupal/field/Tests/TranslationTest.php
index ebf9722..1f2fa86 100644
--- a/core/modules/field/lib/Drupal/field/Tests/TranslationTest.php
+++ b/core/modules/field/lib/Drupal/field/Tests/TranslationTest.php
@@ -148,7 +148,7 @@ function testTranslatableFieldSaveLoad() {
     // Enable field translations for nodes.
     field_test_entity_info_translatable('node', TRUE);
     $entity_info = entity_get_info('node');
-    $this->assertTrue(count($entity_info['translatable']), 'Nodes are translatable.');
+    $this->assertTrue($entity_info->isTranslatable(), 'Nodes are translatable.');
 
     // Prepare the field translations.
     $entity_type = 'entity_test';
diff --git a/core/modules/field/tests/modules/field_test/field_test.entity.inc b/core/modules/field/tests/modules/field_test/field_test.entity.inc
index 4cea03a..46db5af 100644
--- a/core/modules/field/tests/modules/field_test/field_test.entity.inc
+++ b/core/modules/field/tests/modules/field_test/field_test.entity.inc
@@ -24,18 +24,63 @@ function field_test_entity_info() {
  */
 function field_test_entity_info_alter(&$entity_info) {
   foreach (field_test_entity_info_translatable() as $entity_type => $translatable) {
-    $entity_info[$entity_type]['translatable'] = $translatable;
+    if (isset($entity_info[$entity_type])) {
+      $entity_info[$entity_type]->setTranslatable($translatable);
+    }
   }
 }
 
 /**
+ * Implements hook_entity_view_mode_info_alter().
+ */
+function field_test_entity_view_mode_info_alter(&$view_modes) {
+  $entity_info = entity_get_info();
+  foreach ($entity_info as $entity_type => $info) {
+    if ($entity_info[$entity_type]->getModule() == 'field_test') {
+      $view_modes[$entity_type] = array(
+        'full' => array(
+          'label' => t('Full object'),
+          'status' => TRUE,
+        ),
+        'teaser' => array(
+          'label' => t('Teaser'),
+          'status' => TRUE,
+        ),
+      );
+    }
+  }
+}
+
+/**
+ * Implements hook_entity_bundle_info().
+ */
+function field_test_entity_bundle_info() {
+  $bundles = array();
+
+  $entity_info = entity_get_info();
+  foreach ($entity_info as $entity_type => $info) {
+    if ($info->getModule() == 'field_test') {
+      $bundles[$entity_type] = Drupal::state()->get('field_test_bundles') ?: array('test_bundle' => array('label' => 'Test Bundle'));
+    }
+  }
+
+  $bundles['test_entity_bundle']['test_entity_2'] = array('label' => 'Test entity 2');
+
+  $bundles['test_entity_bundle_key']['bundle1'] = array('label' => 'Bundle1');
+  $bundles['test_entity_bundle_key']['bundle2'] = array('label' => 'Bundle2');
+
+  return $bundles;
+}
+
+/**
  * Helper function to enable entity translations.
  */
 function field_test_entity_info_translatable($entity_type = NULL, $translatable = NULL) {
   drupal_static_reset('field_has_translation_handler');
-  $stored_value = &drupal_static(__FUNCTION__, array());
+  $stored_value = \Drupal::state()->get('field_test.translatable_entity_types') ?: array();
   if (isset($entity_type)) {
     $stored_value[$entity_type] = $translatable;
+    \Drupal::state()->set('field_test.translatable_entity_types', $stored_value);
     entity_info_cache_clear();
   }
   return $stored_value;
diff --git a/core/modules/field/tests/modules/field_test/field_test.storage.inc b/core/modules/field/tests/modules/field_test/field_test.storage.inc
index 66c9911..ef442c0 100644
--- a/core/modules/field/tests/modules/field_test/field_test.storage.inc
+++ b/core/modules/field/tests/modules/field_test/field_test.storage.inc
@@ -321,7 +321,7 @@ function field_test_field_storage_query($field_id, $conditions, $count, &$cursor
           // If querying all revisions and the entity type has revisions, we need
           // to key the results by revision_ids.
           $entity_type = entity_get_info($row->type);
-          $id = ($load_current || empty($entity_type['entity_keys']['revision'])) ? $row->entity_id : $row->revision_id;
+          $id = ($load_current || !$entity_type->hasKey('revision')) ? $row->entity_id : $row->revision_id;
 
           if (!isset($return[$row->type][$id])) {
             $return[$row->type][$id] = (object) array('entity_id' => $row->entity_id, 'revision_id' => $row->revision_id, 'bundle' => $row->bundle);
diff --git a/core/modules/field_ui/field_ui.module b/core/modules/field_ui/field_ui.module
index 2040df2..259a2e9 100644
--- a/core/modules/field_ui/field_ui.module
+++ b/core/modules/field_ui/field_ui.module
@@ -66,9 +66,9 @@ function field_ui_menu() {
 
   // Create tabs for all possible bundles.
   foreach (entity_get_info() as $entity_type => $entity_info) {
-    if ($entity_info['fieldable'] && isset($entity_info['route_base_path'])) {
+    // @fixme How to replace this.
+    if ($entity_info->fieldsCacheable() && $path = $entity_info->getRouteBasePath()) {
       // Extract path information from the entity type.
-      $path = $entity_info['route_base_path'];
       $default_path = preg_replace('/{' . DRUPAL_PHP_FUNCTION_PATTERN . '}/', '%', $path);
       // This is the position of the %field_ui_instance placeholder in the
       // items below.
@@ -162,18 +162,18 @@ function field_ui_permission() {
   $permissions = array();
 
   foreach (entity_get_info() as $entity_type => $entity_info) {
-    if ($entity_info['fieldable']) {
+    if ($entity_info->fieldsCacheable()) {
       // Create a permission for each fieldable entity to manage
       // the fields and the display.
       $permissions['administer ' . $entity_type . ' fields'] = array(
-        'title' => t('%entity_label: Administer fields', array('%entity_label' => $entity_info['label'])),
+        'title' => t('%entity_label: Administer fields', array('%entity_label' => $entity_info->getLabel())),
         'restrict access' => TRUE,
       );
       $permissions['administer ' . $entity_type . ' form display'] = array(
-        'title' => t('%entity_label: Administer form display', array('%entity_label' => $entity_info['label']))
+        'title' => t('%entity_label: Administer form display', array('%entity_label' => $entity_info->getLabel()))
       );
       $permissions['administer ' . $entity_type . ' display'] = array(
-        'title' => t('%entity_label: Administer display', array('%entity_label' => $entity_info['label']))
+        'title' => t('%entity_label: Administer display', array('%entity_label' => $entity_info->getLabel())),
       );
     }
   }
@@ -253,8 +253,14 @@ function field_ui_element_info() {
  * Implements hook_entity_info().
  */
 function field_ui_entity_info(&$entity_info) {
-  $entity_info['field_instance']['controllers']['form']['delete'] = 'Drupal\field_ui\Form\FieldDeleteForm';
-  $entity_info['field_entity']['controllers']['list'] = 'Drupal\field_ui\FieldListController';
+  if (isset($entity_info['field_instance'])) {
+    $controllers = $entity_info['field_instance']->getController('form');
+    $controllers['delete'] = 'Drupal\field_ui\Form\FieldDeleteForm';
+    $entity_info['field_instance']->setController('form', $controllers);
+  }
+  if (isset($entity_info['field_entity'])) {
+    $entity_info['field_entity']->setController('list',  'Drupal\field_ui\FieldListController');
+  }
 }
 
 /**
diff --git a/core/modules/field_ui/lib/Drupal/field_ui/FieldListController.php b/core/modules/field_ui/lib/Drupal/field_ui/FieldListController.php
index cd3d640..ccfc722 100644
--- a/core/modules/field_ui/lib/Drupal/field_ui/FieldListController.php
+++ b/core/modules/field_ui/lib/Drupal/field_ui/FieldListController.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Config\Entity\ConfigEntityListController;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityManager;
+use Drupal\Core\Entity\EntityType;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\field\FieldInfo;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -52,7 +53,7 @@ class FieldListController extends ConfigEntityListController {
    *
    * @param string $entity_type
    *   The type of entity to be listed.
-   * @param array $entity_info
+   * @param \Drupal\Core\Entity\EntityType $entity_info
    *   An array of entity info for the entity type.
    * @param \Drupal\Core\Entity\EntityManager $entity_manager
    *   The entity manager.
@@ -61,7 +62,7 @@ class FieldListController extends ConfigEntityListController {
    * @param \Drupal\field\FieldInfo $field_info
    *   The field info service.
    */
-  public function __construct($entity_type, array $entity_info, EntityManager $entity_manager, ModuleHandlerInterface $module_handler, FieldInfo $field_info) {
+  public function __construct($entity_type, EntityType $entity_info, EntityManager $entity_manager, ModuleHandlerInterface $module_handler, FieldInfo $field_info) {
     parent::__construct($entity_type, $entity_info, $entity_manager->getStorageController($entity_type), $module_handler);
 
     $this->fieldTypes = field_info_field_types();
@@ -73,7 +74,7 @@ public function __construct($entity_type, array $entity_info, EntityManager $ent
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, $entity_type, EntityType $entity_info) {
     return new static(
       $entity_type,
       $entity_info,
diff --git a/core/modules/field_ui/lib/Drupal/field_ui/Form/FieldDeleteForm.php b/core/modules/field_ui/lib/Drupal/field_ui/Form/FieldDeleteForm.php
index e53eec9..252eea4 100644
--- a/core/modules/field_ui/lib/Drupal/field_ui/Form/FieldDeleteForm.php
+++ b/core/modules/field_ui/lib/Drupal/field_ui/Form/FieldDeleteForm.php
@@ -11,6 +11,7 @@
 use Drupal\Core\Entity\EntityControllerInterface;
 use Drupal\Core\Entity\EntityManager;
 use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Entity\EntityType;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -41,7 +42,7 @@ public function __construct(ModuleHandlerInterface $module_handler, EntityManage
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, $entity_type, EntityType $entity_info) {
     return new static(
       $container->get('module_handler'),
       $container->get('plugin.manager.entity')
diff --git a/core/modules/field_ui/lib/Drupal/field_ui/OverviewBase.php b/core/modules/field_ui/lib/Drupal/field_ui/OverviewBase.php
index 67b742b..6d76490 100644
--- a/core/modules/field_ui/lib/Drupal/field_ui/OverviewBase.php
+++ b/core/modules/field_ui/lib/Drupal/field_ui/OverviewBase.php
@@ -76,8 +76,9 @@ public static function create(ContainerInterface $container) {
    */
   public function buildForm(array $form, array &$form_state, $entity_type = NULL, $bundle = NULL) {
     $entity_info = $this->entityManager->getDefinition($entity_type);
-    if (!empty($entity_info['bundle_prefix'])) {
-      $bundle = $entity_info['bundle_prefix'] . $bundle;
+    // @fixme How to replace this.
+    if ($prefix = $entity_info->getBundlePrefix()) {
+      $bundle = $prefix . $bundle;
     }
 
     $this->entity_type = $entity_type;
diff --git a/core/modules/field_ui/lib/Drupal/field_ui/Routing/RouteSubscriber.php b/core/modules/field_ui/lib/Drupal/field_ui/Routing/RouteSubscriber.php
index e994202..43b10e7 100644
--- a/core/modules/field_ui/lib/Drupal/field_ui/Routing/RouteSubscriber.php
+++ b/core/modules/field_ui/lib/Drupal/field_ui/Routing/RouteSubscriber.php
@@ -50,9 +50,8 @@ public function routes(RouteBuildEvent $event) {
     $collection = $event->getRouteCollection();
     foreach ($this->manager->getDefinitions() as $entity_type => $entity_info) {
       $defaults = array();
-      if ($entity_info['fieldable'] && isset($entity_info['route_base_path'])) {
-        $path = $entity_info['route_base_path'];
-
+      // @fixme How to replace this.
+      if ($entity_info->fieldsCacheable() && $path = $entity_info->getRouteBasePath()) {
         $route = new Route(
           "$path/fields/{field_instance}",
           array('_form' => '\Drupal\field_ui\Form\FieldInstanceEditForm'),
@@ -76,7 +75,7 @@ public function routes(RouteBuildEvent $event) {
 
         // If the entity type has no bundles, use the entity type.
         $defaults['entity_type'] = $entity_type;
-        if (empty($entity_info['entity_keys']['bundle'])) {
+        if (!$entity_info->hasKey('bundle')) {
           $defaults['bundle'] = $entity_type;
         }
         $route = new Route(
diff --git a/core/modules/field_ui/tests/modules/field_ui_test/field_ui_test.entity.yml b/core/modules/field_ui/tests/modules/field_ui_test/field_ui_test.entity.yml
new file mode 100644
index 0000000..261d2d0
--- /dev/null
+++ b/core/modules/field_ui/tests/modules/field_ui_test/field_ui_test.entity.yml
@@ -0,0 +1 @@
+field_ui_test_no_bundle: Drupal\field_ui_test\Plugin\Core\Entity\FieldUITestNoBundle
diff --git a/core/modules/file/file.entity.yml b/core/modules/file/file.entity.yml
new file mode 100644
index 0000000..c5e1870
--- /dev/null
+++ b/core/modules/file/file.entity.yml
@@ -0,0 +1 @@
+file: Drupal\file\Plugin\Core\Entity\File
diff --git a/core/modules/file/file.module b/core/modules/file/file.module
index 0d66adb..e472cb1 100644
--- a/core/modules/file/file.module
+++ b/core/modules/file/file.module
@@ -1577,7 +1577,7 @@ function file_get_file_references(File $file, $field = NULL, $age = FIELD_LOAD_R
       // The usage table contains usage of every revision. If we are looking
       // for every revision or the entity does not support revisions then
       // every usage is already a match.
-      $match_entity_type = $age == FIELD_LOAD_REVISION || !isset($entity_info['entity_keys']['revision']);
+      $match_entity_type = $age == FIELD_LOAD_REVISION || !$entity_info->getKey('revision');
       $entities = entity_load_multiple($entity_type, $entity_ids);
       foreach ($entities as $entity) {
         $bundle = $entity->bundle();
diff --git a/core/modules/file/file.views.inc b/core/modules/file/file.views.inc
index 5c2743f..d2c7332 100644
--- a/core/modules/file/file.views.inc
+++ b/core/modules/file/file.views.inc
@@ -478,7 +478,7 @@ function file_field_views_data_views_data_alter(&$data, $field) {
     $pseudo_field_name = 'reverse_' . $field['field_name'] . '_' . $entity_type;
 
     list($label, $all_labels) = field_views_field_label($field['field_name']);
-    $entity = $entity_info['label'];
+    $entity = $entity_info->getLabel();
     if ($entity == t('Node')) {
       $entity = t('Content');
     }
@@ -490,8 +490,8 @@ function file_field_views_data_views_data_alter(&$data, $field) {
       'field_name' => $field['field_name'],
       'field table' => _field_sql_storage_tablename($field),
       'field field' => $field['field_name'] . '_target_id',
-      'base' => $entity_info['base_table'],
-      'base field' => $entity_info['entity_keys']['id'],
+      'base' => $entity_info->getBaseTable(),
+      'base field' => $entity_info->getKey('id'),
       'label' => t('!field_name', array('!field_name' => $field['field_name'])),
       'join_extra' => array(
         0 => array(
diff --git a/core/modules/file/lib/Drupal/file/FileStorageController.php b/core/modules/file/lib/Drupal/file/FileStorageController.php
index 10ba8d3..9fb2fdf 100644
--- a/core/modules/file/lib/Drupal/file/FileStorageController.php
+++ b/core/modules/file/lib/Drupal/file/FileStorageController.php
@@ -18,7 +18,7 @@ class FileStorageController extends DatabaseStorageControllerNG implements FileS
    * {@inheritdoc}
    */
   public function spaceUsed($uid = NULL, $status = FILE_STATUS_PERMANENT) {
-    $query = $this->database->select($this->entityInfo['base_table'], 'f')
+    $query = $this->database->select($this->entityInfo->getBaseTable(), 'f')
       ->condition('f.status', $status);
     $query->addExpression('SUM(f.filesize)', 'filesize');
     if (isset($uid)) {
@@ -33,7 +33,7 @@ public function spaceUsed($uid = NULL, $status = FILE_STATUS_PERMANENT) {
   public function retrieveTemporaryFiles() {
     // Use separate placeholders for the status to avoid a bug in some versions
     // of PHP. See http://drupal.org/node/352956.
-    return $this->database->query('SELECT fid FROM {' . $this->entityInfo['base_table'] . '} WHERE status <> :permanent AND timestamp < :timestamp', array(
+    return $this->database->query('SELECT fid FROM {' . $this->entityInfo->getBaseTable() . '} WHERE status <> :permanent AND timestamp < :timestamp', array(
       ':permanent' => FILE_STATUS_PERMANENT,
       ':timestamp' => REQUEST_TIME - DRUPAL_MAXIMUM_TEMP_FILE_AGE
     ));
diff --git a/core/modules/filter/filter.entity.yml b/core/modules/filter/filter.entity.yml
new file mode 100644
index 0000000..a0303c15
--- /dev/null
+++ b/core/modules/filter/filter.entity.yml
@@ -0,0 +1 @@
+filter_format: Drupal\filter\Plugin\Core\Entity\FilterFormat
diff --git a/core/modules/filter/lib/Drupal/filter/FilterFormatFormControllerBase.php b/core/modules/filter/lib/Drupal/filter/FilterFormatFormControllerBase.php
index 98495d5..f9faad4 100644
--- a/core/modules/filter/lib/Drupal/filter/FilterFormatFormControllerBase.php
+++ b/core/modules/filter/lib/Drupal/filter/FilterFormatFormControllerBase.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Config\ConfigFactory;
 use Drupal\Core\Entity\EntityControllerInterface;
 use Drupal\Core\Entity\EntityFormController;
+use Drupal\Core\Entity\EntityType;
 use Drupal\Core\Entity\Query\QueryFactory;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -52,7 +53,7 @@ public function __construct(ModuleHandlerInterface $module_handler, ConfigFactor
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, $entity_type, EntityType $entity_info) {
     return new static(
       $container->get('module_handler'),
       $container->get('config.factory'),
diff --git a/core/modules/filter/lib/Drupal/filter/FilterFormatListController.php b/core/modules/filter/lib/Drupal/filter/FilterFormatListController.php
index c8df5e6..b8adde9 100644
--- a/core/modules/filter/lib/Drupal/filter/FilterFormatListController.php
+++ b/core/modules/filter/lib/Drupal/filter/FilterFormatListController.php
@@ -12,6 +12,7 @@
 use Drupal\Core\Config\Entity\ConfigEntityListController;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
+use Drupal\Core\Entity\EntityType;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Form\FormInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -33,7 +34,7 @@ class FilterFormatListController extends ConfigEntityListController implements F
    *
    * @param string $entity_type
    *   The type of entity to be listed.
-   * @param array $entity_info
+   * @param \Drupal\Core\Entity\EntityType $entity_info
    *   An array of entity info for the entity type.
    * @param \Drupal\Core\Entity\EntityStorageControllerInterface $storage
    *   The entity storage controller class.
@@ -42,7 +43,7 @@ class FilterFormatListController extends ConfigEntityListController implements F
    * @param \Drupal\Core\Config\ConfigFactory $config_factory
    *   The config factory.
    */
-  public function __construct($entity_type, array $entity_info, EntityStorageControllerInterface $storage, ModuleHandlerInterface $module_handler, ConfigFactory $config_factory) {
+  public function __construct($entity_type, EntityType $entity_info, EntityStorageControllerInterface $storage, ModuleHandlerInterface $module_handler, ConfigFactory $config_factory) {
     parent::__construct($entity_type, $entity_info, $storage, $module_handler);
 
     $this->configFactory = $config_factory;
@@ -51,7 +52,7 @@ public function __construct($entity_type, array $entity_info, EntityStorageContr
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, $entity_type, EntityType $entity_info) {
     return new static(
       $entity_type,
       $entity_info,
diff --git a/core/modules/image/image.entity.yml b/core/modules/image/image.entity.yml
new file mode 100644
index 0000000..f5c7f97
--- /dev/null
+++ b/core/modules/image/image.entity.yml
@@ -0,0 +1 @@
+image_style: Drupal\image\Plugin\Core\Entity\ImageStyle
diff --git a/core/modules/image/image.views.inc b/core/modules/image/image.views.inc
index 2e70c08..edb983b 100644
--- a/core/modules/image/image.views.inc
+++ b/core/modules/image/image.views.inc
@@ -38,10 +38,13 @@ function image_field_views_data($field) {
 function image_field_views_data_views_data_alter(&$data, $field) {
   foreach ($field['bundles'] as $entity_type => $bundles) {
     $entity_info = entity_get_info($entity_type);
+    if (!$entity_info) {
+      continue;
+    }
     $pseudo_field_name = 'reverse_' . $field['field_name'] . '_' . $entity_type;
 
     list($label, $all_labels) = field_views_field_label($field['field_name']);
-    $entity = $entity_info['label'];
+    $entity = $entity_info->getLabel();
     if ($entity == t('Node')) {
       $entity = t('Content');
     }
@@ -53,8 +56,8 @@ function image_field_views_data_views_data_alter(&$data, $field) {
       'field_name' => $field['field_name'],
       'field table' => _field_sql_storage_tablename($field),
       'field field' => $field['field_name'] . '_target_id',
-      'base' => $entity_info['base_table'],
-      'base field' => $entity_info['entity_keys']['id'],
+      'base' => $entity_info->getBaseTable(),
+      'base field' => $entity_info->getKey('id'),
       'label' => t('!field_name', array('!field_name' => $field['field_name'])),
       'join_extra' => array(
         0 => array(
diff --git a/core/modules/language/language.admin.inc b/core/modules/language/language.admin.inc
index 041398e..d3f04dc 100644
--- a/core/modules/language/language.admin.inc
+++ b/core/modules/language/language.admin.inc
@@ -759,7 +759,7 @@ function language_content_settings_form(array $form, array $form_state, array $s
   $default = array();
 
   foreach ($supported as $entity_type) {
-    $labels[$entity_type] = isset($entity_info[$entity_type]['label']) ? $entity_info[$entity_type]['label'] : $entity_type;
+    $labels[$entity_type] = $entity_info[$entity_type]->getLabel() ? $entity_info[$entity_type]->getLabel() : $entity_type;
     $default[$entity_type] = FALSE;
 
     // Check whether we have any custom setting.
@@ -799,7 +799,7 @@ function language_content_settings_form(array $form, array $form_state, array $s
       '#type' => 'container',
       '#entity_type' => $entity_type,
       '#theme' => 'language_content_settings_table',
-      '#bundle_label' => isset($info['bundle_label']) ? $info['bundle_label'] : $label,
+      '#bundle_label' => $info->getBundleLable() ?: $label,
       '#states' => array(
         'visible' => array(
           ':input[name="entity_types[' . $entity_type . ']"]' => array('checked' => TRUE),
diff --git a/core/modules/language/language.entity.yml b/core/modules/language/language.entity.yml
new file mode 100644
index 0000000..3e34199
--- /dev/null
+++ b/core/modules/language/language.entity.yml
@@ -0,0 +1 @@
+language_entity: Drupal\language\Plugin\Core\Entity\Language
diff --git a/core/modules/language/language.module b/core/modules/language/language.module
index 9674630..23426a4 100644
--- a/core/modules/language/language.module
+++ b/core/modules/language/language.module
@@ -203,7 +203,7 @@ function language_theme() {
 function language_entity_supported() {
   $supported = array();
   foreach (entity_get_info() as $entity_type => $info) {
-    if (!empty($info['translatable'])) {
+    if ($info->isTranslatable()) {
       $supported[$entity_type] = $entity_type;
     }
   }
diff --git a/core/modules/layout/layout.entity.yml b/core/modules/layout/layout.entity.yml
new file mode 100644
index 0000000..0293dd9
--- /dev/null
+++ b/core/modules/layout/layout.entity.yml
@@ -0,0 +1,2 @@
+display: Drupal\layout\Plugin\Core\Entity\Display
+unbound_display: Drupal\layout\Plugin\Core\Entity\UnboundDisplay
diff --git a/core/modules/menu/lib/Drupal/menu/Form/MenuDeleteForm.php b/core/modules/menu/lib/Drupal/menu/Form/MenuDeleteForm.php
index febf8b9..0671065 100644
--- a/core/modules/menu/lib/Drupal/menu/Form/MenuDeleteForm.php
+++ b/core/modules/menu/lib/Drupal/menu/Form/MenuDeleteForm.php
@@ -12,6 +12,7 @@
 use Drupal\Core\Entity\EntityControllerInterface;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Entity\EntityType;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -52,7 +53,7 @@ public function __construct(ModuleHandlerInterface $module_handler, EntityStorag
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, $entity_type, EntityType $entity_info) {
     return new static(
       $container->get('module_handler'),
       $container->get('plugin.manager.entity')->getStorageController('menu_link'),
diff --git a/core/modules/menu/menu.module b/core/modules/menu/menu.module
index af081e5..ef2dd8d 100644
--- a/core/modules/menu/menu.module
+++ b/core/modules/menu/menu.module
@@ -146,16 +146,21 @@ function menu_menu() {
  * Implements hook_entity_info().
  */
 function menu_entity_info(&$entity_info) {
-  $entity_info['menu']['controllers']['list'] = 'Drupal\menu\MenuListController';
-  $entity_info['menu']['controllers']['access'] = 'Drupal\menu\MenuAccessController';
-  $entity_info['menu']['uri_callback'] = 'menu_uri';
-  $entity_info['menu']['controllers']['form'] = array(
-    'default' => 'Drupal\menu\MenuFormController',
-    'delete' => 'Drupal\menu\Form\MenuDeleteForm',
-  );
-
-  $entity_info['menu_link']['controllers']['form']['delete'] = 'Drupal\menu\Form\MenuLinkDeleteForm';
-  $entity_info['menu_link']['controllers']['form']['reset'] = 'Drupal\menu\Form\MenuLinkResetForm';
+  if (isset($entity_info['menu'])) {
+    $entity_info['menu']->setController('list', 'Drupal\menu\MenuListController');
+    $entity_info['menu']->setController('access', 'Drupal\menu\MenuAccessController');
+    $entity_info['menu']->setUriCallback('menu_uri');
+    $controllers = $entity_info['menu']->getController('form');
+    $controllers['default'] = 'Drupal\menu\MenuFormController';
+    $controllers['delete'] = 'Drupal\menu\Form\MenuDeleteForm';
+    $entity_info['menu']->setController('form', $controllers);
+  }
+  if (isset($entity_info['menu_link'])) {
+    $controllers = $entity_info['menu_link']->getController('form');
+    $controllers['delete'] = 'Drupal\menu\Form\MenuLinkDeleteForm';
+    $controllers['reset'] = 'Drupal\menu\Form\MenuLinkResetForm';
+    $entity_info['menu_link']->setController('form', $controllers);
+  }
 }
 
 /**
diff --git a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkFormController.php b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkFormController.php
index 8eb4458..d47d699 100644
--- a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkFormController.php
+++ b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkFormController.php
@@ -9,6 +9,7 @@
 
 use Drupal\Core\Entity\EntityControllerInterface;
 use Drupal\Core\Entity\EntityFormController;
+use Drupal\Core\Entity\EntityType;
 use Drupal\Core\Language\Language;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Path\AliasManagerInterface;
@@ -45,10 +46,14 @@ class MenuLinkFormController extends EntityFormController implements EntityContr
   /**
    * Constructs a new MenuLinkFormController object.
    *
-   * @param \Drupal\Core\Extension\ModuleHandlerInterface
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
    *   The module handler service.
+   * @param MenuLinkStorageControllerInterface $menu_link_storage_controller
+   *   The menu link storage.
    * @param \Drupal\Core\Path\AliasManagerInterface $path_alias_manager
    *   The path alias manager.
+   * @param \Drupal\Core\Routing\UrlGenerator $url_generator
+   *   The URL generator.
    */
   public function __construct(ModuleHandlerInterface $module_handler, MenuLinkStorageControllerInterface $menu_link_storage_controller, AliasManagerInterface $path_alias_manager, UrlGenerator $url_generator) {
     parent::__construct($module_handler);
@@ -60,7 +65,7 @@ public function __construct(ModuleHandlerInterface $module_handler, MenuLinkStor
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info, $operation = NULL) {
+  public static function createInstance(ContainerInterface $container, $entity_type, EntityType $entity_info, $operation = NULL) {
     return new static(
       $container->get('module_handler'),
       $container->get('plugin.manager.entity')->getStorageController('menu_link'),
diff --git a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageController.php b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageController.php
index 43d54ce..eb22422 100644
--- a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageController.php
+++ b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageController.php
@@ -9,6 +9,7 @@
 
 use Drupal\Core\Entity\DatabaseStorageController;
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityType;
 use Drupal\Core\Entity\EntityStorageException;
 use Drupal\Core\Database\Connection;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -55,7 +56,7 @@ class MenuLinkStorageController extends DatabaseStorageController implements Men
    * @param \Symfony\Cmf\Component\Routing\RouteProviderInterface $route_provider
    *   The route provider service.
    */
-  public function __construct($entity_type, array $entity_info, Connection $database, RouteProviderInterface $route_provider) {
+  public function __construct($entity_type, EntityType $entity_info, Connection $database, RouteProviderInterface $route_provider) {
     parent::__construct($entity_type, $entity_info, $database);
 
     $this->routeProvider = $route_provider;
@@ -80,7 +81,7 @@ public function create(array $values) {
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, $entity_type, EntityType $entity_info) {
     return new static(
       $entity_type,
       $entity_info,
@@ -143,8 +144,6 @@ protected function attachLoad(&$menu_links, $load_revision = FALSE) {
    * Overrides DatabaseStorageController::save().
    */
   public function save(EntityInterface $entity) {
-    $entity_class = $this->entityInfo['class'];
-
     // We return SAVED_UPDATED by default because the logic below might not
     // update the entity if its values haven't changed, so returning FALSE
     // would be confusing in that situation.
@@ -158,7 +157,7 @@ public function save(EntityInterface $entity) {
       }
 
       if ($entity->isNew()) {
-        $entity->mlid = $this->database->insert($this->entityInfo['base_table'])->fields(array('menu_name' => 'tools'))->execute();
+        $entity->mlid = $this->database->insert($this->entityInfo->getBaseTable())->fields(array('menu_name' => 'tools'))->execute();
         $entity->enforceIsNew();
       }
 
@@ -175,7 +174,7 @@ public function save(EntityInterface $entity) {
       // $entity may have additional keys left over from building a router entry.
       // The intersect removes the extra keys, allowing a meaningful comparison.
       if ($entity->isNew() || (array_intersect_key(get_object_vars($entity), get_object_vars($entity->original)) != get_object_vars($entity->original))) {
-        $return = drupal_write_record($this->entityInfo['base_table'], $entity, $this->idKey);
+        $return = drupal_write_record($this->entityInfo->getBaseTable(), $entity, $this->idKey);
 
         if ($return) {
           if (!$entity->isNew()) {
@@ -239,11 +238,11 @@ public function loadUpdatedCustomized(array $router_paths) {
       );
     $query_result = $query->execute();
 
-    if (!empty($this->entityInfo['class'])) {
+    if ($entity_class = $this->entityInfo->getClass()) {
       // We provide the necessary arguments for PDO to create objects of the
       // specified entity class.
       // @see Drupal\Core\Entity\EntityInterface::__construct()
-      $query_result->setFetchMode(\PDO::FETCH_CLASS, $this->entityInfo['class'], array(array(), $this->entityType));
+      $query_result->setFetchMode(\PDO::FETCH_CLASS, $entity_class, array(array(), $this->entityType));
     }
 
     return $query_result->fetchAllAssoc($this->idKey);
@@ -319,7 +318,7 @@ public function findChildrenRelativeDepth(EntityInterface $entity) {
    * {@inheritdoc}
    */
   public function moveChildren(EntityInterface $entity) {
-    $query = $this->database->update($this->entityInfo['base_table']);
+    $query = $this->database->update($this->entityInfo->getBaseTable());
 
     $query->fields(array('menu_name' => $entity->menu_name));
 
diff --git a/core/modules/menu_link/menu_link.entity.yml b/core/modules/menu_link/menu_link.entity.yml
new file mode 100644
index 0000000..c8c37bb
--- /dev/null
+++ b/core/modules/menu_link/menu_link.entity.yml
@@ -0,0 +1 @@
+menu_link: Drupal\menu_link\Plugin\Core\Entity\MenuLink
diff --git a/core/modules/node/lib/Drupal/node/Form/NodeDeleteForm.php b/core/modules/node/lib/Drupal/node/Form/NodeDeleteForm.php
index 5a3fdb0..14f4203 100644
--- a/core/modules/node/lib/Drupal/node/Form/NodeDeleteForm.php
+++ b/core/modules/node/lib/Drupal/node/Form/NodeDeleteForm.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Entity\EntityControllerInterface;
 use Drupal\Core\Entity\EntityNGConfirmFormBase;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
+use Drupal\Core\Entity\EntityType;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Routing\PathBasedGeneratorInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -54,7 +55,7 @@ public function __construct(ModuleHandlerInterface $module_handler, PathBasedGen
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, $entity_type, EntityType $entity_info) {
     return new static(
       $container->get('module_handler'),
       $container->get('url_generator'),
diff --git a/core/modules/node/lib/Drupal/node/Form/NodeTypeDeleteConfirm.php b/core/modules/node/lib/Drupal/node/Form/NodeTypeDeleteConfirm.php
index d159b3d..a44508e 100644
--- a/core/modules/node/lib/Drupal/node/Form/NodeTypeDeleteConfirm.php
+++ b/core/modules/node/lib/Drupal/node/Form/NodeTypeDeleteConfirm.php
@@ -9,6 +9,7 @@
 
 use Drupal\Core\Entity\EntityConfirmFormBase;
 use Drupal\Core\Entity\EntityControllerInterface;
+use Drupal\Core\Entity\EntityType;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Database\Connection;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -42,7 +43,7 @@ public function __construct(ModuleHandlerInterface $module_handler, Connection $
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, $entity_type, EntityType $entity_info) {
     return new static(
       $container->get('module_handler'),
       $container->get('database')
diff --git a/core/modules/node/lib/Drupal/node/NodeAccessController.php b/core/modules/node/lib/Drupal/node/NodeAccessController.php
index c0106a7..c46f565 100644
--- a/core/modules/node/lib/Drupal/node/NodeAccessController.php
+++ b/core/modules/node/lib/Drupal/node/NodeAccessController.php
@@ -7,16 +7,14 @@
 
 namespace Drupal\node;
 
-use Drupal\Core\Database\Connection;
-use Drupal\Core\Database\Query\SelectInterface;
 use Drupal\Core\Entity\EntityControllerInterface;
+use Drupal\Core\Entity\EntityType;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Language\Language;
 use Drupal\Core\Entity\EntityAccessController;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityNG;
 use Drupal\Core\Session\AccountInterface;
-use Drupal\user\Plugin\Core\Entity\User;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -27,7 +25,7 @@ class NodeAccessController extends EntityAccessController implements NodeAccessC
   /**
    * The node grant storage.
    *
-   * @var \Drupal\node\NodeGrantStorageControllerInterface
+   * @var \Drupal\node\NodeGrantDatabaseStorageInterface
    */
   protected $grantStorage;
 
@@ -43,6 +41,8 @@ class NodeAccessController extends EntityAccessController implements NodeAccessC
    *
    * @param \Drupal\node\NodeGrantDatabaseStorageInterface $grant_storage
    *   The node grant storage.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler.
    */
   public function __construct(NodeGrantDatabaseStorageInterface $grant_storage, ModuleHandlerInterface $module_handler) {
      $this->grantStorage = $grant_storage;
@@ -52,7 +52,7 @@ public function __construct(NodeGrantDatabaseStorageInterface $grant_storage, Mo
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, $entity_type, EntityType $entity_info) {
     return new static(
       $container->get('node.grant_storage'),
       $container->get('module_handler')
diff --git a/core/modules/node/lib/Drupal/node/NodeStorageController.php b/core/modules/node/lib/Drupal/node/NodeStorageController.php
index c3ccc3c..aaae6d6 100644
--- a/core/modules/node/lib/Drupal/node/NodeStorageController.php
+++ b/core/modules/node/lib/Drupal/node/NodeStorageController.php
@@ -86,7 +86,7 @@ protected function invokeHook($hook, EntityInterface $node) {
     if ($function == 'field_attach_revision_delete') {
       $function = 'field_attach_delete_revision';
     }
-    if (!empty($this->entityInfo['fieldable']) && function_exists($function)) {
+    if ($this->entityInfo->isFieldable() && function_exists($function)) {
       $function($node);
     }
 
diff --git a/core/modules/node/lib/Drupal/node/NodeTypeListController.php b/core/modules/node/lib/Drupal/node/NodeTypeListController.php
index d6be67d..db59b6b 100644
--- a/core/modules/node/lib/Drupal/node/NodeTypeListController.php
+++ b/core/modules/node/lib/Drupal/node/NodeTypeListController.php
@@ -8,6 +8,7 @@
 
 use Drupal\Core\Config\Entity\ConfigEntityListController;
 use Drupal\Core\Entity\EntityControllerInterface;
+use Drupal\Core\Entity\EntityType;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
@@ -33,7 +34,7 @@ class NodeTypeListController extends ConfigEntityListController implements Entit
    *
    * @param string $entity_type
    *   The type of entity to be listed.
-   * @param array $entity_info
+   * @param \Drupal\Core\Entity\EntityType $entity_info
    *   An array of entity info for the entity type.
    * @param \Drupal\Core\Entity\EntityStorageControllerInterface $storage
    *   The entity storage controller class.
@@ -42,14 +43,14 @@ class NodeTypeListController extends ConfigEntityListController implements Entit
    * @param \Drupal\Core\Routing\PathBasedGeneratorInterface $url_generator
    *   The url generator service.
    */
-  public function __construct($entity_type, array $entity_info, EntityStorageControllerInterface $storage, ModuleHandlerInterface $module_handler, PathBasedGeneratorInterface $url_generator) {
+  public function __construct($entity_type, EntityType $entity_info, EntityStorageControllerInterface $storage, ModuleHandlerInterface $module_handler, PathBasedGeneratorInterface $url_generator) {
     parent::__construct($entity_type, $entity_info, $storage, $module_handler);
     $this->urlGenerator = $url_generator;
   }
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, $entity_type, EntityType $entity_info) {
     return new static(
       $entity_type,
       $entity_info,
diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeTranslationUITest.php b/core/modules/node/lib/Drupal/node/Tests/NodeTranslationUITest.php
index 473947b..b32dd43 100644
--- a/core/modules/node/lib/Drupal/node/Tests/NodeTranslationUITest.php
+++ b/core/modules/node/lib/Drupal/node/Tests/NodeTranslationUITest.php
@@ -76,9 +76,9 @@ protected function getFormSubmitAction(EntityInterface $entity) {
   }
 
   /**
-   * Overrides \Drupal\content_translation\Tests\ContentTranslationUITest::assertPublishedStatus().
+   * Overrides \Drupal\content_translation\Tests\ContentTranslationUITest::doPublishedStatus().
    */
-  protected function assertPublishedStatus() {
+  protected function doPublishedStatus() {
     $entity = entity_load($this->entityType, $this->entityId, TRUE);
     $path = $this->controller->getEditPath($entity);
     $languages = language_list();
@@ -99,7 +99,7 @@ protected function assertPublishedStatus() {
       }
       $entity = entity_load($this->entityType, $this->entityId, TRUE);
       foreach ($this->langcodes as $langcode) {
-        // The node is created as unpulished thus we switch to the published
+        // The node is created as unpublished thus we switch to the published
         // status first.
         $status = !$index;
         $this->assertEqual($status, $entity->translation[$langcode]['status'], 'The translation has been correctly unpublished.');
@@ -108,9 +108,9 @@ protected function assertPublishedStatus() {
   }
 
   /**
-   * Overrides \Drupal\content_translation\Tests\ContentTranslationUITest::assertAuthoringInfo().
+   * Overrides \Drupal\content_translation\Tests\ContentTranslationUITest::doAuthoringInfo().
    */
-  protected function assertAuthoringInfo() {
+  protected function doAuthoringInfo() {
     $entity = entity_load($this->entityType, $this->entityId, TRUE);
     $path = $this->controller->getEditPath($entity);
     $languages = language_list();
@@ -133,8 +133,8 @@ protected function assertAuthoringInfo() {
 
     $entity = entity_load($this->entityType, $this->entityId, TRUE);
     foreach ($this->langcodes as $langcode) {
-      $this->assertEqual($entity->translation[$langcode]['uid'] == $values[$langcode]['uid'], 'Translation author correctly stored.');
-      $this->assertEqual($entity->translation[$langcode]['created'] == $values[$langcode]['created'], 'Translation date correctly stored.');
+      $this->assertEqual($entity->translation[$langcode]['uid'], $values[$langcode]['uid'], 'Translation author correctly stored.');
+      $this->assertEqual($entity->translation[$langcode]['created'], $values[$langcode]['created'], 'Translation date correctly stored.');
     }
   }
 
diff --git a/core/modules/node/node.entity.yml b/core/modules/node/node.entity.yml
new file mode 100644
index 0000000..b1273a8
--- /dev/null
+++ b/core/modules/node/node.entity.yml
@@ -0,0 +1,2 @@
+node: Drupal\node\Plugin\Core\Entity\Node
+node_type: Drupal\node\Plugin\Core\Entity\NodeType
diff --git a/core/modules/node/tests/modules/node_test/node_test.module b/core/modules/node/tests/modules/node_test/node_test.module
index 12e0eed..84dde36 100644
--- a/core/modules/node/tests/modules/node_test/node_test.module
+++ b/core/modules/node/tests/modules/node_test/node_test.module
@@ -185,7 +185,7 @@ function node_test_node_insert(EntityInterface $node) {
  * Implements hook_entity_info_alter().
  */
 function node_test_entity_info_alter(&$entity_info) {
-  if (Drupal::state()->get('node_test.storage_controller')) {
-    $entity_info['node']['class'] = 'Drupal\node_test\NodeTest';
+  if (isset($entity_info['node']) && Drupal::state()->get('node_test.storage_controller')) {
+    $entity_info['node']->setClass('Drupal\node_test\NodeTest');
   }
 }
diff --git a/core/modules/picture/lib/Drupal/picture/PictureMappingListController.php b/core/modules/picture/lib/Drupal/picture/PictureMappingListController.php
index 0b63462..6754c93 100644
--- a/core/modules/picture/lib/Drupal/picture/PictureMappingListController.php
+++ b/core/modules/picture/lib/Drupal/picture/PictureMappingListController.php
@@ -19,7 +19,7 @@ class PictureMappingListController extends ConfigEntityListController {
    * Overrides Drupal\config\EntityListControllerBase::hookMenu().
    */
   public function hookMenu() {
-    $path = $this->entityInfo['list path'];
+    $path = $this->entityInfo->get('list_path');
     $items = parent::hookMenu();
 
     // Override the access callback.
diff --git a/core/modules/picture/picture.entity.yml b/core/modules/picture/picture.entity.yml
new file mode 100644
index 0000000..13c29e0
--- /dev/null
+++ b/core/modules/picture/picture.entity.yml
@@ -0,0 +1 @@
+picture_mapping: Drupal\picture\Plugin\Core\Entity\PictureMapping
diff --git a/core/modules/rdf/rdf.entity.yml b/core/modules/rdf/rdf.entity.yml
new file mode 100644
index 0000000..47ed7b1
--- /dev/null
+++ b/core/modules/rdf/rdf.entity.yml
@@ -0,0 +1 @@
+rdf_mapping: Drupal\rdf\Plugin\Core\Entity\RdfMapping
diff --git a/core/modules/rest/lib/Drupal/rest/LinkManager/TypeLinkManager.php b/core/modules/rest/lib/Drupal/rest/LinkManager/TypeLinkManager.php
index 6802282..66fb4ea 100644
--- a/core/modules/rest/lib/Drupal/rest/LinkManager/TypeLinkManager.php
+++ b/core/modules/rest/lib/Drupal/rest/LinkManager/TypeLinkManager.php
@@ -83,7 +83,7 @@ protected function writeCache() {
     $entity_info = entity_get_info();
     foreach (entity_get_bundles() as $entity_type => $bundles) {
       $entity_type_info = $entity_info[$entity_type];
-      $reflection = new \ReflectionClass($entity_type_info['class']);
+      $reflection = new \ReflectionClass($entity_type_info->getClass());
       // Only content entities are supported currently.
       // @todo Consider supporting config entities.
       if ($reflection->implementsInterface('\Drupal\Core\Config\Entity\ConfigEntityInterface')) {
diff --git a/core/modules/rest/lib/Drupal/rest/Plugin/Derivative/EntityDerivative.php b/core/modules/rest/lib/Drupal/rest/Plugin/Derivative/EntityDerivative.php
index 310d843..72e5034 100644
--- a/core/modules/rest/lib/Drupal/rest/Plugin/Derivative/EntityDerivative.php
+++ b/core/modules/rest/lib/Drupal/rest/Plugin/Derivative/EntityDerivative.php
@@ -43,8 +43,8 @@ public function getDerivativeDefinitions(array $base_plugin_definition) {
         $this->derivatives[$entity_type] = array(
           'id' => 'entity:' . $entity_type,
           'entity_type' => $entity_type,
-          'serialization_class' => $entity_info['class'],
-          'label' => $entity_info['label'],
+          'serialization_class' => $entity_info->getClass(),
+          'label' => $entity_info->getLabel(),
         );
         $this->derivatives[$entity_type] += $base_plugin_definition;
       }
diff --git a/core/modules/rest/lib/Drupal/rest/Plugin/rest/resource/EntityResource.php b/core/modules/rest/lib/Drupal/rest/Plugin/rest/resource/EntityResource.php
index 504fc59..09a6cd7 100644
--- a/core/modules/rest/lib/Drupal/rest/Plugin/rest/resource/EntityResource.php
+++ b/core/modules/rest/lib/Drupal/rest/Plugin/rest/resource/EntityResource.php
@@ -144,7 +144,7 @@ public function patch($id, EntityInterface $entity = NULL) {
     }
     $info = $original_entity->entityInfo();
     // Make sure that the entity ID is the one provided in the URL.
-    $entity->{$info['entity_keys']['id']} = $id;
+    $entity->{$info->getKey('id')} = $id;
 
     // Overwrite the received properties.
     foreach ($entity as $field_name => $field) {
diff --git a/core/modules/shortcut/lib/Drupal/shortcut/Form/ShortcutDeleteForm.php b/core/modules/shortcut/lib/Drupal/shortcut/Form/ShortcutDeleteForm.php
index aec4a4e..7bd4e47 100644
--- a/core/modules/shortcut/lib/Drupal/shortcut/Form/ShortcutDeleteForm.php
+++ b/core/modules/shortcut/lib/Drupal/shortcut/Form/ShortcutDeleteForm.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Entity\EntityConfirmFormBase;
 use Drupal\Core\Entity\EntityControllerInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Entity\EntityType;
 use Drupal\shortcut\ShortcutStorageControllerInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Drupal\Core\Database\Connection;
@@ -46,7 +47,7 @@ public function __construct(ModuleHandlerInterface $module_handler, Connection $
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, $entity_type, EntityType $entity_info) {
     return new static(
       $container->get('module_handler'),
       $container->get('database'),
diff --git a/core/modules/shortcut/shortcut.entity.yml b/core/modules/shortcut/shortcut.entity.yml
new file mode 100644
index 0000000..3649b4e
--- /dev/null
+++ b/core/modules/shortcut/shortcut.entity.yml
@@ -0,0 +1 @@
+shortcut: Drupal\shortcut\Plugin\Core\Entity\Shortcut
diff --git a/core/modules/system/lib/Drupal/system/DateFormatListController.php b/core/modules/system/lib/Drupal/system/DateFormatListController.php
index e08c825..8776540 100644
--- a/core/modules/system/lib/Drupal/system/DateFormatListController.php
+++ b/core/modules/system/lib/Drupal/system/DateFormatListController.php
@@ -12,6 +12,7 @@
 use Drupal\Core\Datetime\Date;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
+use Drupal\Core\Entity\EntityType;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -32,7 +33,7 @@ class DateFormatListController extends ConfigEntityListController {
    *
    * @param string $entity_type
    *   The type of entity to be listed.
-   * @param array $entity_info
+   * @param \Drupal\Core\Entity\EntityType $entity_info
    *   An array of entity info for the entity type.
    * @param \Drupal\Core\Entity\EntityStorageControllerInterface $storage
    *   The entity storage controller class.
@@ -41,7 +42,7 @@ class DateFormatListController extends ConfigEntityListController {
    * @param \Drupal\Core\Datetime\Date $date_service
    *   The date service.
    */
-  public function __construct($entity_type, array $entity_info, EntityStorageControllerInterface $storage, ModuleHandlerInterface $module_handler, Date $date_service) {
+  public function __construct($entity_type, EntityType $entity_info, EntityStorageControllerInterface $storage, ModuleHandlerInterface $module_handler, Date $date_service) {
     parent::__construct($entity_type, $entity_info, $storage, $module_handler);
 
     $this->dateService = $date_service;
@@ -50,7 +51,7 @@ public function __construct($entity_type, array $entity_info, EntityStorageContr
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, $entity_type, EntityType $entity_info) {
     return new static(
       $entity_type,
       $entity_info,
diff --git a/core/modules/system/lib/Drupal/system/Form/DateFormatDeleteForm.php b/core/modules/system/lib/Drupal/system/Form/DateFormatDeleteForm.php
index 3d3dfc7..24c077c 100644
--- a/core/modules/system/lib/Drupal/system/Form/DateFormatDeleteForm.php
+++ b/core/modules/system/lib/Drupal/system/Form/DateFormatDeleteForm.php
@@ -11,6 +11,7 @@
 use Drupal\Core\Datetime\Date;
 use Drupal\Core\Entity\EntityConfirmFormBase;
 use Drupal\Core\Entity\EntityControllerInterface;
+use Drupal\Core\Entity\EntityType;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -43,7 +44,7 @@ public function __construct(ModuleHandlerInterface $module_handler, Date $date_s
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, $entity_type, EntityType $entity_info) {
     return new static(
       $container->get('module_handler'),
       $container->get('date')
diff --git a/core/modules/system/lib/Drupal/system/Form/DateFormatFormBase.php b/core/modules/system/lib/Drupal/system/Form/DateFormatFormBase.php
index 4d347c5..b335ee9 100644
--- a/core/modules/system/lib/Drupal/system/Form/DateFormatFormBase.php
+++ b/core/modules/system/lib/Drupal/system/Form/DateFormatFormBase.php
@@ -11,6 +11,7 @@
 use Drupal\Core\Ajax\ReplaceCommand;
 use Drupal\Core\Datetime\Date;
 use Drupal\Core\Entity\EntityControllerInterface;
+use Drupal\Core\Entity\EntityType;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Drupal\Core\Datetime\DrupalDateTime;
@@ -66,7 +67,7 @@ function __construct(ModuleHandlerInterface $module_handler, QueryFactory $query
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, $entity_type, EntityType $entity_info) {
     return new static(
       $container->get('module_handler'),
       $container->get('entity.query'),
diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityApiInfoTest.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityApiInfoTest.php
index cf7d9cf..b7a65d6 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityApiInfoTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityApiInfoTest.php
@@ -28,6 +28,7 @@ public static function getInfo() {
   function testEntityInfoChanges() {
     module_enable(array('entity_cache_test'));
     $entity_info = entity_get_info();
+    // @fixme How to replace this.
     $this->assertTrue(isset($entity_info['entity_cache_test']), 'Test entity type found.');
 
     // Change the label of the test entity type and make sure changes appear
@@ -42,6 +43,7 @@ function testEntityInfoChanges() {
     // Disable the providing module and make sure the entity type is gone.
     module_disable(array('entity_cache_test', 'entity_cache_test_dependency'));
     $entity_info = entity_get_info();
+    // @fixme How to replace this.
     $this->assertFalse(isset($entity_info['entity_cache_test']), 'Entity type of the providing module is gone.');
   }
 
diff --git a/core/modules/system/system.entity.yml b/core/modules/system/system.entity.yml
new file mode 100644
index 0000000..f64bbf2
--- /dev/null
+++ b/core/modules/system/system.entity.yml
@@ -0,0 +1,3 @@
+action: Drupal\system\Plugin\Core\Entity\Action
+menu: Drupal\system\Plugin\Core\Entity\Menu
+date_format: Drupal\system\Plugin\Core\Entity\DateFormat
diff --git a/core/modules/system/tests/modules/entity_cache_test_dependency/entity_cache_test_dependency.entity.yml b/core/modules/system/tests/modules/entity_cache_test_dependency/entity_cache_test_dependency.entity.yml
new file mode 100644
index 0000000..e088086
--- /dev/null
+++ b/core/modules/system/tests/modules/entity_cache_test_dependency/entity_cache_test_dependency.entity.yml
@@ -0,0 +1 @@
+entity_cache_test: Drupal\entity_cache_test_dependency\Plugin\Core\Entity\EntityCacheTest
diff --git a/core/modules/system/tests/modules/entity_test/entity_test.entity.yml b/core/modules/system/tests/modules/entity_test/entity_test.entity.yml
new file mode 100644
index 0000000..e3ca7f9
--- /dev/null
+++ b/core/modules/system/tests/modules/entity_test/entity_test.entity.yml
@@ -0,0 +1,10 @@
+entity_test: Drupal\entity_test\Plugin\Core\Entity\EntityTest
+entity_test_cache: Drupal\entity_test\Plugin\Core\Entity\EntityTestCache
+entity_test_default_access: Drupal\entity_test\Plugin\Core\Entity\EntityTestDefaultAccess
+entity_test_label: Drupal\entity_test\Plugin\Core\Entity\EntityTestLabel
+entity_test_label_callback: Drupal\entity_test\Plugin\Core\Entity\EntityTestLabelCallback
+entity_test_mul: Drupal\entity_test\Plugin\Core\Entity\EntityTestMul
+entity_test_mulrev: Drupal\entity_test\Plugin\Core\Entity\EntityTestMulRev
+entity_test_no_label: Drupal\entity_test\Plugin\Core\Entity\EntityTestNoLabel
+entity_test_render: Drupal\entity_test\Plugin\Core\Entity\EntityTestRender
+entity_test_rev: Drupal\entity_test\Plugin\Core\Entity\EntityTestRev
diff --git a/core/modules/system/tests/modules/entity_test/entity_test.module b/core/modules/system/tests/modules/entity_test/entity_test.module
index 05498f7..4f341d0 100644
--- a/core/modules/system/tests/modules/entity_test/entity_test.module
+++ b/core/modules/system/tests/modules/entity_test/entity_test.module
@@ -60,8 +60,10 @@ function entity_test_entity_types($filter = NULL) {
 function entity_test_entity_info_alter(&$info) {
   // Optionally specify a translation handler for testing translations.
   if (Drupal::state()->get('entity_test.translation')) {
-    foreach(entity_test_entity_types() as $entity_type) {
-      $info[$entity_type]['translation'][$entity_type] = TRUE;
+    foreach (entity_test_entity_types() as $entity_type) {
+      if (isset($info[$entity_type])) {
+        $info[$entity_type]->translation[$entity_type] = TRUE;
+      }
     }
   }
 }
@@ -130,7 +132,7 @@ function entity_test_entity_bundle_info() {
   $bundles = array();
   $entity_info = entity_get_info();
   foreach ($entity_info as $entity_type => $info) {
-    if ($info['module'] == 'entity_test') {
+    if ($info->getModule() == 'entity_test') {
       $bundles[$entity_type] = Drupal::state()->get($entity_type . '.bundles') ?: array($entity_type => array('label' => 'Entity Test Bundle'));
     }
   }
@@ -143,7 +145,7 @@ function entity_test_entity_bundle_info() {
 function entity_test_entity_view_mode_info_alter(&$view_modes) {
   $entity_info = entity_get_info();
   foreach ($entity_info as $entity_type => $info) {
-    if ($entity_info[$entity_type]['module'] == 'entity_test') {
+    if ($info->getModule() == 'entity_test') {
       $view_modes[$entity_type] = array(
         'full' => array(
           'label' => t('Full object'),
diff --git a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestFormController.php b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestFormController.php
index 1dbb859..04357ab 100644
--- a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestFormController.php
+++ b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestFormController.php
@@ -49,8 +49,7 @@ public function form(array $form, array &$form_state) {
     );
 
     // @todo: Is there a better way to check if an entity type is revisionable?
-    $entity_info = $entity->entityInfo();
-    if (!empty($entity_info['entity_keys']['revision']) && !$entity->isNew()) {
+    if ($entity->entityInfo()->hasKey('revision') && !$entity->isNew()) {
       $form['revision'] = array(
         '#type' => 'checkbox',
         '#title' => t('Create new revision'),
diff --git a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Plugin/Core/Entity/EntityTest.php b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Plugin/Core/Entity/EntityTest.php
index 332b322..bd31ca0 100644
--- a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Plugin/Core/Entity/EntityTest.php
+++ b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Plugin/Core/Entity/EntityTest.php
@@ -93,7 +93,7 @@ protected function init() {
    */
   public function label($langcode = Language::LANGCODE_DEFAULT) {
     $info = $this->entityInfo();
-    if (isset($info['entity_keys']['label']) && $info['entity_keys']['label'] == 'name') {
+    if ($info->getKey('label') == 'name') {
       return $this->getTranslation($langcode)->name->value;
     }
     else {
diff --git a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Plugin/Core/Entity/EntityTestCache.php b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Plugin/Core/Entity/EntityTestCache.php
index c40b11e..73edfc4 100644
--- a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Plugin/Core/Entity/EntityTestCache.php
+++ b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Plugin/Core/Entity/EntityTestCache.php
@@ -93,7 +93,7 @@ protected function init() {
    */
   public function label($langcode = Language::LANGCODE_DEFAULT) {
     $info = $this->entityInfo();
-    if (isset($info['entity_keys']['label']) && $info['entity_keys']['label'] == 'name') {
+    if ($info->getKey('label') == 'name') {
       return $this->getTranslation($langcode)->name->value;
     }
     else {
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Access/TaxonomyTermCreateAccess.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Access/TaxonomyTermCreateAccess.php
index dbf7d06..a562b17 100644
--- a/core/modules/taxonomy/lib/Drupal/taxonomy/Access/TaxonomyTermCreateAccess.php
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Access/TaxonomyTermCreateAccess.php
@@ -8,6 +8,7 @@
 namespace Drupal\taxonomy\Access;
 
 use Drupal\Core\Entity\EntityCreateAccessCheck;
+use Drupal\Core\Entity\EntityType;
 use Symfony\Component\HttpFoundation\Request;
 
 /**
@@ -23,7 +24,7 @@ class TaxonomyTermCreateAccess extends EntityCreateAccessCheck {
   /**
    * {@inheritdoc}
    */
-  protected function prepareEntityValues(array $definition, Request $request, $bundle = NULL) {
+  protected function prepareEntityValues(EntityType $definition, Request $request, $bundle = NULL) {
     $values = array();
     if ($vocabulary = $request->attributes->get('taxonomy_vocabulary')) {
       $values = parent::prepareEntityValues($definition, $request, $vocabulary->id());
diff --git a/core/modules/taxonomy/taxonomy.entity.yml b/core/modules/taxonomy/taxonomy.entity.yml
new file mode 100644
index 0000000..3dfbe22
--- /dev/null
+++ b/core/modules/taxonomy/taxonomy.entity.yml
@@ -0,0 +1,2 @@
+taxonomy_term: Drupal\taxonomy\Plugin\Core\Entity\Term
+taxonomy_vocabulary: Drupal\taxonomy\Plugin\Core\Entity\Vocabulary
diff --git a/core/modules/taxonomy/taxonomy.views.inc b/core/modules/taxonomy/taxonomy.views.inc
index 5646268..387585e 100644
--- a/core/modules/taxonomy/taxonomy.views.inc
+++ b/core/modules/taxonomy/taxonomy.views.inc
@@ -369,7 +369,7 @@ function taxonomy_field_views_data_views_data_alter(&$data, $field) {
     $pseudo_field_name = 'reverse_' . $field['field_name'] . '_' . $entity_type;
 
     list($label, $all_labels) = field_views_field_label($field['field_name']);
-    $entity = $entity_info['label'];
+    $entity = $entity_info->getLabel();
     if ($entity == t('Node')) {
       $entity = t('Content');
     }
@@ -381,8 +381,8 @@ function taxonomy_field_views_data_views_data_alter(&$data, $field) {
       'field_name' => $field['field_name'],
       'field table' => _field_sql_storage_tablename($field),
       'field field' => $field['field_name'] . '_target_id',
-      'base' => $entity_info['base_table'],
-      'base field' => $entity_info['entity_keys']['id'],
+      'base' => $entity_info->getBaseTable(),
+      'base field' => $entity_info->getKey('id'),
       'label' => t('!field_name', array('!field_name' => $field['field_name'])),
       'join_extra' => array(
         0 => array(
diff --git a/core/modules/text/lib/Drupal/text/Tests/Formatter/TextPlainUnitTest.php b/core/modules/text/lib/Drupal/text/Tests/Formatter/TextPlainUnitTest.php
index 0a2b900..aec3e06 100644
--- a/core/modules/text/lib/Drupal/text/Tests/Formatter/TextPlainUnitTest.php
+++ b/core/modules/text/lib/Drupal/text/Tests/Formatter/TextPlainUnitTest.php
@@ -105,7 +105,7 @@ function setUp() {
    */
   protected function createEntity($values = array()) {
     $info = entity_get_info($this->entity_type);
-    $bundle_key = $info['entity_keys']['bundle'];
+    $bundle_key = $info->getKey('bundle');
     $entity = entity_create($this->entity_type, $values + array(
       $bundle_key => $this->bundle,
     ));
diff --git a/core/modules/tour/tour.entity.yml b/core/modules/tour/tour.entity.yml
new file mode 100644
index 0000000..372f76e
--- /dev/null
+++ b/core/modules/tour/tour.entity.yml
@@ -0,0 +1 @@
+tour: Drupal\tour\Plugin\Core\Entity\Tour
diff --git a/core/modules/user/lib/Drupal/user/RoleListController.php b/core/modules/user/lib/Drupal/user/RoleListController.php
index a2250f5..e1a8a73 100644
--- a/core/modules/user/lib/Drupal/user/RoleListController.php
+++ b/core/modules/user/lib/Drupal/user/RoleListController.php
@@ -91,7 +91,7 @@ public function buildForm(array $form, array &$form_state) {
     $form['entities'] = array(
       '#type' => 'table',
       '#header' => $this->buildHeader(),
-      '#empty' => t('There is no @label yet.', array('@label' => $this->entityInfo['label'])),
+      '#empty' => t('There is no @label yet.', array('@label' => $this->entityInfo->getLabel())),
       '#tabledrag' => array(
         array('order', 'sibling', 'weight'),
       ),
diff --git a/core/modules/user/lib/Drupal/user/UserStorageController.php b/core/modules/user/lib/Drupal/user/UserStorageController.php
index 8b4653c..c5eb7bd 100644
--- a/core/modules/user/lib/Drupal/user/UserStorageController.php
+++ b/core/modules/user/lib/Drupal/user/UserStorageController.php
@@ -9,6 +9,7 @@
 
 use Drupal\Core\Entity\EntityBCDecorator;
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityType;
 use Drupal\Core\Password\PasswordInterface;
 use Drupal\Core\Database\Connection;
 use Drupal\user\UserDataInterface;
@@ -61,7 +62,7 @@ public function __construct($entity_type, $entity_info, Connection $database, Pa
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, $entity_type, EntityType $entity_info) {
     return new static(
       $entity_type,
       $entity_info,
@@ -161,7 +162,7 @@ protected function invokeHook($hook, EntityInterface $entity) {
     if ($function == 'field_attach_revision_delete') {
       $function = 'field_attach_delete_revision';
     }
-    if (!empty($this->entityInfo['fieldable']) && function_exists($function)) {
+    if ($this->entityInfo->isFieldable() && function_exists($function)) {
       $function($entity->getBCEntity());
     }
 
diff --git a/core/modules/user/tests/Drupal/user/Tests/Views/Argument/RolesRidTest.php b/core/modules/user/tests/Drupal/user/Tests/Views/Argument/RolesRidTest.php
index c5cd817..c7247a1 100644
--- a/core/modules/user/tests/Drupal/user/Tests/Views/Argument/RolesRidTest.php
+++ b/core/modules/user/tests/Drupal/user/Tests/Views/Argument/RolesRidTest.php
@@ -9,6 +9,7 @@
 
 use Drupal\Component\Utility\String;
 use Drupal\Core\DependencyInjection\ContainerBuilder;
+use Drupal\Core\Entity\EntityType;
 use Drupal\Tests\UnitTestCase;
 use Drupal\user\Plugin\Core\Entity\Role;
 use Drupal\user\Plugin\views\argument\RolesRid;
@@ -74,7 +75,7 @@ public function testTitleQuery() {
     $entity_manager->expects($this->any())
       ->method('getDefinition')
       ->with($this->equalTo('user_role'))
-      ->will($this->returnValue(static::$entityInfo));
+      ->will($this->returnValue(new EntityType(static::$entityInfo)));
 
     $entity_manager
       ->expects($this->once())
diff --git a/core/modules/user/user.entity.yml b/core/modules/user/user.entity.yml
new file mode 100644
index 0000000..fb68dd8
--- /dev/null
+++ b/core/modules/user/user.entity.yml
@@ -0,0 +1,2 @@
+user: Drupal\user\Plugin\Core\Entity\User
+user_role: Drupal\user\Plugin\Core\Entity\Role
diff --git a/core/modules/views/lib/Drupal/views/Plugin/Derivative/ViewsEntityRow.php b/core/modules/views/lib/Drupal/views/Plugin/Derivative/ViewsEntityRow.php
index 5da356b..8e3a991 100644
--- a/core/modules/views/lib/Drupal/views/Plugin/Derivative/ViewsEntityRow.php
+++ b/core/modules/views/lib/Drupal/views/Plugin/Derivative/ViewsEntityRow.php
@@ -46,13 +46,13 @@ public function getDerivativeDefinitions(array $base_plugin_definition) {
     $views_data = Views::viewsData();
     foreach ($entity_types as $entity_type => $entity_info) {
       // Just add support for entity types which have a views integration.
-      if (isset($entity_info['base_table']) && $views_data->get($entity_info['base_table']) && \Drupal::entityManager()->hasController($entity_type, 'render')) {
+      if ($entity_info->hasBaseTable() && $views_data->get($entity_info->getBaseTable()) && \Drupal::entityManager()->hasController($entity_type, 'render')) {
         $this->derivatives[$entity_type] = array(
           'id' => 'entity:' . $entity_type,
           'module' => 'views',
-          'title' => $entity_info['label'],
-          'help' => t('Display the @label', array('@label' => $entity_info['label'])),
-          'base' => array($entity_info['base_table']),
+          'title' => $entity_info->getLabel(),
+          'help' => t('Display the @label', array('@label' => $entity_info->getLabel())),
+          'base' => array($entity_info->getBaseTable()),
           'entity_type' => $entity_type,
           'display_types' => array('normal'),
           'class' => $base_plugin_definition['class'],
diff --git a/core/modules/views/lib/Drupal/views/Plugin/entity_reference/selection/ViewsSelection.php b/core/modules/views/lib/Drupal/views/Plugin/entity_reference/selection/ViewsSelection.php
index e9db07e..e5b0b29 100644
--- a/core/modules/views/lib/Drupal/views/Plugin/entity_reference/selection/ViewsSelection.php
+++ b/core/modules/views/lib/Drupal/views/Plugin/entity_reference/selection/ViewsSelection.php
@@ -67,7 +67,7 @@ public static function settingsForm(&$field, &$instance) {
     $options = array();
     foreach ($displays as $data) {
       list($view, $display_id) = $data;
-      if ($view->storage->get('base_table') == $entity_info['base_table']) {
+      if ($view->storage->get('base_table') == $entity_info->getBaseTable()) {
         $name = $view->storage->get('id');
         $display = $view->storage->get('display');
         $options[$name . ':' . $display_id] = $name . ' - ' . $display[$display_id]['display_title'];
diff --git a/core/modules/views/lib/Drupal/views/Plugin/views/display/DisplayPluginBase.php b/core/modules/views/lib/Drupal/views/Plugin/views/display/DisplayPluginBase.php
index b0cbb7b..8f3c848 100644
--- a/core/modules/views/lib/Drupal/views/Plugin/views/display/DisplayPluginBase.php
+++ b/core/modules/views/lib/Drupal/views/Plugin/views/display/DisplayPluginBase.php
@@ -1572,9 +1572,9 @@ public function buildOptionsForm(&$form, &$form_state) {
         $entity_tables = array();
         $has_translation_handlers = FALSE;
         foreach ($entities as $type => $entity_info) {
-          $entity_tables[] = $entity_info['base_table'];
+          $entity_tables[] = $entity_info->getBaseTable();
 
-          if (!empty($entity_info['translation'])) {
+          if (!empty($entity_info->translation)) {
             $has_translation_handlers = TRUE;
           }
         }
diff --git a/core/modules/views/lib/Drupal/views/Plugin/views/filter/Bundle.php b/core/modules/views/lib/Drupal/views/Plugin/views/filter/Bundle.php
index 3c1f54f..4234ad2 100644
--- a/core/modules/views/lib/Drupal/views/Plugin/views/filter/Bundle.php
+++ b/core/modules/views/lib/Drupal/views/Plugin/views/filter/Bundle.php
@@ -30,7 +30,7 @@ class Bundle extends InOperator {
   /**
    * The entity info for the entity type.
    *
-   * @var array
+   * @var \Drupal\Core\Entity\Annotation\EntityType
    */
   protected $entityInfo;
 
@@ -42,7 +42,7 @@ public function init(ViewExecutable $view, DisplayPluginBase $display, array &$o
 
     $this->entityType = $this->getEntityType();
     $this->entityInfo = entity_get_info($this->entityType);
-    $this->real_field = $this->entityInfo['entity_keys']['bundle'];
+    $this->real_field = $this->entityInfo->getKey('bundle');
   }
 
   /**
@@ -51,7 +51,7 @@ public function init(ViewExecutable $view, DisplayPluginBase $display, array &$o
   public function getValueOptions() {
     if (!isset($this->value_options)) {
       $types = entity_get_bundles($this->entityType);
-      $this->value_title = t('@entity types', array('@entity' => $this->entityInfo['label']));
+      $this->value_title = t('@entity types', array('@entity' => $this->entityInfo->getLabel()));
 
       $options = array();
       foreach ($types as $type => $info) {
diff --git a/core/modules/views/lib/Drupal/views/Plugin/views/query/Sql.php b/core/modules/views/lib/Drupal/views/Plugin/views/query/Sql.php
index 8162b2c..f7862a2 100644
--- a/core/modules/views/lib/Drupal/views/Plugin/views/query/Sql.php
+++ b/core/modules/views/lib/Drupal/views/Plugin/views/query/Sql.php
@@ -1262,7 +1262,7 @@ public function query($get_count = FALSE) {
 
       foreach ($entity_tables as $table_alias => $table) {
         $info = entity_get_info($table['entity_type']);
-        $base_field = empty($table['revision']) ? $info['entity_keys']['id'] : $info['entity_keys']['revision'];
+        $base_field = empty($table['revision']) ? $info->getKey('id') : $info->getKey('revision');
         $this->addField($table_alias, $base_field, '', $params);
       }
     }
@@ -1497,7 +1497,7 @@ public function getEntityTables() {
     // Determine which of the tables are revision tables.
     foreach ($entity_tables as $table_alias => $table) {
       $info = entity_get_info($table['entity_type']);
-      if (isset($info['revision table']) && $info['revision table'] == $table['base']) {
+      if ($info->hasRevisionTable() && $info->getRevisionTable() == $table['base']) {
         $entity_tables[$table_alias]['revision'] = TRUE;
       }
     }
@@ -1530,7 +1530,7 @@ function loadEntities(&$results) {
     foreach ($entity_tables as $table_alias => $table) {
       $entity_type = $table['entity_type'];
       $info = entity_get_info($entity_type);
-      $id_key = empty($table['revision']) ? $info['entity_keys']['id'] : $info['entity_keys']['revision'];
+      $id_key = empty($table['revision']) ? $info->getKey('id') : $info->getKey('revision');
       $id_alias = $this->getFieldAlias($table_alias, $id_key);
 
       foreach ($results as $index => $result) {
diff --git a/core/modules/views/lib/Drupal/views/Plugin/views/row/EntityRow.php b/core/modules/views/lib/Drupal/views/Plugin/views/row/EntityRow.php
index 10ae792..cb52a40 100644
--- a/core/modules/views/lib/Drupal/views/Plugin/views/row/EntityRow.php
+++ b/core/modules/views/lib/Drupal/views/Plugin/views/row/EntityRow.php
@@ -78,8 +78,8 @@ public function init(ViewExecutable $view, DisplayPluginBase $display, array &$o
 
     $this->entityType = $this->definition['entity_type'];
     $this->entityInfo = $this->entityManager->getDefinition($this->entityType);
-    $this->base_table = $this->entityInfo['base_table'];
-    $this->base_field = $this->entityInfo['entity_keys']['id'];
+    $this->base_table = $this->entityInfo->getBaseTable();
+    $this->base_field = $this->entityInfo->getKey('id');
   }
 
   /**
diff --git a/core/modules/views/lib/Drupal/views/Plugin/views/wizard/WizardPluginBase.php b/core/modules/views/lib/Drupal/views/Plugin/views/wizard/WizardPluginBase.php
index a84eb92..86b0181 100644
--- a/core/modules/views/lib/Drupal/views/Plugin/views/wizard/WizardPluginBase.php
+++ b/core/modules/views/lib/Drupal/views/Plugin/views/wizard/WizardPluginBase.php
@@ -120,7 +120,7 @@ public function __construct(array $configuration, $plugin_id, array $plugin_defi
 
     $entities = entity_get_info();
     foreach ($entities as $entity_type => $entity_info) {
-      if (isset($entity_info['base_table']) && $this->base_table == $entity_info['base_table']) {
+      if ($this->base_table == $entity_info->getBaseTable()) {
         $this->entity_info = $entity_info;
         $this->entity_type = $entity_type;
       }
@@ -559,7 +559,7 @@ protected function buildFilters(&$form, &$form_state) {
 
     $bundles = entity_get_bundles($this->entity_type);
     // If the current base table support bundles and has more than one (like user).
-    if (isset($this->entity_info['bundle_keys']) && !empty($bundles)) {
+    if ($this->entity_info && $this->entity_info->getBundleKeys() && !empty($bundles)) {
       // Get all bundles and their human readable names.
       $options = array('all' => t('All'));
       foreach ($bundles as $type => $bundle) {
@@ -833,7 +833,7 @@ protected function defaultDisplayFiltersUser(array $form, array &$form_state) {
     $filters = array();
 
     if (!empty($form_state['values']['show']['type']) && $form_state['values']['show']['type'] != 'all') {
-      $bundle_key = $this->entity_info['bundle_keys']['bundle'];
+      $bundle_key = $this->entity_info->getBundleKey('bundle');
       // Figure out the table where $bundle_key lives. It may not be the same as
       // the base table for the view; the taxonomy vocabulary machine_name, for
       // example, is stored in taxonomy_vocabulary, not taxonomy_term_data.
diff --git a/core/modules/views/lib/Drupal/views/Tests/Handler/AreaEntityTest.php b/core/modules/views/lib/Drupal/views/Tests/Handler/AreaEntityTest.php
index b2669a1..9a1ad82 100644
--- a/core/modules/views/lib/Drupal/views/Tests/Handler/AreaEntityTest.php
+++ b/core/modules/views/lib/Drupal/views/Tests/Handler/AreaEntityTest.php
@@ -53,7 +53,7 @@ public function testEntityAreaData() {
     $entity_info = $this->container->get('plugin.manager.entity')->getDefinitions();
 
     $expected_entities = array_filter($entity_info, function($info) {
-      return !empty($info['controllers']['render']);
+      return $info->hasController('render');
     });
 
     // Test that all expected entity types have data.
@@ -64,7 +64,7 @@ public function testEntityAreaData() {
     }
 
     $expected_entities = array_filter($entity_info, function($info) {
-      return empty($info['controllers']['render']);
+      return !$info->hasController('render');
     });
 
     // Test that no configuration entity types have data.
diff --git a/core/modules/views/lib/Drupal/views/Tests/ViewStorageTest.php b/core/modules/views/lib/Drupal/views/Tests/ViewStorageTest.php
index 1cfd62a..2990457 100644
--- a/core/modules/views/lib/Drupal/views/Tests/ViewStorageTest.php
+++ b/core/modules/views/lib/Drupal/views/Tests/ViewStorageTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\views\Tests;
 
+use Drupal\Core\Entity\EntityType;
 use Drupal\views\ViewStorageController;
 use Drupal\views\Plugin\Core\Entity\View;
 use Drupal\views\Plugin\views\display\Page;
@@ -76,7 +77,7 @@ function testConfigurationEntityCRUD() {
     $this->controller = $this->container->get('plugin.manager.entity')->getStorageController('view');
 
     // Confirm that an info array has been returned.
-    $this->assertTrue(!empty($this->info) && is_array($this->info), 'The View info array is loaded.');
+    $this->assertTrue($this->info instanceof EntityType, 'The View info array is loaded.');
 
     // Confirm we have the correct controller class.
     $this->assertTrue($this->controller instanceof ViewStorageController, 'The correct controller is loaded.');
diff --git a/core/modules/views/views.entity.yml b/core/modules/views/views.entity.yml
new file mode 100644
index 0000000..ca3f94e
--- /dev/null
+++ b/core/modules/views/views.entity.yml
@@ -0,0 +1 @@
+view: Drupal\views\Plugin\Core\Entity\View
diff --git a/core/modules/views/views.views.inc b/core/modules/views/views.views.inc
index 99d23c1..b213c9c 100644
--- a/core/modules/views/views.views.inc
+++ b/core/modules/views/views.views.inc
@@ -109,8 +109,8 @@ function views_views_data() {
   // Registers an entity area handler per entity type.
   foreach (entity_get_info() as $entity_type => $entity_info) {
     // Exclude entity types, which cannot be rendered.
-    if (!empty($entity_info['controllers']['render'])) {
-      $label = $entity_info['label'];
+    if ($entity_info->hasController('render')) {
+      $label = $entity_info->getLabel();
       $data['views']['entity_' . $entity_type] = array(
         'title' => t('Rendered entity - @label', array('@label' => $label)),
         'help' => t('Displays a rendered @label entity in an area.', array('@label' => $label)),
diff --git a/core/modules/views_ui/lib/Drupal/views_ui/Form/BreakLockForm.php b/core/modules/views_ui/lib/Drupal/views_ui/Form/BreakLockForm.php
index a161caf..2fcedb0 100644
--- a/core/modules/views_ui/lib/Drupal/views_ui/Form/BreakLockForm.php
+++ b/core/modules/views_ui/lib/Drupal/views_ui/Form/BreakLockForm.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Entity\EntityConfirmFormBase;
 use Drupal\Core\Entity\EntityControllerInterface;
 use Drupal\Core\Entity\EntityManager;
+use Drupal\Core\Entity\EntityType;
 use Drupal\user\TempStoreFactory;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpFoundation\Request;
@@ -49,7 +50,7 @@ public function __construct(EntityManager $entity_manager, TempStoreFactory $tem
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, $entity_type, EntityType $entity_info) {
     return new static(
       $container->get('plugin.manager.entity'),
       $container->get('user.tempstore')
diff --git a/core/modules/views_ui/lib/Drupal/views_ui/ViewAddFormController.php b/core/modules/views_ui/lib/Drupal/views_ui/ViewAddFormController.php
index 4ad9376..a277b5f 100644
--- a/core/modules/views_ui/lib/Drupal/views_ui/ViewAddFormController.php
+++ b/core/modules/views_ui/lib/Drupal/views_ui/ViewAddFormController.php
@@ -8,6 +8,7 @@
 namespace Drupal\views_ui;
 
 use Drupal\Core\Entity\EntityControllerInterface;
+use Drupal\Core\Entity\EntityType;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\views\Plugin\views\wizard\WizardPluginBase;
 use Drupal\views\Plugin\views\wizard\WizardException;
@@ -43,7 +44,7 @@ public function __construct(ModuleHandlerInterface $module_handler, ViewsPluginM
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, $entity_type, EntityType $entity_info) {
     return new static(
       $container->get('module_handler'),
       $container->get('plugin.manager.views.wizard')
diff --git a/core/modules/views_ui/lib/Drupal/views_ui/ViewEditFormController.php b/core/modules/views_ui/lib/Drupal/views_ui/ViewEditFormController.php
index 015a855..523f329 100644
--- a/core/modules/views_ui/lib/Drupal/views_ui/ViewEditFormController.php
+++ b/core/modules/views_ui/lib/Drupal/views_ui/ViewEditFormController.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Ajax\AjaxResponse;
 use Drupal\Core\Ajax\HtmlCommand;
 use Drupal\Core\Ajax\ReplaceCommand;
+use Drupal\Core\Entity\EntityType;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Component\Utility\NestedArray;
 use Drupal\views\ViewExecutable;
@@ -57,7 +58,7 @@ public function __construct(ModuleHandlerInterface $module_handler, TempStoreFac
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info, $operation = NULL) {
+  public static function createInstance(ContainerInterface $container, $entity_type, EntityType $entity_info, $operation = NULL) {
     return new static(
       $container->get('module_handler'),
       $container->get('user.tempstore'),
diff --git a/core/modules/views_ui/lib/Drupal/views_ui/ViewListController.php b/core/modules/views_ui/lib/Drupal/views_ui/ViewListController.php
index 909aad0..cf2f55d 100644
--- a/core/modules/views_ui/lib/Drupal/views_ui/ViewListController.php
+++ b/core/modules/views_ui/lib/Drupal/views_ui/ViewListController.php
@@ -13,6 +13,7 @@
 use Drupal\Core\Config\Entity\ConfigEntityListController;
 use Drupal\Core\Entity\EntityControllerInterface;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
+use Drupal\Core\Entity\EntityType;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -31,7 +32,7 @@ class ViewListController extends ConfigEntityListController implements EntityCon
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, $entity_type, EntityType $entity_info) {
     return new static(
       $entity_type,
       $container->get('plugin.manager.entity')->getStorageController($entity_type),
@@ -48,14 +49,14 @@ public static function createInstance(ContainerInterface $container, $entity_typ
    *   The type of entity to be listed.
    * @param \Drupal\Core\Entity\EntityStorageControllerInterface $storage.
    *   The entity storage controller class.
-   * @param array $entity_info
+   * @param \Drupal\Core\Entity\EntityType $entity_info
    *   An array of entity info for this entity type.
    * @param \Drupal\Component\Plugin\PluginManagerInterface $display_manager
    *   The views display plugin manager to use.
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
    *   The module handler.
    */
-  public function __construct($entity_type, EntityStorageControllerInterface $storage, $entity_info, PluginManagerInterface $display_manager, ModuleHandlerInterface $module_handler) {
+  public function __construct($entity_type, EntityStorageControllerInterface $storage, EntityType $entity_info, PluginManagerInterface $display_manager, ModuleHandlerInterface $module_handler) {
     parent::__construct($entity_type, $entity_info, $storage, $module_handler);
 
     $this->displayManager = $display_manager;
diff --git a/core/modules/views_ui/lib/Drupal/views_ui/ViewPreviewFormController.php b/core/modules/views_ui/lib/Drupal/views_ui/ViewPreviewFormController.php
index 4ac5b8c..fc76297 100644
--- a/core/modules/views_ui/lib/Drupal/views_ui/ViewPreviewFormController.php
+++ b/core/modules/views_ui/lib/Drupal/views_ui/ViewPreviewFormController.php
@@ -8,6 +8,7 @@
 namespace Drupal\views_ui;
 
 use Drupal\Core\Entity\EntityControllerInterface;
+use Drupal\Core\Entity\EntityType;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\user\TempStoreFactory;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -41,7 +42,7 @@ public function __construct(ModuleHandlerInterface $module_handler, TempStoreFac
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, $entity_type, EntityType $entity_info) {
     return new static(
       $container->get('module_handler'),
       $container->get('user.tempstore')
diff --git a/core/modules/views_ui/tests/Drupal/views_ui/Tests/ViewListControllerTest.php b/core/modules/views_ui/tests/Drupal/views_ui/Tests/ViewListControllerTest.php
index 9dd1295..40f2211 100644
--- a/core/modules/views_ui/tests/Drupal/views_ui/Tests/ViewListControllerTest.php
+++ b/core/modules/views_ui/tests/Drupal/views_ui/Tests/ViewListControllerTest.php
@@ -9,6 +9,7 @@
 
 
 use Drupal\Core\DependencyInjection\ContainerBuilder;
+use Drupal\Core\Entity\EntityType;
 use Drupal\Tests\UnitTestCase;
 use Drupal\views\Plugin\Core\Entity\View;
 use Drupal\views\ViewExecutableFactory;
@@ -32,7 +33,7 @@ public function testBuildRowEntityList() {
     $storage_controller = $this->getMockBuilder('Drupal\views\ViewStorageController')
       ->disableOriginalConstructor()
       ->getMock();
-    $entity_info = array();
+    $entity_info = new EntityType(array());
     $display_manager = $this->getMockBuilder('\Drupal\views\Plugin\ViewsPluginManager')
       ->disableOriginalConstructor()
       ->getMock();
diff --git a/core/modules/views_ui/views_ui.module b/core/modules/views_ui/views_ui.module
index b208d92..8dbfb7f 100644
--- a/core/modules/views_ui/views_ui.module
+++ b/core/modules/views_ui/views_ui.module
@@ -5,6 +5,7 @@
  * Provide structure for the administrative interface to Views.
  */
 
+use Drupal\Core\Entity\EntityType;
 use Drupal\views\ViewExecutable;
 use Drupal\views\ViewStorageInterface;
 use Drupal\views_ui\ViewUI;
@@ -95,17 +96,17 @@ function views_ui_menu() {
  * Implements hook_entity_info().
  */
 function views_ui_entity_info(&$entity_info) {
-  $entity_info['view']['controllers'] += array(
-    'list' => 'Drupal\views_ui\ViewListController',
-    'form' => array(
-      'edit' => 'Drupal\views_ui\ViewEditFormController',
-      'add' => 'Drupal\views_ui\ViewAddFormController',
-      'preview' => 'Drupal\views_ui\ViewPreviewFormController',
-      'clone' => 'Drupal\views_ui\ViewCloneFormController',
-      'delete' => 'Drupal\views_ui\ViewDeleteFormController',
-      'break_lock' => 'Drupal\views_ui\Form\BreakLockForm',
-    ),
-  );
+  if (isset($entity_info['view'])) {
+    $controllers = $entity_info['view']->getController('form');
+    $controllers['edit'] = 'Drupal\views_ui\ViewEditFormController';
+    $controllers['add'] = 'Drupal\views_ui\ViewAddFormController';
+    $controllers['preview'] = 'Drupal\views_ui\ViewPreviewFormController';
+    $controllers['clone'] = 'Drupal\views_ui\ViewCloneFormController';
+    $controllers['delete'] = 'Drupal\views_ui\ViewDeleteFormController';
+    $controllers['break_lock'] = 'Drupal\views_ui\Form\BreakLockForm';
+    $entity_info['view']->setController('form', $controllers);
+    $entity_info['view']->setController('list', 'Drupal\views_ui\ViewListController');
+  }
 }
 
 /**
diff --git a/core/tests/Drupal/Tests/Core/AnnotationReaderTest.php b/core/tests/Drupal/Tests/Core/AnnotationReaderTest.php
new file mode 100644
index 0000000..16d8eb8
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/AnnotationReaderTest.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace Drupal\Tests\Core;
+
+use Drupal\Core\AnnotationReader;
+use Drupal\Tests\UnitTestCase;
+use Drupal\Core\Entity\Annotation\EntityType;
+use Drupal\Tests\Core\TestAnnotationClass;
+
+class AnnotationReaderTest extends UnitTestCase {
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Annotation reader tesst',
+      'description' => '.',
+      'group' => 'core'
+    );
+  }
+
+  public function setUp() {
+    $this->reader = new AnnotationReader;
+    $this->reader->setLoader(test_classloader());
+  }
+
+  public function testGetClassAnnotation() {
+    $res = $this->reader->getClassAnnotation('Drupal\Tests\Core\TestAnnotationClass', 'Drupal\Core\Entity\Annotation\EntityType');
+    $expected = new EntityType(array('id' => "test", 'label' => "Test"));
+    $this->assertEquals($expected, $res);
+  }
+
+  public function testGetClassAnnotationCache() {
+    $reader_mock = $this->getMock('Doctrine\Common\Annotations\AnnotationReader');
+    $return_annotation = new EntityType(array('id' => "test", 'label' => "Test"));
+    $reader_mock->expects($this->once())
+                ->method('getClassAnnotations')
+                ->will($this->returnValue($return_annotation));
+    $this->reader->setReader($reader_mock);
+
+    // Expectation is to only call $reader_mock once, so this would fail if the
+    // cache wasn't working correctly.
+    $this->reader->getClassAnnotations('Drupal\Tests\Core\TestAnnotationClass');
+    $this->reader->getClassAnnotations('Drupal\Tests\Core\TestAnnotationClass');
+  }
+}
diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityCreateAccessCheckTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityCreateAccessCheckTest.php
index 0d4689e..2236238 100644
--- a/core/tests/Drupal/Tests/Core/Entity/EntityCreateAccessCheckTest.php
+++ b/core/tests/Drupal/Tests/Core/Entity/EntityCreateAccessCheckTest.php
@@ -9,6 +9,7 @@
 
 use Drupal\Core\Access\AccessCheckInterface;
 use Drupal\Core\Entity\EntityCreateAccessCheck;
+use Drupal\Core\Entity\EntityType;
 use Drupal\Tests\UnitTestCase;
 use Symfony\Component\HttpFoundation\Request;
 
@@ -103,7 +104,7 @@ public function testAccess($entity_bundle, $requirement, $access, $expected) {
     $entity_manager->expects($this->any())
       ->method('getDefinition')
       ->with($this->equalTo('entity_test'))
-      ->will($this->returnValue(array('entity_keys' => array('bundle' => 'type'))));
+      ->will($this->returnValue(new EntityType(array('entity_keys' => array('bundle' => 'type')))));
 
     $storage_controller = $this->getMock('Drupal\Core\Entity\EntityStorageControllerInterface');
     $values = $entity_bundle ? array('type' => $entity_bundle) : array();
diff --git a/core/tests/Drupal/Tests/Core/TestAnnotationClass.php b/core/tests/Drupal/Tests/Core/TestAnnotationClass.php
new file mode 100644
index 0000000..f7e4994
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/TestAnnotationClass.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace Drupal\Tests\Core;
+
+use Drupal\Core\Entity\Annotation\EntityType;
+
+/**
+ * Defines the comment entity class.
+ *
+ * @EntityType(
+ *   id = "test",
+ *   label = "Test"
+ * )
+ */
+class TestAnnotationClass {
+}
+
diff --git a/core/tests/bootstrap.php b/core/tests/bootstrap.php
index 08d8871..95ba988 100644
--- a/core/tests/bootstrap.php
+++ b/core/tests/bootstrap.php
@@ -1,22 +1,26 @@
 <?php
 
-// Register the namespaces we'll need to autoload from.
-$loader = require __DIR__ . "/../vendor/autoload.php";
-$loader->add('Drupal\\', __DIR__);
-$loader->add('Drupal\Core', __DIR__ . "/../../core/lib");
-$loader->add('Drupal\Component', __DIR__ . "/../../core/lib");
+function test_classloader() {
+  // Register the namespaces we'll need to autoload from.
+  $loader = require __DIR__ . "/../vendor/autoload.php";
+  $loader->add('Drupal\\', __DIR__);
+  $loader->add('Drupal\Core', __DIR__ . "/../../core/lib");
+  $loader->add('Drupal\Component', __DIR__ . "/../../core/lib");
 
-foreach (scandir(__DIR__ . "/../modules") as $module) {
-  $loader->add('Drupal\\' . $module, __DIR__ . "/../modules/" . $module . "/lib");
-  // Add test module classes.
-  $test_modules_dir = __DIR__ . "/../modules/$module/tests/modules";
-  if (is_dir($test_modules_dir)) {
-    foreach (scandir($test_modules_dir) as $test_module) {
-      $loader->add('Drupal\\' . $test_module, $test_modules_dir . '/' . $test_module . '/lib');
+  foreach (scandir(__DIR__ . "/../modules") as $module) {
+    $loader->add('Drupal\\' . $module, __DIR__ . "/../modules/" . $module . "/lib");
+    // Add test module classes.
+    $test_modules_dir = __DIR__ . "/../modules/$module/tests/modules";
+    if (is_dir($test_modules_dir)) {
+      foreach (scandir($test_modules_dir) as $test_module) {
+        $loader->add('Drupal\\' . $test_module, $test_modules_dir . '/' . $test_module . '/lib');
+      }
     }
   }
+  return $loader;
 }
 
+test_classloader();
 require __DIR__ . "/../../core/lib/Drupal.php";
 // Look into removing this later.
 define('REQUEST_TIME', (int) $_SERVER['REQUEST_TIME']);
