diff --git a/core/includes/schema.inc b/core/includes/schema.inc
index 18fab75..f698189 100644
--- a/core/includes/schema.inc
+++ b/core/includes/schema.inc
@@ -403,9 +403,18 @@ function drupal_write_record($table, &$record, $primary_keys = array()) {
       continue;
     }
 
+    // Skip fields that are not provided, default values are already known
+    // by the database. property_exists() allows to explicitly set a value to
+    // NULL.
     if (!property_exists($object, $field)) {
-      // Skip fields that are not provided, default values are already known
-      // by the database.
+      $default_fields[] = $field;
+      continue;
+    }
+    // However, if $object is an entity class instance, then class properties
+    // always exist, as they cannot be unset. Therefore, if $field is a serial
+    // type and the value is NULL, skip it.
+    // @see http://php.net/manual/en/function.property-exists.php
+    if ($info['type'] == 'serial' && !isset($object->$field)) {
       $default_fields[] = $field;
       continue;
     }
diff --git a/core/modules/comment/comment.entity.inc b/core/modules/comment/comment.entity.inc
index 0c0ea0d..bce8b56 100644
--- a/core/modules/comment/comment.entity.inc
+++ b/core/modules/comment/comment.entity.inc
@@ -196,7 +196,7 @@ class CommentStorageController extends EntityDatabaseStorageController {
   /**
    * Overrides EntityDatabaseStorageController::postSave().
    */
-  protected function postSave(EntityInterface $comment) {
+  protected function postSave(EntityInterface $comment, $update) {
     // Update the {node_comment_statistics} table prior to executing the hook.
     $this->updateNodeStatistics($comment->nid);
     if ($comment->status == COMMENT_PUBLISHED) {
diff --git a/core/modules/entity/entity.class.inc b/core/modules/entity/entity.class.inc
index 1dec8ee..a2007aa 100644
--- a/core/modules/entity/entity.class.inc
+++ b/core/modules/entity/entity.class.inc
@@ -33,12 +33,31 @@ interface EntityInterface {
   /**
    * Returns whether the entity is new.
    *
+   * Usually an entity is new if no ID exists for it yet. However, entities may
+   * be enforced to be new with existing IDs too.
+   *
    * @return
    *   TRUE if the entity is new, or FALSE if the entity has already been saved.
+   *
+   * @see EntityInterface::enforceIsNew()
    */
   public function isNew();
 
   /**
+   * Enforces an entity to be new.
+   *
+   * Allows migrations to create entities with pre-defined IDs by forcing the
+   * entity to be new before saving.
+   *
+   * @param bool $value
+   *   (optional) Whether the entity should be forced to be new. Defaults to
+   *   TRUE.
+   *
+   * @see EntityInterface::isNew()
+   */
+  public function enforceIsNew($value = TRUE);
+
+  /**
    * Returns the type of the entity.
    *
    * @return
@@ -148,6 +167,13 @@ class Entity implements EntityInterface {
   protected $bundleKey;
 
   /**
+   * Boolean indicating whether the entity should be forced to be new.
+   *
+   * @var bool
+   */
+  protected $enforceIsNew;
+
+  /**
    * Constructs a new entity object.
    */
   public function __construct(array $values = array(), $entity_type) {
@@ -179,9 +205,14 @@ class Entity implements EntityInterface {
    * Implements EntityInterface::isNew().
    */
   public function isNew() {
-    // We support creating entities with pre-defined IDs to ease migrations.
-    // For that the "is_new" property may be set to TRUE.
-    return !empty($this->is_new) || empty($this->{$this->idKey});
+    return !empty($this->enforceIsNew) || empty($this->{$this->idKey});
+  }
+
+  /**
+   * Implements EntityInterface::enforceIsNew().
+   */
+  public function enforceIsNew($value = TRUE) {
+    $this->enforceIsNew = $value;
   }
 
   /**
diff --git a/core/modules/entity/entity.controller.inc b/core/modules/entity/entity.controller.inc
index 04c1f31..60a1e5f 100644
--- a/core/modules/entity/entity.controller.inc
+++ b/core/modules/entity/entity.controller.inc
@@ -404,6 +404,18 @@ class DrupalDefaultEntityController implements DrupalEntityControllerInterface {
 interface EntityStorageControllerInterface extends DrupalEntityControllerInterface {
 
   /**
+   * Constructs a new entity object, without permanently saving it.
+   *
+   * @param $values
+   *   An array of values to set, keyed by property name. If the entity type has
+   *   bundles the bundle key has to be specified.
+   *
+   * @return EntityInterface
+   *   A new entity object.
+   */
+  public function create(array $values);
+
+  /**
    * Deletes permanently saved entities.
    *
    * @param $ids
@@ -442,6 +454,14 @@ class EntityStorageException extends Exception { }
 class EntityDatabaseStorageController extends DrupalDefaultEntityController implements EntityStorageControllerInterface {
 
   /**
+   * Implements EntityStorageControllerInterface::create().
+   */
+  public function create(array $values) {
+    $class = isset($this->entityInfo['entity class']) ? $this->entityInfo['entity class'] : 'Entity';
+    return new $class($values, $this->entityType);
+  }
+
+  /**
    * Implements EntityStorageControllerInterface::delete().
    */
   public function delete($ids) {
@@ -496,18 +516,21 @@ class EntityDatabaseStorageController extends DrupalDefaultEntityController impl
       if (!$entity->isNew()) {
         $return = drupal_write_record($this->entityInfo['base table'], $entity, $this->idKey);
         $this->resetCache(array($entity->{$this->idKey}));
-        $this->postSave($entity);
+        $this->postSave($entity, TRUE);
         $this->invokeHook('update', $entity);
       }
       else {
         $return = drupal_write_record($this->entityInfo['base table'], $entity);
-        $this->postSave($entity);
+        // Reset general caches, but keep caches specific to certain entities.
+        $this->resetCache(array());
+
+        $entity->enforceIsNew(FALSE);
+        $this->postSave($entity, FALSE);
         $this->invokeHook('insert', $entity);
       }
 
       // Ignore slave server temporarily.
       db_ignore_slave();
-      unset($entity->is_new);
       unset($entity->original);
 
       return $return;
@@ -531,8 +554,12 @@ class EntityDatabaseStorageController extends DrupalDefaultEntityController impl
    *
    * Used after the entity is saved, but before invoking the insert or update
    * hook.
+   *
+   * @param $update
+   *   (bool) TRUE if the entity has been updated, or FALSE if it has been
+   *   inserted.
    */
-  protected function postSave(EntityInterface $entity) { }
+  protected function postSave(EntityInterface $entity, $update) { }
 
   /**
    * Acts on entities before they are deleted.
diff --git a/core/modules/entity/entity.module b/core/modules/entity/entity.module
index fe9a6e5..9b2b6f2 100644
--- a/core/modules/entity/entity.module
+++ b/core/modules/entity/entity.module
@@ -270,7 +270,7 @@ function entity_delete_multiple($entity_type, $ids) {
 }
 
 /**
- * Constructs a new entity object, without saving it to the database.
+ * Constructs a new entity object, without permanently saving it.
  *
  * @param $entity_type
  *   The type of the entity.
@@ -282,13 +282,13 @@ function entity_delete_multiple($entity_type, $ids) {
  *   A new entity object.
  */
 function entity_create($entity_type, array $values) {
-  $info = entity_get_info($entity_type) + array('entity class' => 'Entity');
-  $class = $info['entity class'];
-  return new $class($values, $entity_type);
+  return entity_get_controller($entity_type)->create($values);
 }
 
 /**
  * Gets the entity controller class for an entity type.
+ *
+ * @return EntityStorageControllerInterface
  */
 function entity_get_controller($entity_type) {
   $controllers = &drupal_static(__FUNCTION__, array());
diff --git a/core/modules/entity/tests/entity_crud_hook_test.test b/core/modules/entity/tests/entity_crud_hook_test.test
index e7739c9..be59e99 100644
--- a/core/modules/entity/tests/entity_crud_hook_test.test
+++ b/core/modules/entity/tests/entity_crud_hook_test.test
@@ -245,22 +245,22 @@ class EntityCrudHookTestCase extends DrupalWebTestCase {
    * Tests hook invocations for CRUD operations on taxonomy terms.
    */
   public function testTaxonomyTermHooks() {
-    $vocabulary = (object) array(
+    $vocabulary = entity_create('taxonomy_vocabulary', array(
       'name' => 'Test vocabulary',
       'machine_name' => 'test',
       'langcode' => LANGUAGE_NOT_SPECIFIED,
       'description' => NULL,
       'module' => 'entity_crud_hook_test',
-    );
+    ));
     taxonomy_vocabulary_save($vocabulary);
 
-    $term = (object) array(
+    $term = entity_create('taxonomy_term', array(
       'vid' => $vocabulary->vid,
       'name' => 'Test term',
       'langcode' => LANGUAGE_NOT_SPECIFIED,
       'description' => NULL,
       'format' => 1,
-    );
+    ));
     $_SESSION['entity_crud_hook_test'] = array();
     taxonomy_term_save($term);
 
@@ -305,13 +305,13 @@ class EntityCrudHookTestCase extends DrupalWebTestCase {
    * Tests hook invocations for CRUD operations on taxonomy vocabularies.
    */
   public function testTaxonomyVocabularyHooks() {
-    $vocabulary = (object) array(
+    $vocabulary = entity_create('taxonomy_vocabulary', array(
       'name' => 'Test vocabulary',
       'machine_name' => 'test',
       'langcode' => LANGUAGE_NOT_SPECIFIED,
       'description' => NULL,
       'module' => 'entity_crud_hook_test',
-    );
+    ));
     $_SESSION['entity_crud_hook_test'] = array();
     taxonomy_vocabulary_save($vocabulary);
 
diff --git a/core/modules/field_ui/field_ui.test b/core/modules/field_ui/field_ui.test
index 8c202e5..9b71064 100644
--- a/core/modules/field_ui/field_ui.test
+++ b/core/modules/field_ui/field_ui.test
@@ -158,11 +158,11 @@ class FieldUIManageFieldsTestCase extends FieldUITestCase {
     $this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article'));
 
     // Create a vocabulary named "Tags".
-    $vocabulary = (object) array(
+    $vocabulary = entity_create('taxonomy_vocabulary', array(
       'name' => 'Tags',
       'machine_name' => 'tags',
       'langcode' => LANGUAGE_NOT_SPECIFIED,
-    );
+    ));
     taxonomy_vocabulary_save($vocabulary);
 
     $field = array(
diff --git a/core/modules/forum/forum.admin.inc b/core/modules/forum/forum.admin.inc
index 55a4238..5fd396f 100644
--- a/core/modules/forum/forum.admin.inc
+++ b/core/modules/forum/forum.admin.inc
@@ -79,7 +79,9 @@ function forum_form_submit($form, &$form_state) {
     $type = t('forum');
   }
 
-  $term = (object) $form_state['values'];
+  // @todo Set explicit entity properties instead of arbitrary form values.
+  form_state_values_clean($form_state);
+  $term = entity_create('taxonomy_term', $form_state['values']);
   $status = taxonomy_term_save($term);
   switch ($status) {
     case SAVED_NEW:
diff --git a/core/modules/forum/forum.install b/core/modules/forum/forum.install
index 946f9c4..c09c325 100644
--- a/core/modules/forum/forum.install
+++ b/core/modules/forum/forum.install
@@ -30,7 +30,7 @@ function forum_enable() {
   // Create the forum vocabulary if it does not exist.
   $vocabulary = taxonomy_vocabulary_load(variable_get('forum_nav_vocabulary', 0));
   if (!$vocabulary) {
-    $edit = array(
+    $vocabulary = entity_create('taxonomy_vocabulary', array(
       'name' => t('Forums'),
       'machine_name' => 'forums',
       'langcode' => language_default()->langcode,
@@ -38,8 +38,7 @@ function forum_enable() {
       'hierarchy' => 1,
       'module' => 'forum',
       'weight' => -10,
-    );
-    $vocabulary = (object) $edit;
+    ));
     taxonomy_vocabulary_save($vocabulary);
     variable_set('forum_nav_vocabulary', $vocabulary->vid);
   }
@@ -61,14 +60,13 @@ function forum_enable() {
     field_create_field($field);
 
     // Create a default forum so forum posts can be created.
-    $edit = array(
+    $term = entity_create('taxonomy_term', array(
       'name' => t('General discussion'),
       'langcode' => language_default()->langcode,
       'description' => '',
       'parent' => array(0),
       'vid' => $vocabulary->vid,
-    );
-    $term = (object) $edit;
+    ));
     taxonomy_term_save($term);
 
     // Create the instance on the bundle.
diff --git a/core/modules/forum/forum.module b/core/modules/forum/forum.module
index 4c12c41..be77a49 100644
--- a/core/modules/forum/forum.module
+++ b/core/modules/forum/forum.module
@@ -468,7 +468,7 @@ function forum_permission() {
 /**
  * Implements hook_taxonomy_term_delete().
  */
-function forum_taxonomy_term_delete(stdClass $term) {
+function forum_taxonomy_term_delete(TaxonomyTerm $term) {
   // For containers, remove the tid from the forum_containers variable.
   $containers = variable_get('forum_containers', array());
   $key = array_search($term->tid, $containers);
diff --git a/core/modules/path/path.module b/core/modules/path/path.module
index f89927a..1af344f 100644
--- a/core/modules/path/path.module
+++ b/core/modules/path/path.module
@@ -262,7 +262,7 @@ function path_form_taxonomy_form_term_alter(&$form, $form_state) {
 /**
  * Implements hook_taxonomy_term_insert().
  */
-function path_taxonomy_term_insert($term) {
+function path_taxonomy_term_insert(TaxonomyTerm $term) {
   if (isset($term->path)) {
     $path = $term->path;
     $path['alias'] = trim($path['alias']);
@@ -279,7 +279,7 @@ function path_taxonomy_term_insert($term) {
 /**
  * Implements hook_taxonomy_term_update().
  */
-function path_taxonomy_term_update($term) {
+function path_taxonomy_term_update(TaxonomyTerm $term) {
   if (isset($term->path)) {
     $path = $term->path;
     $path['alias'] = trim($path['alias']);
@@ -300,7 +300,7 @@ function path_taxonomy_term_update($term) {
 /**
  * Implements hook_taxonomy_term_delete().
  */
-function path_taxonomy_term_delete($term) {
+function path_taxonomy_term_delete(TaxonomyTerm $term) {
   // Delete all aliases associated with this term.
   path_delete(array('source' => 'taxonomy/term/' . $term->tid));
 }
diff --git a/core/modules/path/path.test b/core/modules/path/path.test
index 32220ee..a3c1025 100644
--- a/core/modules/path/path.test
+++ b/core/modules/path/path.test
@@ -215,11 +215,11 @@ class PathTaxonomyTermTestCase extends PathTestCase {
     parent::setUp(array('taxonomy'));
 
     // Create a Tags vocabulary for the Article node type.
-    $vocabulary = (object) array(
+    $vocabulary = entity_create('taxonomy_vocabulary', array(
       'name' => t('Tags'),
       'machine_name' => 'tags',
-    );
-    taxonomy_vocabulary_save($vocabulary);
+    ));
+    $vocabulary->save();
 
     // Create and login user.
     $web_user = $this->drupalCreateUser(array('administer url aliases', 'administer taxonomy', 'access administration pages'));
diff --git a/core/modules/simpletest/tests/taxonomy_test.module b/core/modules/simpletest/tests/taxonomy_test.module
index aae13a2..72fb29d 100644
--- a/core/modules/simpletest/tests/taxonomy_test.module
+++ b/core/modules/simpletest/tests/taxonomy_test.module
@@ -8,7 +8,7 @@
 /**
  * Implements hook_taxonomy_term_load().
  */
-function taxonomy_test_taxonomy_term_load($terms) {
+function taxonomy_test_taxonomy_term_load(array $terms) {
   foreach ($terms as $term) {
     $antonym = taxonomy_test_get_antonym($term->tid);
     if ($antonym) {
@@ -20,7 +20,7 @@ function taxonomy_test_taxonomy_term_load($terms) {
 /**
  * Implements hook_taxonomy_term_insert().
  */
-function taxonomy_test_taxonomy_term_insert($term) {
+function taxonomy_test_taxonomy_term_insert(TaxonomyTerm $term) {
   if (!empty($term->antonym)) {
     db_insert('taxonomy_term_antonym')
       ->fields(array(
@@ -34,7 +34,7 @@ function taxonomy_test_taxonomy_term_insert($term) {
 /**
  * Implements hook_taxonomy_term_update().
  */
-function taxonomy_test_taxonomy_term_update($term) {
+function taxonomy_test_taxonomy_term_update(TaxonomyTerm $term) {
   if (!empty($term->antonym)) {
     db_merge('taxonomy_term_antonym')
       ->key(array('tid' => $term->tid))
@@ -48,7 +48,7 @@ function taxonomy_test_taxonomy_term_update($term) {
 /**
  * Implements hook_taxonomy_term_delete().
  */
-function taxonomy_test_taxonomy_term_delete($term) {
+function taxonomy_test_taxonomy_term_delete(TaxonomyTerm $term) {
   db_delete('taxonomy_term_antonym')
     ->condition('tid', $term->tid)
     ->execute();
diff --git a/core/modules/simpletest/tests/upgrade/upgrade.test b/core/modules/simpletest/tests/upgrade/upgrade.test
index aafb642..e4045be 100644
--- a/core/modules/simpletest/tests/upgrade/upgrade.test
+++ b/core/modules/simpletest/tests/upgrade/upgrade.test
@@ -298,6 +298,10 @@ abstract class UpgradePathTestCase extends DrupalWebTestCase {
       drupal_load('module', $module);
     }
 
+    // Re-register autoload functions.
+    spl_autoload_register('drupal_autoload_class');
+    spl_autoload_register('drupal_autoload_interface');
+
     // Reload hook implementations
     module_implements_reset();
 
diff --git a/core/modules/taxonomy/taxonomy.admin.inc b/core/modules/taxonomy/taxonomy.admin.inc
index beb87f9..a95b856 100644
--- a/core/modules/taxonomy/taxonomy.admin.inc
+++ b/core/modules/taxonomy/taxonomy.admin.inc
@@ -101,31 +101,27 @@ function theme_taxonomy_overview_vocabularies($variables) {
 /**
  * Form builder for the vocabulary editing form.
  *
+ * @param TaxonomyVocabulary|null $vocabulary
+ *   (optional) The taxonomy vocabulary entity to edit. If NULL or omitted, the
+ *   form creates a new vocabulary.
+ *
  * @ingroup forms
  * @see taxonomy_form_vocabulary_submit()
  * @see taxonomy_form_vocabulary_validate()
  */
-function taxonomy_form_vocabulary($form, &$form_state, $edit = array()) {
+function taxonomy_form_vocabulary($form, &$form_state, TaxonomyVocabulary $vocabulary = NULL) {
   // During initial form build, add the entity to the form state for use
   // during form building and processing. During a rebuild, use what is in the
   // form state.
   if (!isset($form_state['vocabulary'])) {
-    $vocabulary = is_object($edit) ? $edit : (object) $edit;
-    $defaults = array(
-      'name' => '',
-      'machine_name' => '',
-      'description' => '',
-      'hierarchy' => TAXONOMY_HIERARCHY_DISABLED,
-      'weight' => 0,
-      // Default the new vocabulary to the site's default language. This is
-      // the most likely default value until we have better flexible settings.
-      // @todo See http://drupal.org/node/258785 and followups.
-      'langcode' => language_default()->langcode,
-    );
-    foreach ($defaults as $key => $value) {
-      if (!isset($vocabulary->$key)) {
-        $vocabulary->$key = $value;
-      }
+    // Create a new TaxonomyVocabulary entity for the add form.
+    if (!isset($vocabulary)) {
+      $vocabulary = entity_create('taxonomy_vocabulary', array(
+        // Default the new vocabulary to the site's default language. This is
+        // the most likely default value until we have better flexible settings.
+        // @todo See http://drupal.org/node/258785 and followups.
+        'langcode' => language_default()->langcode,
+      ));
     }
     $form_state['vocabulary'] = $vocabulary;
   }
@@ -156,10 +152,6 @@ function taxonomy_form_vocabulary($form, &$form_state, $edit = array()) {
       'exists' => 'taxonomy_vocabulary_machine_name_load',
     ),
   );
-  $form['old_machine_name'] = array(
-    '#type' => 'value',
-    '#value' => $vocabulary->machine_name,
-  );
   $form['description'] = array(
     '#type' => 'textfield',
     '#title' => t('Description'),
@@ -222,6 +214,9 @@ function taxonomy_form_vocabulary_submit($form, &$form_state) {
   $vocabulary = $form_state['vocabulary'];
   entity_form_submit_build_entity('taxonomy_vocabulary', $vocabulary, $form, $form_state);
 
+  // Prevent leading and trailing spaces in vocabulary names.
+  $vocabulary->name = trim($vocabulary->name);
+
   switch (taxonomy_vocabulary_save($vocabulary)) {
     case SAVED_NEW:
       drupal_set_message(t('Created new vocabulary %name.', array('%name' => $vocabulary->name)));
@@ -246,11 +241,14 @@ function taxonomy_form_vocabulary_submit($form, &$form_state) {
  * Display a tree of all the terms in a vocabulary, with options to edit
  * each one. The form is made drag and drop by the theme function.
  *
+ * @param TaxonomyVocabulary $vocabulary
+ *   The taxonomy vocabulary entity to list terms for.
+ *
  * @ingroup forms
  * @see taxonomy_overview_terms_submit()
  * @see theme_taxonomy_overview_terms()
  */
-function taxonomy_overview_terms($form, &$form_state, $vocabulary) {
+function taxonomy_overview_terms($form, &$form_state, TaxonomyVocabulary $vocabulary) {
   global $pager_page_array, $pager_total, $pager_total_items;
 
   // Check for confirmation forms.
@@ -644,35 +642,36 @@ function theme_taxonomy_overview_terms($variables) {
 /**
  * Form function for the term edit form.
  *
+ * @param TaxonomyTerm|null $term
+ *   (optional) The taxonomy term entity to edit. If NULL or omitted, the form
+ *   creates a new term.
+ * @param TaxonomyVocabulary|null $vocabulary
+ *   (optional) A taxonomy vocabulary entity to create the term in. Required if
+ *   the term is omitted.
+ *
  * @ingroup forms
+ * @see taxonomy_form_term_validate()
  * @see taxonomy_form_term_submit()
  */
-function taxonomy_form_term($form, &$form_state, $edit = array(), $vocabulary = NULL) {
+function taxonomy_form_term($form, &$form_state, TaxonomyTerm $term = NULL, TaxonomyVocabulary $vocabulary = NULL) {
   // During initial form build, add the term entity to the form state for use
   // during form building and processing. During a rebuild, use what is in the
   // form state.
   if (!isset($form_state['term'])) {
-    $term = is_object($edit) ? $edit : (object) $edit;
+    // Create a new TaxonomyTerm entity for the add form.
+    if (!isset($term)) {
+      $term = entity_create('taxonomy_term', array(
+        'vid' => $vocabulary->vid,
+        'vocabulary_machine_name' => $vocabulary->machine_name,
+        // Default the new vocabulary to the site's default language. This is
+        // the most likely default value until we have better flexible settings.
+        // @todo See http://drupal.org/node/258785 and followups.
+        'langcode' => language_default()->langcode,
+      ));
+    }
     if (!isset($vocabulary) && isset($term->vid)) {
       $vocabulary = taxonomy_vocabulary_load($term->vid);
     }
-    $defaults = array(
-      'name' => '',
-      'description' => '',
-      'format' => NULL,
-      'vocabulary_machine_name' => isset($vocabulary) ? $vocabulary->machine_name : NULL,
-      'tid' => NULL,
-      'weight' => 0,
-      // Default the new term to the site's default language. This is the most
-      // likely default value until we have better flexible settings.
-      // @todo See http://drupal.org/node/258785 and followups.
-      'langcode' => language_default()->langcode,
-    );
-    foreach ($defaults as $key => $value) {
-      if (!isset($term->$key)) {
-        $term->$key = $value;
-      }
-    }
     $form_state['term'] = $term;
   }
   else {
@@ -858,6 +857,9 @@ function taxonomy_form_term_submit_build_taxonomy_term($form, &$form_state) {
   $term = $form_state['term'];
   entity_form_submit_build_entity('taxonomy_term', $term, $form, $form_state);
 
+  // Prevent leading and trailing spaces in term names.
+  $term->name = trim($term->name);
+
   // Convert text_format field into values expected by taxonomy_term_save().
   $description = $form_state['values']['description'];
   $term->description = $description['value'];
diff --git a/core/modules/taxonomy/taxonomy.api.php b/core/modules/taxonomy/taxonomy.api.php
index 426306d..84fdfc7 100644
--- a/core/modules/taxonomy/taxonomy.api.php
+++ b/core/modules/taxonomy/taxonomy.api.php
@@ -16,10 +16,10 @@
  * Modules implementing this hook can act on the vocabulary objects before they
  * are returned by taxonomy_vocabulary_load_multiple().
  *
- * @param $vocabulary
- *   An array of taxonomy vocabulary objects.
+ * @param array $vocabularies
+ *   An array of taxonomy vocabulary entities.
  */
-function hook_taxonomy_vocabulary_load($vocabularies) {
+function hook_taxonomy_vocabulary_load(array $vocabularies) {
   foreach ($vocabularies as $vocabulary) {
     $vocabulary->synonyms = variable_get('taxonomy_' . $vocabulary->vid . '_synonyms', FALSE);
   }
@@ -32,10 +32,10 @@ function hook_taxonomy_vocabulary_load($vocabularies) {
  * Modules implementing this hook can act on the vocabulary object before it is
  * inserted or updated.
  *
- * @param $vocabulary
- *   A taxonomy vocabulary object.
+ * @param TaxonomyVocabulary $vocabulary
+ *   A taxonomy vocabulary entity.
  */
-function hook_taxonomy_vocabulary_presave($vocabulary) {
+function hook_taxonomy_vocabulary_presave(TaxonomyVocabulary $vocabulary) {
   $vocabulary->foo = 'bar';
 }
 
@@ -45,10 +45,10 @@ function hook_taxonomy_vocabulary_presave($vocabulary) {
  * Modules implementing this hook can act on the vocabulary object when saved
  * to the database.
  *
- * @param $vocabulary
- *   A taxonomy vocabulary object.
+ * @param TaxonomyVocabulary $vocabulary
+ *   A taxonomy vocabulary entity.
  */
-function hook_taxonomy_vocabulary_insert($vocabulary) {
+function hook_taxonomy_vocabulary_insert(TaxonomyVocabulary $vocabulary) {
   if ($vocabulary->synonyms) {
     variable_set('taxonomy_' . $vocabulary->vid . '_synonyms', TRUE);
   }
@@ -59,10 +59,10 @@ function hook_taxonomy_vocabulary_insert($vocabulary) {
  *
  * Modules implementing this hook can act on the vocabulary object when updated.
  *
- * @param $vocabulary
- *   A taxonomy vocabulary object.
+ * @param TaxonomyVocabulary $vocabulary
+ *   A taxonomy vocabulary entity.
  */
-function hook_taxonomy_vocabulary_update($vocabulary) {
+function hook_taxonomy_vocabulary_update(TaxonomyVocabulary $vocabulary) {
   $status = $vocabulary->synonyms ? TRUE : FALSE;
   if ($vocabulary->synonyms) {
     variable_set('taxonomy_' . $vocabulary->vid . '_synonyms', $status);
@@ -76,14 +76,13 @@ function hook_taxonomy_vocabulary_update($vocabulary) {
  * field_attach_delete_bundle() is called and before the vocabulary is actually
  * removed from the database.
  *
- * @param $vocabulary
- *   The taxonomy vocabulary object for the vocabulary that is about to be
- *   deleted.
+ * @param TaxonomyVocabulary $vocabulary
+ *   The taxonomy vocabulary entity that is about to be deleted.
  *
  * @see hook_taxonomy_vocabulary_delete()
  * @see taxonomy_vocabulary_delete()
  */
-function hook_taxonomy_vocabulary_predelete($vocabulary) {
+function hook_taxonomy_vocabulary_predelete(TaxonomyVocabulary $vocabulary) {
   if (variable_get('taxonomy_' . $vocabulary->vid . '_synonyms', FALSE)) {
     variable_del('taxonomy_' . $vocabulary->vid . '_synonyms');
   }
@@ -96,13 +95,13 @@ function hook_taxonomy_vocabulary_predelete($vocabulary) {
  * field_attach_delete_bundle() has been called and after the vocabulary has
  * been removed from the database.
  *
- * @param $vocabulary
- *   The taxonomy vocabulary object for the vocabulary that has been deleted.
+ * @param TaxonomyVocabulary $vocabulary
+ *   The taxonomy vocabulary entity that has been deleted.
  *
  * @see hook_taxonomy_vocabulary_predelete()
  * @see taxonomy_vocabulary_delete()
  */
-function hook_taxonomy_vocabulary_delete($vocabulary) {
+function hook_taxonomy_vocabulary_delete(TaxonomyVocabulary $vocabulary) {
   if (variable_get('taxonomy_' . $vocabulary->vid . '_synonyms', FALSE)) {
     variable_del('taxonomy_' . $vocabulary->vid . '_synonyms');
   }
@@ -121,10 +120,10 @@ function hook_taxonomy_vocabulary_delete($vocabulary) {
  * altering properties provided by the {taxonomy_term_data} table, since this
  * may affect the way results are loaded from cache in subsequent calls.
  *
- * @param $terms
- *   An array of term objects, indexed by tid.
+ * @param array $terms
+ *   An array of taxonomy term entities, indexed by tid.
  */
-function hook_taxonomy_term_load($terms) {
+function hook_taxonomy_term_load(array $terms) {
   $result = db_query('SELECT tid, foo FROM {mytable} WHERE tid IN (:tids)', array(':tids' => array_keys($terms)));
   foreach ($result as $record) {
     $terms[$record->tid]->foo = $record->foo;
@@ -137,10 +136,10 @@ function hook_taxonomy_term_load($terms) {
  * Modules implementing this hook can act on the term object before it is
  * inserted or updated.
  *
- * @param $term
- *   A term object.
+ * @param TaxonomyTerm $term
+ *   A taxonomy term entity.
  */
-function hook_taxonomy_term_presave($term) {
+function hook_taxonomy_term_presave(TaxonomyTerm $term) {
   $term->foo = 'bar';
 }
 
@@ -150,10 +149,10 @@ function hook_taxonomy_term_presave($term) {
  * Modules implementing this hook can act on the term object when saved to
  * the database.
  *
- * @param $term
- *   A taxonomy term object.
+ * @param TaxonomyTerm $term
+ *   A taxonomy term entity.
  */
-function hook_taxonomy_term_insert($term) {
+function hook_taxonomy_term_insert(TaxonomyTerm $term) {
   if (!empty($term->synonyms)) {
     foreach (explode ("\n", str_replace("\r", '', $term->synonyms)) as $synonym) {
       if ($synonym) {
@@ -173,10 +172,10 @@ function hook_taxonomy_term_insert($term) {
  *
  * Modules implementing this hook can act on the term object when updated.
  *
- * @param $term
- *   A taxonomy term object.
+ * @param TaxonomyTerm $term
+ *   A taxonomy term entity.
  */
-function hook_taxonomy_term_update($term) {
+function hook_taxonomy_term_update(TaxonomyTerm $term) {
   hook_taxonomy_term_delete($term);
   if (!empty($term->synonyms)) {
     foreach (explode ("\n", str_replace("\r", '', $term->synonyms)) as $synonym) {
@@ -199,12 +198,12 @@ function hook_taxonomy_term_update($term) {
  * field_attach_delete() is called and before the term is actually removed from
  * the database.
  *
- * @param $term
- *   The taxonomy term object for the term that is about to be deleted.
+ * @param TaxonomyTerm $term
+ *   The taxonomy term entity that is about to be deleted.
  *
  * @see taxonomy_term_delete()
  */
-function hook_taxonomy_term_predelete($term) {
+function hook_taxonomy_term_predelete(TaxonomyTerm $term) {
   db_delete('term_synoynm')->condition('tid', $term->tid)->execute();
 }
 
@@ -214,12 +213,12 @@ function hook_taxonomy_term_predelete($term) {
  * This hook is invoked from taxonomy_term_delete() after field_attach_delete()
  * has been called and after the term has been removed from the database.
  *
- * @param $term
- *   The taxonomy term object for the term that has been deleted.
+ * @param TaxonomyTerm $term
+ *   The taxonomy term entity that has been deleted.
  *
  * @see taxonomy_term_delete()
  */
-function hook_taxonomy_term_delete($term) {
+function hook_taxonomy_term_delete(TaxonomyTerm $term) {
   db_delete('term_synoynm')->condition('tid', $term->tid)->execute();
 }
 
@@ -237,7 +236,7 @@ function hook_taxonomy_term_delete($term) {
  * documentation respectively for details.
  *
  * @param $build
- *   A renderable array representing the node content.
+ *   A renderable array representing the taxonomy term content.
  *
  * @see hook_entity_view_alter()
  */
@@ -248,7 +247,7 @@ function hook_taxonomy_term_view_alter(&$build) {
   }
 
   // Add a #post_render callback to act on the rendered HTML of the term.
-  $build['#post_render'][] = 'my_module_node_post_render';
+  $build['#post_render'][] = 'my_module_taxonomy_term_post_render';
 }
 
 /**
diff --git a/core/modules/taxonomy/taxonomy.entity.inc b/core/modules/taxonomy/taxonomy.entity.inc
new file mode 100644
index 0000000..00182d8
--- /dev/null
+++ b/core/modules/taxonomy/taxonomy.entity.inc
@@ -0,0 +1,344 @@
+<?php
+
+/**
+ * @file
+ * Entity classes and controllers for Taxonomy module.
+ */
+
+/**
+ * Defines the taxonomy term entity.
+ */
+class TaxonomyTerm extends Entity {
+
+  /**
+   * The taxonomy term ID.
+   *
+   * @var integer
+   */
+  public $tid;
+
+  /**
+   * The taxonomy vocabulary ID this term belongs to.
+   *
+   * @var integer
+   */
+  public $vid;
+
+  /**
+   * Name of the term.
+   *
+   * @var string
+   */
+  public $name;
+
+  /**
+   * (optional) Description of the term.
+   *
+   * @var string
+   */
+  public $description;
+
+  /**
+   * (optional) The text format name for the term's description.
+   *
+   * @var string
+   */
+  public $format;
+
+  /**
+   * (optional) The weight of this term in relation to other terms of the same
+   * vocabulary.
+   *
+   * @var integer
+   */
+  public $weight = 0;
+
+  /**
+   * (optional) The parent term(s) for this term.
+   *
+   * This property is not loaded, but may be used to modify the term parents via
+   * TaxonomyTerm::save().
+   *
+   * The property can be set to an array of term IDs. An entry of 0 means this
+   * term does not have any parents. When omitting this variable during an
+   * update, the existing hierarchy for the term remains unchanged.
+   *
+   * @var array
+   */
+  public $parent;
+
+  /**
+   * (optional) The machine name of the vocabulary the term is assigned to. If
+   * not given, this value will be set automatically by loading the vocabulary
+   * based on the $entity->vid property.
+   *
+   * @var string
+   */
+  public $vocabulary_machine_name;
+}
+
+/**
+ * Controller class for taxonomy terms.
+ */
+class TaxonomyTermController extends EntityDatabaseStorageController {
+
+  /**
+   * Overrides EntityDatabaseStorageController::create().
+   *
+   * @param array $values
+   *   An array of values to set, keyed by property name. A value for the
+   *   vocabulary ID ('vid') is required.
+   */
+  public function create(array $values) {
+    $entity = parent::create($values);
+    // Ensure the vocabulary machine name is initialized as it is used as bundle
+    // key.
+    // @todo Move to TaxonomyTerm::bundle() once field API has been converted
+    // to make use of it.
+    if (!isset($entity->vocabulary_machine_name)) {
+      $vocabulary = taxonomy_vocabulary_load($entity->vid);
+      $entity->vocabulary_machine_name = $vocabulary->machine_name;
+    }
+    // Save new terms with no parents by default.
+    if (!isset($entity->parent)) {
+      $entity->parent = array(0);
+    }
+    return $entity;
+  }
+
+  /**
+   * Overrides EntityDatabaseStorageController::buildQuery().
+   */
+  protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) {
+    $query = parent::buildQuery($ids, $conditions, $revision_id);
+    $query->addTag('translatable');
+    $query->addTag('term_access');
+    // When name is passed as a condition use LIKE.
+    if (isset($conditions['name'])) {
+      $query_conditions = &$query->conditions();
+      foreach ($query_conditions as $key => $condition) {
+        if ($condition['field'] == 'base.name') {
+          $query_conditions[$key]['operator'] = 'LIKE';
+          $query_conditions[$key]['value'] = db_like($query_conditions[$key]['value']);
+        }
+      }
+    }
+    // Add the machine name field from the {taxonomy_vocabulary} table.
+    $query->innerJoin('taxonomy_vocabulary', 'v', 'base.vid = v.vid');
+    $query->addField('v', 'machine_name', 'vocabulary_machine_name');
+    return $query;
+  }
+
+  /**
+   * Overrides EntityDatabaseStorageController::cacheGet().
+   */
+  protected function cacheGet($ids, $conditions = array()) {
+    $terms = parent::cacheGet($ids, $conditions);
+    // Name matching is case insensitive, note that with some collations
+    // LOWER() and drupal_strtolower() may return different results.
+    foreach ($terms as $term) {
+      if (isset($conditions['name']) && drupal_strtolower($conditions['name'] != drupal_strtolower($term->name))) {
+        unset($terms[$term->tid]);
+      }
+    }
+    return $terms;
+  }
+
+  /**
+   * Overrides EntityDatabaseStorageController::postDelete().
+   */
+  protected function postDelete($entities) {
+    // See if any of the term's children are about to be become orphans.
+    $orphans = array();
+    foreach (array_keys($entities) as $tid) {
+      if ($children = taxonomy_term_load_children($tid)) {
+        foreach ($children as $child) {
+          // If the term has multiple parents, we don't delete it.
+          $parents = taxonomy_term_load_parents($child->tid);
+          // Because the parent has already been deleted, the parent count might
+          // be 0.
+          if (count($parents) <= 1) {
+            $orphans[] = $child->tid;
+          }
+        }
+      }
+    }
+
+    // Delete term hierarchy information after looking up orphans but before
+    // deleting them so that their children/parent information is consistent.
+    db_delete('taxonomy_term_hierarchy')
+      ->condition('tid', array_keys($entities))
+      ->execute();
+
+    if (!empty($orphans)) {
+      taxonomy_term_delete_multiple($orphans);
+    }
+  }
+
+  /**
+   * Overrides EntityDatabaseStorageController::postSave().
+   */
+  protected function postSave(EntityInterface $entity, $update) {
+    if (isset($entity->parent)) {
+      db_delete('taxonomy_term_hierarchy')
+        ->condition('tid', $entity->tid)
+        ->execute();
+
+      $query = db_insert('taxonomy_term_hierarchy')
+        ->fields(array('tid', 'parent'));
+
+      foreach ($entity->parent as $parent) {
+        $query->values(array(
+          'tid' => $entity->tid,
+          'parent' => $parent
+        ));
+      }
+      $query->execute();
+    }
+  }
+
+  /**
+   * Implements DrupalEntityControllerInterface::resetCache().
+   */
+  public function resetCache(array $ids = NULL) {
+    drupal_static_reset('taxonomy_term_count_nodes');
+    drupal_static_reset('taxonomy_get_tree');
+    drupal_static_reset('taxonomy_get_tree:parents');
+    drupal_static_reset('taxonomy_get_tree:terms');
+    drupal_static_reset('taxonomy_term_load_parents');
+    drupal_static_reset('taxonomy_term_load_parents_all');
+    drupal_static_reset('taxonomy_term_load_children');
+    parent::resetCache($ids);
+  }
+}
+
+/**
+ * Defines the taxonomy vocabulary entity.
+ */
+class TaxonomyVocabulary extends Entity {
+
+  /**
+   * The taxonomy vocabulary ID.
+   *
+   * @var integer
+   */
+  public $vid;
+
+  /**
+   * Name of the vocabulary.
+   *
+   * @var string
+   */
+  public $name;
+
+  /**
+   * The vocabulary machine name.
+   *
+   * @var string
+   */
+  public $machine_name;
+
+  /**
+   * (optional) Description of the vocabulary.
+   *
+   * @var string
+   */
+  public $description;
+
+  /**
+   * The type of hierarchy allowed within the vocabulary.
+   *
+   * Possible values:
+   * - TAXONOMY_HIERARCHY_DISABLED: No parents.
+   * - TAXONOMY_HIERARCHY_SINGLE: Single parent.
+   * - TAXONOMY_HIERARCHY_MULTIPLE: Multiple parents.
+   *
+   * @var integer
+   */
+  public $hierarchy = TAXONOMY_HIERARCHY_DISABLED;
+
+  /**
+   * (optional) The weight of this vocabulary in relation to other vocabularies.
+   *
+   * @var integer
+   */
+  public $weight = 0;
+}
+
+/**
+ * Controller class for taxonomy vocabularies.
+ */
+class TaxonomyVocabularyController extends EntityDatabaseStorageController {
+
+  /**
+   * Overrides EntityDatabaseStorageController::buildQuery().
+   */
+  protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) {
+    $query = parent::buildQuery($ids, $conditions, $revision_id);
+    $query->addTag('translatable');
+    $query->orderBy('base.weight');
+    $query->orderBy('base.name');
+    return $query;
+  }
+
+  /**
+   * Overrides EntityDatabaseStorageController::postSave().
+   */
+  protected function postSave(EntityInterface $entity, $update) {
+    if (!$update) {
+      field_attach_create_bundle('taxonomy_term', $entity->machine_name);
+    }
+    elseif ($entity->original->machine_name != $entity->machine_name) {
+      field_attach_rename_bundle('taxonomy_term', $entity->original->machine_name, $entity->machine_name);
+    }
+  }
+
+  /**
+   * Overrides EntityDatabaseStorageController::preDelete().
+   */
+  protected function preDelete($entities) {
+    // Only load terms without a parent, child terms will get deleted too.
+    $tids = db_query('SELECT t.tid FROM {taxonomy_term_data} t INNER JOIN {taxonomy_term_hierarchy} th ON th.tid = t.tid WHERE t.vid IN (:vids) AND th.parent = 0', array(':vids' => array_keys($entities)))->fetchCol();
+    taxonomy_term_delete_multiple($tids);
+  }
+
+  /**
+   * Overrides EntityDatabaseStorageController::postDelete().
+   */
+  protected function postDelete($entities) {
+    // Load all Taxonomy module fields and delete those which use only this
+    // vocabulary.
+    $taxonomy_fields = field_read_fields(array('module' => 'taxonomy'));
+    foreach ($taxonomy_fields as $field_name => $taxonomy_field) {
+      $modified_field = FALSE;
+      // Term reference fields may reference terms from more than one
+      // vocabulary.
+      foreach ($taxonomy_field['settings']['allowed_values'] as $key => $allowed_value) {
+        foreach ($entities as $vocabulary) {
+          if ($allowed_value['vocabulary'] == $vocabulary->machine_name) {
+            unset($taxonomy_field['settings']['allowed_values'][$key]);
+            $modified_field = TRUE;
+          }
+        }
+      }
+      if ($modified_field) {
+        if (empty($taxonomy_field['settings']['allowed_values'])) {
+          field_delete_field($field_name);
+        }
+        else {
+          // Update the field definition with the new allowed values.
+          field_update_field($taxonomy_field);
+        }
+      }
+    }
+  }
+
+  /**
+   * Implements DrupalEntityControllerInterface::resetCache().
+   */
+  public function resetCache(array $ids = NULL) {
+    drupal_static_reset('taxonomy_vocabulary_get_names');
+    parent::resetCache($ids);
+    cache_clear_all();
+  }
+}
diff --git a/core/modules/taxonomy/taxonomy.info b/core/modules/taxonomy/taxonomy.info
index 6a13f81..197a2ae 100644
--- a/core/modules/taxonomy/taxonomy.info
+++ b/core/modules/taxonomy/taxonomy.info
@@ -5,6 +5,6 @@ version = VERSION
 core = 8.x
 dependencies[] = options
 dependencies[] = entity
-files[] = taxonomy.module
+files[] = taxonomy.entity.inc
 files[] = taxonomy.test
 configure = admin/structure/taxonomy
diff --git a/core/modules/taxonomy/taxonomy.module b/core/modules/taxonomy/taxonomy.module
index 47cde42..03eea92 100644
--- a/core/modules/taxonomy/taxonomy.module
+++ b/core/modules/taxonomy/taxonomy.module
@@ -107,6 +107,7 @@ function taxonomy_entity_info() {
   $return = array(
     'taxonomy_term' => array(
       'label' => t('Taxonomy term'),
+      'entity class' => 'TaxonomyTerm',
       'controller class' => 'TaxonomyTermController',
       'base table' => 'taxonomy_term_data',
       'uri callback' => 'taxonomy_term_uri',
@@ -142,6 +143,7 @@ function taxonomy_entity_info() {
   }
   $return['taxonomy_vocabulary'] = array(
     'label' => t('Taxonomy vocabulary'),
+    'entity class' => 'TaxonomyVocabulary',
     'controller class' => 'TaxonomyVocabularyController',
     'base table' => 'taxonomy_vocabulary',
     'entity keys' => array(
@@ -373,7 +375,7 @@ function taxonomy_menu() {
   $items['admin/structure/taxonomy/%taxonomy_vocabulary_machine_name/add'] = array(
     'title' => 'Add term',
     'page callback' => 'drupal_get_form',
-    'page arguments' => array('taxonomy_form_term', array(), 3),
+    'page arguments' => array('taxonomy_form_term', NULL, 3),
     'access arguments' => array('administer taxonomy'),
     'type' => MENU_LOCAL_ACTION,
     'file' => 'taxonomy.admin.inc',
@@ -422,159 +424,54 @@ function taxonomy_term_access($op, $term) {
 /**
  * Return the vocabulary name given the vocabulary object.
  */
-function taxonomy_admin_vocabulary_title_callback($vocabulary) {
+function taxonomy_admin_vocabulary_title_callback(TaxonomyVocabulary $vocabulary) {
   return check_plain($vocabulary->name);
 }
 
 /**
  * Saves a vocabulary.
  *
- * @param $vocabulary
- *   A vocabulary object with the following properties:
- *   - vid: The ID of the vocabulary.
- *   - name: The human-readable name of the vocabulary.
- *   - machine_name: The machine name of the vocabulary.
- *   - langcode: The language code of the vocabulary.
- *   - description: (optional) The vocabulary's description.
- *   - hierarchy: The hierarchy level of the vocabulary.
- *   - module: (optional) The module altering the vocabulary.
- *   - weight: (optional) The weight of this vocabulary in relation to other
- *     vocabularies.
- *   - original: (optional) The original vocabulary object before any changes
- *     are applied.
- *   - old_machine_name: (optional) The original machine name of the
- *     vocabulary.
- *
- * @return
- *   Status constant indicating whether the vocabulary was inserted (SAVED_NEW)
- *   or updated(SAVED_UPDATED).
+ * @param TaxonomyVocabulary $vocabulary
+ *   The taxonomy vocabulary entity to be saved.
  */
-function taxonomy_vocabulary_save($vocabulary) {
-  // Prevent leading and trailing spaces in vocabulary names.
-  if (!empty($vocabulary->name)) {
-    $vocabulary->name = trim($vocabulary->name);
-  }
-  // Load the stored entity, if any.
-  if (!empty($vocabulary->vid)) {
-    if (!isset($vocabulary->original)) {
-      $vocabulary->original = entity_load_unchanged('taxonomy_vocabulary', $vocabulary->vid);
-    }
-    // Make sure machine name changes are easily detected.
-    // @todo: Remove in Drupal 8, as it is deprecated by directly reading from
-    // $vocabulary->original.
-    $vocabulary->old_machine_name = $vocabulary->original->machine_name;
-  }
-
-  module_invoke_all('taxonomy_vocabulary_presave', $vocabulary);
-  module_invoke_all('entity_presave', $vocabulary, 'taxonomy_vocabulary');
-
-  if (!empty($vocabulary->vid) && !empty($vocabulary->name)) {
-    $status = drupal_write_record('taxonomy_vocabulary', $vocabulary, 'vid');
-    taxonomy_vocabulary_static_reset(array($vocabulary->vid));
-    if ($vocabulary->old_machine_name != $vocabulary->machine_name) {
-      field_attach_rename_bundle('taxonomy_term', $vocabulary->old_machine_name, $vocabulary->machine_name);
-    }
-    module_invoke_all('taxonomy_vocabulary_update', $vocabulary);
-    module_invoke_all('entity_update', $vocabulary, 'taxonomy_vocabulary');
-  }
-  elseif (empty($vocabulary->vid)) {
-    $status = drupal_write_record('taxonomy_vocabulary', $vocabulary);
-    taxonomy_vocabulary_static_reset();
-    field_attach_create_bundle('taxonomy_term', $vocabulary->machine_name);
-    module_invoke_all('taxonomy_vocabulary_insert', $vocabulary);
-    module_invoke_all('entity_insert', $vocabulary, 'taxonomy_vocabulary');
-  }
-
-  unset($vocabulary->original);
-  cache_clear_all();
-
-  return $status;
+function taxonomy_vocabulary_save(TaxonomyVocabulary $vocabulary) {
+  return $vocabulary->save();
 }
 
 /**
- * Delete a vocabulary.
+ * Deletes a vocabulary.
  *
  * @param $vid
  *   A vocabulary ID.
- * @return
- *   Constant indicating items were deleted.
  *
- * @see hook_taxonomy_vocabulary_predelete()
- * @see hook_taxonomy_vocabulary_delete()
  */
 function taxonomy_vocabulary_delete($vid) {
-  $vocabulary = taxonomy_vocabulary_load($vid);
-
-  $transaction = db_transaction();
-  try {
-    // Allow modules to act before vocabulary deletion.
-    module_invoke_all('taxonomy_vocabulary_predelete', $vocabulary);
-    module_invoke_all('entity_predelete', $vocabulary, 'taxonomy_vocabulary');
-
-    // Only load terms without a parent, child terms will get deleted too.
-    $result = db_query('SELECT t.tid FROM {taxonomy_term_data} t INNER JOIN {taxonomy_term_hierarchy} th ON th.tid = t.tid WHERE t.vid = :vid AND th.parent = 0', array(':vid' => $vid))->fetchCol();
-    foreach ($result as $tid) {
-      taxonomy_term_delete($tid);
-    }
-    db_delete('taxonomy_vocabulary')
-      ->condition('vid', $vid)
-      ->execute();
-
-    field_attach_delete_bundle('taxonomy_term', $vocabulary->machine_name);
-
-    // Allow modules to respond to vocabulary deletion.
-    module_invoke_all('taxonomy_vocabulary_delete', $vocabulary);
-    module_invoke_all('entity_delete', $vocabulary, 'taxonomy_vocabulary');
-
-    // Load all Taxonomy module fields and delete those which use only this
-    // vocabulary.
-    $taxonomy_fields = field_read_fields(array('module' => 'taxonomy'));
-    foreach ($taxonomy_fields as $field_name => $taxonomy_field) {
-      $modified_field = FALSE;
-      // Term reference fields may reference terms from more than one
-      // vocabulary.
-      foreach ($taxonomy_field['settings']['allowed_values'] as $key => $allowed_value) {
-        if ($allowed_value['vocabulary'] == $vocabulary->machine_name) {
-          unset($taxonomy_field['settings']['allowed_values'][$key]);
-          $modified_field = TRUE;
-        }
-      }
-      if ($modified_field) {
-        if (empty($taxonomy_field['settings']['allowed_values'])) {
-          field_delete_field($field_name);
-        }
-        else {
-          // Update the field definition with the new allowed values.
-          field_update_field($taxonomy_field);
-        }
-      }
-    }
-
-    cache_clear_all();
-    taxonomy_vocabulary_static_reset();
+  taxonomy_vocabulary_delete_multiple(array($vid));
+}
 
-    return SAVED_DELETED;
-  }
-  catch (Exception $e) {
-    $transaction->rollback();
-    watchdog_exception('taxonomy', $e);
-    throw $e;
-  }
+/**
+ * Deletes vocabularies.
+ *
+ * @param $vids
+ *   The vocabulary ids.
+ */
+function taxonomy_vocabulary_delete_multiple(array $vids) {
+  entity_delete_multiple('taxonomy_vocabulary', $vids);
 }
 
 /**
  * Implements hook_taxonomy_vocabulary_update().
  */
-function taxonomy_taxonomy_vocabulary_update($vocabulary) {
+function taxonomy_taxonomy_vocabulary_update(TaxonomyVocabulary $vocabulary) {
   // Reflect machine name changes in the definitions of existing 'taxonomy'
   // fields.
-  if (!empty($vocabulary->old_machine_name) && $vocabulary->old_machine_name != $vocabulary->machine_name) {
+  if (!empty($vocabulary->original->machine_name) && $vocabulary->original->machine_name != $vocabulary->machine_name) {
     $fields = field_read_fields();
     foreach ($fields as $field_name => $field) {
       $update = FALSE;
       if ($field['type'] == 'taxonomy_term_reference') {
         foreach ($field['settings']['allowed_values'] as $key => &$value) {
-          if ($value['vocabulary'] == $vocabulary->old_machine_name) {
+          if ($value['vocabulary'] == $vocabulary->original->machine_name) {
             $value['vocabulary'] = $vocabulary->machine_name;
             $update = TRUE;
           }
@@ -598,15 +495,15 @@ function taxonomy_taxonomy_vocabulary_update($vocabulary) {
  * term has multiple parents then the vocabulary will be given a hierarchy of
  * TAXONOMY_HIERARCHY_MULTIPLE.
  *
- * @param $vocabulary
- *   A vocabulary object.
+ * @param TaxonomyVocabulary $vocabulary
+ *   A taxonomy vocabulary entity.
  * @param $changed_term
  *   An array of the term structure that was updated.
  *
  * @return
  *   An integer that represents the level of the vocabulary's hierarchy.
  */
-function taxonomy_check_vocabulary_hierarchy($vocabulary, $changed_term) {
+function taxonomy_check_vocabulary_hierarchy(TaxonomyVocabulary $vocabulary, $changed_term) {
   $tree = taxonomy_get_tree($vocabulary->vid);
   $hierarchy = TAXONOMY_HIERARCHY_DISABLED;
   foreach ($tree as $term) {
@@ -635,175 +532,43 @@ function taxonomy_check_vocabulary_hierarchy($vocabulary, $changed_term) {
 /**
  * Saves a term object to the database.
  *
- * @param $term
- *   The taxonomy term object with the following properties:
- *   - vid: The ID of the vocabulary the term is assigned to.
- *   - name: The name of the term.
- *   - langcode: The language code of the term.
- *   - tid: (optional) The unique ID for the term being saved. If $term->tid is
- *     empty or omitted, a new term will be inserted.
- *   - description: (optional) The term's description.
- *   - format: (optional) The text format for the term's description.
- *   - weight: (optional) The weight of this term in relation to other terms
- *     within the same vocabulary.
- *   - parent: (optional) The parent term(s) for this term. This can be a single
- *     term ID or an array of term IDs. A value of 0 means this term does not
- *     have any parents. When omitting this variable during an update, the
- *     existing hierarchy for the term remains unchanged.
- *   - vocabulary_machine_name: (optional) The machine name of the vocabulary
- *     the term is assigned to. If not given, this value will be set
- *     automatically by loading the vocabulary based on $term->vid.
- *   - original: (optional) The original taxonomy term object before any changes
- *     were applied. When omitted, the unchanged taxonomy term object is
- *     loaded from the database and stored in this property.
- *   Since a taxonomy term is an entity, any fields contained in the term object
- *   are saved alongside the term object.
+ * @param TaxonomyTerm $term
+ *   The taxonomy term entity to be saved.
  *
  * @return
  *   Status constant indicating whether term was inserted (SAVED_NEW) or updated
  *   (SAVED_UPDATED). When inserting a new term, $term->tid will contain the
  *   term ID of the newly created term.
  */
-function taxonomy_term_save($term) {
-  // Prevent leading and trailing spaces in term names.
-  $term->name = trim($term->name);
-  if (!isset($term->vocabulary_machine_name)) {
-    $vocabulary = taxonomy_vocabulary_load($term->vid);
-    $term->vocabulary_machine_name = $vocabulary->machine_name;
-  }
-
-  // Load the stored entity, if any.
-  if (!empty($term->tid) && !isset($term->original)) {
-    $term->original = entity_load_unchanged('taxonomy_term', $term->tid);
-  }
-
-  field_attach_presave('taxonomy_term', $term);
-  module_invoke_all('taxonomy_term_presave', $term);
-  module_invoke_all('entity_presave', $term, 'taxonomy_term');
-
-  if (empty($term->tid)) {
-    $op = 'insert';
-    $status = drupal_write_record('taxonomy_term_data', $term);
-    field_attach_insert('taxonomy_term', $term);
-    if (!isset($term->parent)) {
-      $term->parent = array(0);
-    }
-  }
-  else {
-    $op = 'update';
-    $status = drupal_write_record('taxonomy_term_data', $term, 'tid');
-    field_attach_update('taxonomy_term', $term);
-    if (isset($term->parent)) {
-      db_delete('taxonomy_term_hierarchy')
-        ->condition('tid', $term->tid)
-        ->execute();
-    }
-  }
-
-  if (isset($term->parent)) {
-    if (!is_array($term->parent)) {
-      $term->parent = array($term->parent);
-    }
-    $query = db_insert('taxonomy_term_hierarchy')
-      ->fields(array('tid', 'parent'));
-    foreach ($term->parent as $parent) {
-      if (is_array($parent)) {
-        foreach ($parent as $tid) {
-          $query->values(array(
-            'tid' => $term->tid,
-            'parent' => $tid
-          ));
-        }
-      }
-      else {
-        $query->values(array(
-          'tid' => $term->tid,
-          'parent' => $parent
-        ));
-      }
-    }
-    $query->execute();
-  }
-
-  // Reset the taxonomy term static variables.
-  taxonomy_terms_static_reset();
-
-  // Invoke the taxonomy hooks.
-  module_invoke_all("taxonomy_term_$op", $term);
-  module_invoke_all("entity_$op", $term, 'taxonomy_term');
-  unset($term->original);
-
-  return $status;
+function taxonomy_term_save(TaxonomyTerm $term) {
+  return $term->save();
 }
 
 /**
- * Delete a term.
+ * Deletes a term.
  *
  * @param $tid
  *   The term ID.
- * @return
- *   Status constant indicating deletion.
- *
- * @see hook_taxonomy_term_predelete()
- * @see hook_taxonomy_term_delete()
  */
 function taxonomy_term_delete($tid) {
-  $transaction = db_transaction();
-  try {
-    $tids = array($tid);
-    while ($tids) {
-      $children_tids = $orphans = array();
-      foreach ($tids as $tid) {
-        // Allow modules to act before term deletion.
-        if ($term = taxonomy_term_load($tid)) {
-          module_invoke_all('taxonomy_term_predelete', $term);
-          module_invoke_all('entity_predelete', $term, 'taxonomy_term');
-        }
-
-        // See if any of the term's children are about to be become orphans:
-        if ($children = taxonomy_term_load_children($tid)) {
-          foreach ($children as $child) {
-            // If the term has multiple parents, we don't delete it.
-            $parents = taxonomy_term_load_parents($child->tid);
-            if (count($parents) == 1) {
-              $orphans[] = $child->tid;
-            }
-          }
-        }
-
-        if ($term) {
-          db_delete('taxonomy_term_data')
-            ->condition('tid', $tid)
-            ->execute();
-          db_delete('taxonomy_term_hierarchy')
-            ->condition('tid', $tid)
-            ->execute();
-
-          field_attach_delete('taxonomy_term', $term);
-
-          // Allow modules to respond to term deletion.
-          module_invoke_all('taxonomy_term_delete', $term);
-          module_invoke_all('entity_delete', $term, 'taxonomy_term');
-          taxonomy_terms_static_reset();
-        }
-      }
+  taxonomy_term_delete_multiple(array($tid));
+}
 
-      $tids = $orphans;
-    }
-    return SAVED_DELETED;
-  }
-  catch (Exception $e) {
-    $transaction->rollback();
-    watchdog_exception('taxonomy', $e);
-    throw $e;
-  }
+/**
+ * Deletes taxonomy terms.
+ *
+ * @param $tids
+ *   The term ids to be deleted.
+ */
+function taxonomy_term_delete_multiple(array $tids) {
+  entity_delete_multiple('taxonomy_term', $tids);
 }
 
 /**
  * Generate an array for rendering the given term.
  *
- * @param $term
- *   A term object.
+ * @param TaxonomyTerm $term
+ *   A taxonomy term entity.
  * @param $view_mode
  *   View mode, e.g. 'full', 'teaser'...
  * @param $langcode
@@ -813,7 +578,7 @@ function taxonomy_term_delete($tid) {
  * @return
  *   An array as expected by drupal_render().
  */
-function taxonomy_term_view($term, $view_mode = 'full', $langcode = NULL) {
+function taxonomy_term_view(TaxonomyTerm $term, $view_mode = 'full', $langcode = NULL) {
   if (!isset($langcode)) {
     $langcode = $GLOBALS['language_content']->langcode;
   }
@@ -888,10 +653,10 @@ function template_preprocess_taxonomy_term(&$variables) {
 /**
  * Returns whether the current page is the page of the passed-in term.
  *
- * @param $term
- *   A term object.
+ * @param TaxonomyTerm $term
+ *   A taxonomy term entity.
  */
-function taxonomy_term_is_page($term) {
+function taxonomy_term_is_page(TaxonomyTerm $term) {
   $page_term = menu_get_object('taxonomy_term', 2);
   return (!empty($page_term) ? $page_term->tid == $term->tid : FALSE);
 }
@@ -900,13 +665,6 @@ function taxonomy_term_is_page($term) {
  * Clear all static cache variables for terms.
  */
 function taxonomy_terms_static_reset() {
-  drupal_static_reset('taxonomy_term_count_nodes');
-  drupal_static_reset('taxonomy_get_tree');
-  drupal_static_reset('taxonomy_get_tree:parents');
-  drupal_static_reset('taxonomy_get_tree:terms');
-  drupal_static_reset('taxonomy_term_load_parents');
-  drupal_static_reset('taxonomy_term_load_parents_all');
-  drupal_static_reset('taxonomy_term_load_children');
   entity_get_controller('taxonomy_term')->resetCache();
 }
 
@@ -914,10 +672,9 @@ function taxonomy_terms_static_reset() {
  * Clear all static cache variables for vocabularies.
  *
  * @param $ids
- * An array of ids to reset in entity controller cache.
+ *   An array of ids to reset in entity controller cache.
  */
-function taxonomy_vocabulary_static_reset($ids = NULL) {
-  drupal_static_reset('taxonomy_vocabulary_get_names');
+function taxonomy_vocabulary_static_reset(array $ids = NULL) {
   entity_get_controller('taxonomy_vocabulary')->resetCache($ids);
 }
 
@@ -1171,65 +928,6 @@ function taxonomy_term_load_multiple_by_name($name, $vocabulary = NULL) {
 }
 
 /**
- * Controller class for taxonomy terms.
- *
- * This extends the DrupalDefaultEntityController class. Only alteration is
- * that we match the condition on term name case-independently.
- */
-class TaxonomyTermController extends DrupalDefaultEntityController {
-
-  protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) {
-    $query = parent::buildQuery($ids, $conditions, $revision_id);
-    $query->addTag('translatable');
-    $query->addTag('term_access');
-    // When name is passed as a condition use LIKE.
-    if (isset($conditions['name'])) {
-      $query_conditions = &$query->conditions();
-      foreach ($query_conditions as $key => $condition) {
-        if ($condition['field'] == 'base.name') {
-          $query_conditions[$key]['operator'] = 'LIKE';
-          $query_conditions[$key]['value'] = db_like($query_conditions[$key]['value']);
-        }
-      }
-    }
-    // Add the machine name field from the {taxonomy_vocabulary} table.
-    $query->innerJoin('taxonomy_vocabulary', 'v', 'base.vid = v.vid');
-    $query->addField('v', 'machine_name', 'vocabulary_machine_name');
-    return $query;
-  }
-
-  protected function cacheGet($ids, $conditions = array()) {
-    $terms = parent::cacheGet($ids, $conditions);
-    // Name matching is case insensitive, note that with some collations
-    // LOWER() and drupal_strtolower() may return different results.
-    foreach ($terms as $term) {
-      $term_values = (array) $term;
-      if (isset($conditions['name']) && drupal_strtolower($conditions['name'] != drupal_strtolower($term_values['name']))) {
-        unset($terms[$term->tid]);
-      }
-    }
-    return $terms;
-  }
-}
-
-/**
- * Controller class for taxonomy vocabularies.
- *
- * This extends the DrupalDefaultEntityController class, adding required
- * special handling for taxonomy vocabulary objects.
- */
-class TaxonomyVocabularyController extends DrupalDefaultEntityController {
-
-  protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) {
-    $query = parent::buildQuery($ids, $conditions, $revision_id);
-    $query->addTag('translatable');
-    $query->orderBy('base.weight');
-    $query->orderBy('base.name');
-    return $query;
-  }
-}
-
-/**
  * Load multiple taxonomy terms based on certain conditions.
  *
  * This function should be used whenever you need to load more than one term
@@ -1249,8 +947,8 @@ class TaxonomyVocabularyController extends DrupalDefaultEntityController {
  *   this function.
  *
  * @return
- *   An array of term objects, indexed by tid. When no results are found, an
- *   empty array is returned.
+ *   An array of taxonomy term entities, indexed by tid. When no results are
+ *   found, an empty array is returned.
  *
  * @todo Remove $conditions in Drupal 8.
  */
@@ -1280,14 +978,14 @@ function taxonomy_vocabulary_load_multiple($vids = array(), $conditions = array(
 }
 
 /**
- * Return the vocabulary object matching a vocabulary ID.
+ * Return the taxonomy vocabulary entity matching a vocabulary ID.
  *
  * @param $vid
  *   The vocabulary's ID.
  *
- * @return
- *   The vocabulary object with all of its metadata, if exists, FALSE otherwise.
- *   Results are statically cached.
+ * @return TaxonomyVocabulary|false
+ *   The taxonomy vocabulary entity, if exists, FALSE otherwise. Results are
+ *   statically cached.
  *
  * @see taxonomy_vocabulary_machine_name_load()
  */
@@ -1297,14 +995,14 @@ function taxonomy_vocabulary_load($vid) {
 }
 
 /**
- * Return the vocabulary object matching a vocabulary machine name.
+ * Return the taxonomy vocabulary entity matching a vocabulary machine name.
  *
  * @param $name
  *   The vocabulary's machine name.
  *
- * @return
- *   The vocabulary object with all of its metadata, if exists, FALSE otherwise.
- *   Results are statically cached.
+ * @return TaxonomyVocabulary|false
+ *   The taxonomy vocabulary entity, if exists, FALSE otherwise. Results are
+ *   statically cached.
  *
  * @see taxonomy_vocabulary_load()
  */
@@ -1314,13 +1012,13 @@ function taxonomy_vocabulary_machine_name_load($name) {
 }
 
 /**
- * Return the term object matching a term ID.
+ * Return the taxonomy term entity matching a term ID.
  *
  * @param $tid
  *   A term's ID
  *
- * @return
- *   A term object. Results are statically cached.
+ * @return TaxonomyTerm|false
+ *   A taxonomy term entity. Results are statically cached.
  */
 function taxonomy_term_load($tid) {
   if (!is_numeric($tid)) {
@@ -1333,7 +1031,7 @@ function taxonomy_term_load($tid) {
 /**
  * Helper function for array_map purposes.
  */
-function _taxonomy_get_tid_from_term($term) {
+function _taxonomy_get_tid_from_term(TaxonomyTerm $term) {
   return $term->tid;
 }
 
@@ -1647,13 +1345,13 @@ function taxonomy_field_formatter_prepare_view($entity_type, $entities, $field,
 /**
  * Title callback for term pages.
  *
- * @param $term
- *   A term object.
+ * @param TaxonomyTerm $term
+ *   A taxonomy term entity.
  *
  * @return
  *   The term name to be used as the page title.
  */
-function taxonomy_term_title($term) {
+function taxonomy_term_title(TaxonomyTerm $term) {
   return $term->name;
 }
 
@@ -1829,7 +1527,7 @@ function taxonomy_rdf_mapping() {
 function taxonomy_field_presave($entity_type, $entity, $field, $instance, $langcode, &$items) {
   foreach ($items as $delta => $item) {
     if ($item['tid'] == 'autocreate') {
-      $term = (object) $item;
+      $term = entity_create('taxonomy_term', $item);
       unset($term->tid);
       $term->langcode = $langcode;
       taxonomy_term_save($term);
@@ -1947,7 +1645,7 @@ function taxonomy_delete_node_index($node) {
 /**
  * Implements hook_taxonomy_term_delete().
  */
-function taxonomy_taxonomy_term_delete($term) {
+function taxonomy_taxonomy_term_delete(TaxonomyTerm $term) {
   if (variable_get('taxonomy_maintain_index_table', TRUE)) {
     // Clean up the {taxonomy_index} table when terms are deleted.
     db_delete('taxonomy_index')->condition('tid', $term->tid)->execute();
diff --git a/core/modules/taxonomy/taxonomy.pages.inc b/core/modules/taxonomy/taxonomy.pages.inc
index 6994b6e..7f4fa45 100644
--- a/core/modules/taxonomy/taxonomy.pages.inc
+++ b/core/modules/taxonomy/taxonomy.pages.inc
@@ -8,12 +8,10 @@
 /**
  * Menu callback; displays all nodes associated with a term.
  *
- * @param $term
- *   The taxonomy term.
- * @return
- *   The page content.
+ * @param TaxonomyTerm $term
+ *   The taxonomy term entity.
  */
-function taxonomy_term_page($term) {
+function taxonomy_term_page(TaxonomyTerm $term) {
   // Assign the term name as the page title.
   drupal_set_title($term->name);
 
@@ -62,10 +60,10 @@ function taxonomy_term_page($term) {
 /**
  * Generate the content feed for a taxonomy term.
  *
- * @param $term
- *   The taxonomy term.
+ * @param TaxonomyTerm $term
+ *   The taxonomy term entity.
  */
-function taxonomy_term_feed($term) {
+function taxonomy_term_feed(TaxonomyTerm $term) {
   $channel['link'] = url('taxonomy/term/' . $term->tid, array('absolute' => TRUE));
   $channel['title'] = variable_get('site_name', 'Drupal') . ' - ' . $term->name;
   // Only display the description if we have a single term, to avoid clutter and confusion.
diff --git a/core/modules/taxonomy/taxonomy.test b/core/modules/taxonomy/taxonomy.test
index de50f2a..00c29dd 100644
--- a/core/modules/taxonomy/taxonomy.test
+++ b/core/modules/taxonomy/taxonomy.test
@@ -28,14 +28,15 @@ class TaxonomyWebTestCase extends DrupalWebTestCase {
    */
   function createVocabulary() {
     // Create a vocabulary.
-    $vocabulary = new stdClass();
-    $vocabulary->name = $this->randomName();
-    $vocabulary->description = $this->randomName();
-    $vocabulary->machine_name = drupal_strtolower($this->randomName());
-    $vocabulary->langcode = LANGUAGE_NOT_SPECIFIED;
-    $vocabulary->help = '';
-    $vocabulary->nodes = array('article' => 'article');
-    $vocabulary->weight = mt_rand(0, 10);
+    $vocabulary = entity_create('taxonomy_vocabulary', array(
+      'name' => $this->randomName(),
+      'description' => $this->randomName(),
+      'machine_name' => drupal_strtolower($this->randomName()),
+      'langcode' => LANGUAGE_NOT_SPECIFIED,
+      'help' => '',
+      'nodes' => array('article' => 'article'),
+      'weight' => mt_rand(0, 10),
+    ));
     taxonomy_vocabulary_save($vocabulary);
     return $vocabulary;
   }
@@ -44,13 +45,14 @@ class TaxonomyWebTestCase extends DrupalWebTestCase {
    * Returns a new term with random properties in vocabulary $vid.
    */
   function createTerm($vocabulary) {
-    $term = new stdClass();
-    $term->name = $this->randomName();
-    $term->description = $this->randomName();
-    // Use the first available text format.
-    $term->format = db_query_range('SELECT format FROM {filter_format}', 0, 1)->fetchField();
-    $term->vid = $vocabulary->vid;
-    $term->langcode = LANGUAGE_NOT_SPECIFIED;
+    $term = entity_create('taxonomy_term', array(
+      'name' => $this->randomName(),
+      'description' => $this->randomName(),
+      // Use the first available text format.
+      'format' => db_query_range('SELECT format FROM {filter_format}', 0, 1)->fetchField(),
+      'vid' => $vocabulary->vid,
+      'langcode' => LANGUAGE_NOT_SPECIFIED,
+    ));
     taxonomy_term_save($term);
     return $term;
   }
@@ -975,9 +977,10 @@ class TaxonomyTermTestCase extends TaxonomyWebTestCase {
 
     // Create a new term in a different vocabulary with the same name.
     $new_vocabulary = $this->createVocabulary();
-    $new_term = new stdClass();
-    $new_term->name = $term->name;
-    $new_term->vid = $new_vocabulary->vid;
+    $new_term = entity_create('taxonomy_term', array(
+      'name' => $term->name,
+      'vid' => $new_vocabulary->vid,
+    ));
     taxonomy_term_save($new_term);
 
     // Load multiple terms with the same name.
@@ -1358,7 +1361,7 @@ class TaxonomyLoadMultipleUnitTest extends TaxonomyWebTestCase {
 
     // Load the terms by tid, with a condition on vid.
     $terms3 = taxonomy_term_load_multiple(array_keys($terms2), array('vid' => $vocabulary->vid));
-    $this->assertEqual($terms2, $terms3);
+    $this->assertEqual($terms2, $terms3, 'Same terms found when limiting load to vocabulary.');
 
     // Remove one term from the array, then delete it.
     $deleted = array_shift($terms3);
diff --git a/core/scripts/generate-d7-content.sh b/core/scripts/generate-d7-content.sh
index e65c099..ccb0b46 100644
--- a/core/scripts/generate-d7-content.sh
+++ b/core/scripts/generate-d7-content.sh
@@ -66,16 +66,17 @@ $required  = array(0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1);
 $voc_id = 0;
 $term_id = 0;
 for ($i = 0; $i < 24; $i++) {
-  $vocabulary = new stdClass;
   ++$voc_id;
-  $vocabulary->name = "vocabulary $voc_id (i=$i)";
-  $vocabulary->machine_name = 'vocabulary_' . $voc_id . '_' . $i;
-  $vocabulary->description = "description of ". $vocabulary->name;
-  $vocabulary->multiple = $multiple[$i % 12];
-  $vocabulary->required = $required[$i % 12];
-  $vocabulary->relations = 1;
-  $vocabulary->hierarchy = $hierarchy[$i % 12];
-  $vocabulary->weight = $i;
+  $vocabulary = entity_create('taxonomy_vocabulary', array(
+    'name' => "vocabulary $voc_id (i=$i)",
+    'machine_name' => 'vocabulary_' . $voc_id . '_' . $i,
+    'description' => "description of " . $vocabulary->name,
+    'multiple' => $multiple[$i % 12],
+    'required' => $required[$i % 12],
+    'relations' => 1,
+    'hierarchy' => $hierarchy[$i % 12],
+    'weight' => $i,
+  ));
   taxonomy_vocabulary_save($vocabulary);
   $field = array(
     'field_name' => 'taxonomy_'. $vocabulary->machine_name,
@@ -139,16 +140,17 @@ for ($i = 0; $i < 24; $i++) {
   // one parent and one child term. Multiple parent vocabularies get three
   // terms: t0, t1, t2 where t0 is a parent of both t1 and t2.
   for ($j = 0; $j < $vocabulary->hierarchy + 1; $j++) {
-    $term = new stdClass;
-    $term->vocabulary_machine_name = $vocabulary->machine_name;
-    // For multiple parent vocabularies, omit the t0-t1 relation, otherwise
-    // every parent in the vocabulary is a parent.
-    $term->parent = $vocabulary->hierarchy == 2 && i == 1 ? array() : $parents;
     ++$term_id;
-    $term->name = "term $term_id of vocabulary $voc_id (j=$j)";
-    $term->description = 'description of ' . $term->name;
-    $term->format = 'filtered_html';
-    $term->weight = $i * 3 + $j;
+    $term = entity_create('taxonomy_term', array(
+      'vocabulary_machine_name' => $vocabulary->machine_name,
+      // For multiple parent vocabularies, omit the t0-t1 relation, otherwise
+      // every parent in the vocabulary is a parent.
+      'parent' => $vocabulary->hierarchy == 2 && i == 1 ? array() : $parents,
+      'name' => "term $term_id of vocabulary $voc_id (j=$j)",
+      'description' => 'description of ' . $term->name,
+      'format' => 'filtered_html',
+      'weight' => $i * 3 + $j,
+    ));
     taxonomy_term_save($term);
     $terms[] = $term->tid;
     $term_vocabs[$term->tid] = 'taxonomy_' . $vocabulary->machine_name;
diff --git a/profiles/standard/standard.install b/profiles/standard/standard.install
index d62b8d3..30a943b 100644
--- a/profiles/standard/standard.install
+++ b/profiles/standard/standard.install
@@ -285,14 +285,13 @@ function standard_install() {
   // Create a default vocabulary named "Tags", enabled for the 'article' content type.
   $description = st('Use tags to group articles on similar topics into categories.');
   $help = st('Enter a comma-separated list of words to describe your content.');
-  $vocabulary = (object) array(
+  $vocabulary = entity_create('taxonomy_vocabulary', array(
     'name' => st('Tags'),
     'description' => $description,
     'machine_name' => 'tags',
     'langcode' => language_default()->langcode,
     'help' => $help,
-
-  );
+  ));
   taxonomy_vocabulary_save($vocabulary);
 
   $field = array(
