 core/modules/forum/forum.module                    |  3 +-
 core/modules/taxonomy/src/Entity/Vocabulary.php    |  8 ++--
 core/modules/taxonomy/src/Form/OverviewTerms.php   | 12 +++---
 core/modules/taxonomy/src/TermForm.php             |  6 +--
 core/modules/taxonomy/src/VocabularyInterface.php  | 21 ++++++++--
 core/modules/taxonomy/taxonomy.module              | 36 ++++++++++------
 .../src/VocabularyResponse.php                     | 19 +++++++++
 .../src/VocabularySerializationTestController.php  | 15 +++++++
 .../vocabulary_serialization_test.info.yml         |  7 ++++
 .../vocabulary_serialization_test.routing.yml      |  6 +++
 .../src/Functional/VocabularySerializationTest.php | 48 ++++++++++++++++++++++
 .../Migrate/d7/MigrateTaxonomyVocabularyTest.php   |  6 +--
 12 files changed, 154 insertions(+), 33 deletions(-)

diff --git a/core/modules/forum/forum.module b/core/modules/forum/forum.module
index 3306887..8d4a9f4 100644
--- a/core/modules/forum/forum.module
+++ b/core/modules/forum/forum.module
@@ -12,6 +12,7 @@
 use Drupal\Core\Url;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\taxonomy\VocabularyInterface;
 use Drupal\user\Entity\User;
 
 /**
@@ -295,7 +296,7 @@ function forum_form_taxonomy_vocabulary_form_alter(&$form, FormStateInterface $f
     // Forum's vocabulary always has single hierarchy. Forums and containers
     // have only one parent or no parent for root items. By default this value
     // is 0.
-    $form['hierarchy']['#value'] = TAXONOMY_HIERARCHY_SINGLE;
+    $form['hierarchy']['#value'] = VocabularyInterface::HIERARCHY_SINGLE;
     // Do not allow to delete forum's vocabulary.
     $form['actions']['delete']['#access'] = FALSE;
     // Do not allow to change a vid of forum's vocabulary.
diff --git a/core/modules/taxonomy/src/Entity/Vocabulary.php b/core/modules/taxonomy/src/Entity/Vocabulary.php
index fa6cc18..a2d7eef 100644
--- a/core/modules/taxonomy/src/Entity/Vocabulary.php
+++ b/core/modules/taxonomy/src/Entity/Vocabulary.php
@@ -73,13 +73,13 @@ class Vocabulary extends ConfigEntityBundleBase implements VocabularyInterface {
    * 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.
+   * - VocabularyInterface::HIERARCHY_DISABLED: No parents.
+   * - VocabularyInterface::HIERARCHY_SINGLE: Single parent.
+   * - VocabularyInterface::HIERARCHY_MULTIPL: Multiple parents.
    *
    * @var int
    */
-  protected $hierarchy = TAXONOMY_HIERARCHY_DISABLED;
+  protected $hierarchy = VocabularyInterface::HIERARCHY_DISABLED;
 
   /**
    * The weight of this vocabulary in relation to other vocabularies.
diff --git a/core/modules/taxonomy/src/Form/OverviewTerms.php b/core/modules/taxonomy/src/Form/OverviewTerms.php
index cdc6619..1babe40 100644
--- a/core/modules/taxonomy/src/Form/OverviewTerms.php
+++ b/core/modules/taxonomy/src/Form/OverviewTerms.php
@@ -223,7 +223,7 @@ public function buildForm(array $form, FormStateInterface $form_state, Vocabular
         '#title' => $term->getName(),
         '#url' => $term->urlInfo(),
       );
-      if ($taxonomy_vocabulary->getHierarchy() != TAXONOMY_HIERARCHY_MULTIPLE && count($tree) > 1) {
+      if ($taxonomy_vocabulary->getHierarchy() != VocabularyInterface::HIERARCHY_MULTIPLE && count($tree) > 1) {
         $parent_fields = TRUE;
         $form['terms'][$key]['term']['tid'] = array(
           '#type' => 'hidden',
@@ -340,7 +340,7 @@ public function buildForm(array $form, FormStateInterface $form_state, Vocabular
       'group' => 'term-weight',
     );
 
-    if ($taxonomy_vocabulary->getHierarchy() != TAXONOMY_HIERARCHY_MULTIPLE && count($tree) > 1) {
+    if ($taxonomy_vocabulary->getHierarchy() != VocabularyInterface::HIERARCHY_MULTIPLE && count($tree) > 1) {
       $form['actions'] = array('#type' => 'actions', '#tree' => FALSE);
       $form['actions']['submit'] = array(
         '#type' => 'submit',
@@ -382,7 +382,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
 
     $vocabulary = $form_state->get(['taxonomy', 'vocabulary']);
     // Update the current hierarchy type as we go.
-    $hierarchy = TAXONOMY_HIERARCHY_DISABLED;
+    $hierarchy = VocabularyInterface::HIERARCHY_DISABLED;
 
     $changed_terms = array();
     $tree = $this->storageController->loadTree($vocabulary->id(), 0, NULL, TRUE);
@@ -400,7 +400,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
         $changed_terms[$term->id()] = $term;
       }
       $weight++;
-      $hierarchy = $term->parents[0] != 0 ? TAXONOMY_HIERARCHY_SINGLE : $hierarchy;
+      $hierarchy = $term->parents[0] != 0 ? VocabularyInterface::HIERARCHY_SINGLE: $hierarchy;
       $term = $tree[$weight];
     }
 
@@ -427,7 +427,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
           $term->parent->target_id = $values['term']['parent'];
           $changed_terms[$term->id()] = $term;
         }
-        $hierarchy = $term->parents[0] != 0 ? TAXONOMY_HIERARCHY_SINGLE : $hierarchy;
+        $hierarchy = $term->parents[0] != 0 ? VocabularyInterface::HIERARCHY_SINGLE : $hierarchy;
         $weight++;
       }
     }
@@ -440,7 +440,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
         $term->setWeight($weight);
         $changed_terms[$term->id()] = $term;
       }
-      $hierarchy = $term->parents[0] != 0 ? TAXONOMY_HIERARCHY_SINGLE : $hierarchy;
+      $hierarchy = $term->parents[0] != 0 ? VocabularyInterface::HIERARCHY_SINGLE : $hierarchy;
     }
 
     // Save all updated terms.
diff --git a/core/modules/taxonomy/src/TermForm.php b/core/modules/taxonomy/src/TermForm.php
index cf05932..1f56351 100644
--- a/core/modules/taxonomy/src/TermForm.php
+++ b/core/modules/taxonomy/src/TermForm.php
@@ -26,7 +26,7 @@ public function form(array $form, FormStateInterface $form_state) {
     $form['relations'] = array(
       '#type' => 'details',
       '#title' => $this->t('Relations'),
-      '#open' => $vocabulary->getHierarchy() == TAXONOMY_HIERARCHY_MULTIPLE,
+      '#open' => $vocabulary->getHierarchy() == VocabularyInterface::HIERARCHY_MULTIPLE,
       '#weight' => 10,
     );
 
@@ -152,8 +152,8 @@ public function save(array $form, FormStateInterface $form_state) {
     }
     // If we've increased the number of parents and this is a single or flat
     // hierarchy, update the vocabulary immediately.
-    elseif ($current_parent_count > $previous_parent_count && $vocabulary->getHierarchy() != TAXONOMY_HIERARCHY_MULTIPLE) {
-      $vocabulary->setHierarchy($current_parent_count == 1 ? TAXONOMY_HIERARCHY_SINGLE : TAXONOMY_HIERARCHY_MULTIPLE);
+    elseif ($current_parent_count > $previous_parent_count && $vocabulary->getHierarchy() != VocabularyInterface::HIERARCHY_MULTIPLE) {
+      $vocabulary->setHierarchy($current_parent_count == 1 ? VocabularyInterface::HIERARCHY_SINGLE : VocabularyInterface::HIERARCHY_MULTIPLE);
       $vocabulary->save();
     }
 
diff --git a/core/modules/taxonomy/src/VocabularyInterface.php b/core/modules/taxonomy/src/VocabularyInterface.php
index ff52bec..cb54e20 100644
--- a/core/modules/taxonomy/src/VocabularyInterface.php
+++ b/core/modules/taxonomy/src/VocabularyInterface.php
@@ -9,6 +9,21 @@
  */
 interface VocabularyInterface extends ConfigEntityInterface {
 
+  /**
+   * Denotes that no term in the vocabulary has a parent.
+   */
+  const HIERARCHY_DISABLED = 0;
+
+  /**
+   * Denotes that one or more terms in the vocabulary has a single parent.
+   */
+  const HIERARCHY_SINGLE = 1;
+
+  /**
+   * Denotes that one or more terms in the vocabulary have multiple parents.
+   */
+  const HIERARCHY_MULTIPLE = 2;
+
   /**
    * Returns the vocabulary hierarchy.
    *
@@ -23,9 +38,9 @@ public function getHierarchy();
    * @param int $hierarchy
    *   The hierarchy type of vocabulary.
    *   Possible values:
-   *    - TAXONOMY_HIERARCHY_DISABLED: No parents.
-   *    - TAXONOMY_HIERARCHY_SINGLE: Single parent.
-   *    - TAXONOMY_HIERARCHY_MULTIPLE: Multiple parents.
+   *    - VocabularyInterface::HIERARCHY_DISABLED: No parents.
+   *    - VocabularyInterface::HIERARCHY_SINGLE: Single parent.
+   *    - VocabularyInterface::HIERARCHY_MULTIPLE: Multiple parents.
    *
    * @return $this
    */
diff --git a/core/modules/taxonomy/taxonomy.module b/core/modules/taxonomy/taxonomy.module
index 32df678..90acec7 100644
--- a/core/modules/taxonomy/taxonomy.module
+++ b/core/modules/taxonomy/taxonomy.module
@@ -19,18 +19,27 @@
 
 /**
  * Denotes that no term in the vocabulary has a parent.
+ *
+ * @deprecated in Drupal 8.2.x and will be removed before 9.0.0. Use
+ *   \Drupal\taxonomy\VocabularyInterface::HIERARCHY_DISABLED instead.
  */
-const TAXONOMY_HIERARCHY_DISABLED = 0;
+const TAXONOMY_HIERARCHY_DISABLED = VocabularyInterface::HIERARCHY_DISABLED;
 
 /**
  * Denotes that one or more terms in the vocabulary has a single parent.
+ *
+ * @deprecated in Drupal 8.2.x and will be removed before 9.0.0. Use
+ *   \Drupal\taxonomy\VocabularyInterface::HIERARCHY_SINGLE instead.
  */
-const TAXONOMY_HIERARCHY_SINGLE = 1;
+const TAXONOMY_HIERARCHY_SINGLE = VocabularyInterface::HIERARCHY_SINGLE;
 
 /**
  * Denotes that one or more terms in the vocabulary have multiple parents.
+ *
+ * @deprecated in Drupal 8.2.x and will be removed before 9.0.0. Use
+ *   \Drupal\taxonomy\VocabularyInterface::HIERARCHY_MULTIPLE instead.
  */
-const TAXONOMY_HIERARCHY_MULTIPLE = 2;
+const TAXONOMY_HIERARCHY_MULTIPLE = VocabularyInterface::HIERARCHY_MULTIPLE;
 
 /**
  * Implements hook_help().
@@ -67,11 +76,11 @@ function taxonomy_help($route_name, RouteMatchInterface $route_match) {
     case 'entity.taxonomy_vocabulary.overview_form':
       $vocabulary = $route_match->getParameter('taxonomy_vocabulary');
       switch ($vocabulary->getHierarchy()) {
-        case TAXONOMY_HIERARCHY_DISABLED:
+        case VocabularyInterface::HIERARCHY_DISABLED:
           return '<p>' . t('You can reorganize the terms in %capital_name using their drag-and-drop handles, and group terms under a parent term by sliding them under and to the right of the parent.', array('%capital_name' => Unicode::ucfirst($vocabulary->label()), '%name' => $vocabulary->label())) . '</p>';
-        case TAXONOMY_HIERARCHY_SINGLE:
+        case VocabularyInterface::HIERARCHY_SINGLE:
           return '<p>' . t('%capital_name contains terms grouped under parent terms. You can reorganize the terms in %capital_name using their drag-and-drop handles.', array('%capital_name' => Unicode::ucfirst($vocabulary->label()), '%name' => $vocabulary->label())) . '</p>';
-        case TAXONOMY_HIERARCHY_MULTIPLE:
+        case VocabularyInterface::HIERARCHY_MULTIPLE:
           return '<p>' . t('%capital_name contains terms with multiple parents. Drag and drop of terms with multiple parents is not supported, but you can re-enable drag-and-drop support by editing each term to include only a single parent.', array('%capital_name' => Unicode::ucfirst($vocabulary->label()))) . '</p>';
       }
   }
@@ -134,10 +143,11 @@ function taxonomy_theme() {
  * Checks the current parents of all terms in a vocabulary and updates the
  * vocabulary's hierarchy setting to the lowest possible level. If no term
  * has parent terms then the vocabulary will be given a hierarchy of
- * TAXONOMY_HIERARCHY_DISABLED. If any term has a single parent then the
- * vocabulary will be given a hierarchy of TAXONOMY_HIERARCHY_SINGLE. If any
- * term has multiple parents then the vocabulary will be given a hierarchy of
- * TAXONOMY_HIERARCHY_MULTIPLE.
+ * VocabularyInterface::HIERARCHY_DISABLED. If any term has a single parent then
+ * the vocabulary will be given a hierarchy of
+ * VocabularyInterface::HIERARCHY_SINGLE. If any term has multiple parents then
+ * the vocabulary will be given a hierarchy of
+ * VocabularyInterface::HIERARCHY_MULTIPLE.
  *
  * @param \Drupal\taxonomy\VocabularyInterface $vocabulary
  *   A taxonomy vocabulary entity.
@@ -149,7 +159,7 @@ function taxonomy_theme() {
  */
 function taxonomy_check_vocabulary_hierarchy(VocabularyInterface $vocabulary, $changed_term) {
   $tree = \Drupal::entityManager()->getStorage('taxonomy_term')->loadTree($vocabulary->id());
-  $hierarchy = TAXONOMY_HIERARCHY_DISABLED;
+  $hierarchy = VocabularyInterface::HIERARCHY_DISABLED;
   foreach ($tree as $term) {
     // Update the changed term with the new parent value before comparison.
     if ($term->tid == $changed_term['tid']) {
@@ -158,11 +168,11 @@ function taxonomy_check_vocabulary_hierarchy(VocabularyInterface $vocabulary, $c
     }
     // Check this term's parent count.
     if (count($term->parents) > 1) {
-      $hierarchy = TAXONOMY_HIERARCHY_MULTIPLE;
+      $hierarchy = VocabularyInterface::HIERARCHY_MULTIPLE;
       break;
     }
     elseif (count($term->parents) == 1 && !isset($term->parents[0])) {
-      $hierarchy = TAXONOMY_HIERARCHY_SINGLE;
+      $hierarchy = VocabularyInterface::HIERARCHY_SINGLE;
     }
   }
   if ($hierarchy != $vocabulary->getHierarchy()) {
diff --git a/core/modules/taxonomy/tests/modules/vocabulary_serialization_test/src/VocabularyResponse.php b/core/modules/taxonomy/tests/modules/vocabulary_serialization_test/src/VocabularyResponse.php
new file mode 100644
index 0000000..39941b4
--- /dev/null
+++ b/core/modules/taxonomy/tests/modules/vocabulary_serialization_test/src/VocabularyResponse.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace Drupal\vocabulary_serialization_test;
+
+use Drupal\Core\Cache\CacheableResponse;
+use Drupal\taxonomy\VocabularyInterface;
+
+class VocabularyResponse extends CacheableResponse {
+
+  /**
+   * @var \Drupal\taxonomy\VocabularyInterface
+   */
+  protected $vocabulary;
+
+  public function setVocabulary(VocabularyInterface $vocabulary) {
+    $this->vocabulary = $vocabulary;
+  }
+
+}
diff --git a/core/modules/taxonomy/tests/modules/vocabulary_serialization_test/src/VocabularySerializationTestController.php b/core/modules/taxonomy/tests/modules/vocabulary_serialization_test/src/VocabularySerializationTestController.php
new file mode 100644
index 0000000..829edfb
--- /dev/null
+++ b/core/modules/taxonomy/tests/modules/vocabulary_serialization_test/src/VocabularySerializationTestController.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Drupal\vocabulary_serialization_test;
+
+use Drupal\taxonomy\VocabularyInterface;
+
+class VocabularySerializationTestController {
+
+  public function vocabularyResponse(VocabularyInterface $taxonomy_vocabulary) {
+    $response = new VocabularyResponse('this is the output');
+    $response->setVocabulary($taxonomy_vocabulary);
+    return $response;
+  }
+
+}
diff --git a/core/modules/taxonomy/tests/modules/vocabulary_serialization_test/vocabulary_serialization_test.info.yml b/core/modules/taxonomy/tests/modules/vocabulary_serialization_test/vocabulary_serialization_test.info.yml
new file mode 100644
index 0000000..be28f20
--- /dev/null
+++ b/core/modules/taxonomy/tests/modules/vocabulary_serialization_test/vocabulary_serialization_test.info.yml
@@ -0,0 +1,7 @@
+name: 'Vocabulary serialization test'
+type: module
+package: Testing
+version: VERSION
+core: 8.x
+dependencies:
+  - taxonomy
diff --git a/core/modules/taxonomy/tests/modules/vocabulary_serialization_test/vocabulary_serialization_test.routing.yml b/core/modules/taxonomy/tests/modules/vocabulary_serialization_test/vocabulary_serialization_test.routing.yml
new file mode 100644
index 0000000..82f2b22
--- /dev/null
+++ b/core/modules/taxonomy/tests/modules/vocabulary_serialization_test/vocabulary_serialization_test.routing.yml
@@ -0,0 +1,6 @@
+vocabulary_serialization_test:
+  path: '/vocabulary_serialization_test/{taxonomy_vocabulary}'
+  defaults:
+    _controller: 'Drupal\vocabulary_serialization_test\VocabularySerializationTestController::vocabularyResponse'
+  requirements:
+    _access: 'TRUE'
diff --git a/core/modules/taxonomy/tests/src/Functional/VocabularySerializationTest.php b/core/modules/taxonomy/tests/src/Functional/VocabularySerializationTest.php
new file mode 100644
index 0000000..0ae0c48
--- /dev/null
+++ b/core/modules/taxonomy/tests/src/Functional/VocabularySerializationTest.php
@@ -0,0 +1,48 @@
+<?php
+
+namespace Drupal\Tests\taxonomy\Functional;
+
+use Drupal\Core\Url;
+use Drupal\taxonomy\Entity\Vocabulary;
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Regression test for https://www.drupal.org/node/2807263.
+ *
+ * When a Vocabulary entity is unserialized before the modules have been loaded
+ * (which happens in the KernelPreHandle Stack middleware), then the constants
+ * that the Vocabulary entity uses are not yet available because they are set in
+ * taxonomy.module. This means that for example the PageCache middleware cannot
+ * load any cached Vocabulary entity, because unserialization will fail.
+ *
+ * @group taxonomy
+ */
+class VocabularySerializationTest extends BrowserTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['taxonomy', 'vocabulary_serialization_test'];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    Vocabulary::create(['vid' => 'test'])->save();
+  }
+
+  public function testSerialization() {
+    $this->drupalGet('/vocabulary_serialization_test/test');
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSame('this is the output', $this->getSession()->getPage()->getContent());
+    $this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'MISS');
+
+    $this->drupalGet('/vocabulary_serialization_test/test');
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSame('this is the output', $this->getSession()->getPage()->getContent());
+    $this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'HIT');
+  }
+
+}
diff --git a/core/modules/taxonomy/tests/src/Kernel/Migrate/d7/MigrateTaxonomyVocabularyTest.php b/core/modules/taxonomy/tests/src/Kernel/Migrate/d7/MigrateTaxonomyVocabularyTest.php
index f94d791..3f29fbf 100644
--- a/core/modules/taxonomy/tests/src/Kernel/Migrate/d7/MigrateTaxonomyVocabularyTest.php
+++ b/core/modules/taxonomy/tests/src/Kernel/Migrate/d7/MigrateTaxonomyVocabularyTest.php
@@ -54,9 +54,9 @@ protected function assertEntity($id, $expected_label, $expected_description, $ex
    * Tests the Drupal 7 taxonomy vocabularies to Drupal 8 migration.
    */
   public function testTaxonomyVocabulary() {
-    $this->assertEntity('tags', 'Tags', 'Use tags to group articles on similar topics into categories.', TAXONOMY_HIERARCHY_DISABLED, 0);
-    $this->assertEntity('forums', 'Forums', 'Forum navigation vocabulary', TAXONOMY_HIERARCHY_SINGLE, -10);
-    $this->assertEntity('test_vocabulary', 'Test Vocabulary', 'This is the vocabulary description', TAXONOMY_HIERARCHY_SINGLE, 0);
+    $this->assertEntity('tags', 'Tags', 'Use tags to group articles on similar topics into categories.', VocabularyInterface::HIERARCHY_DISABLED, 0);
+    $this->assertEntity('forums', 'Forums', 'Forum navigation vocabulary', VocabularyInterface::HIERARCHY_SINGLE, -10);
+    $this->assertEntity('test_vocabulary', 'Test Vocabulary', 'This is the vocabulary description', VocabularyInterface::HIERARCHY_SINGLE, 0);
   }
 
 }
