commit 5a9fff628fa579334cf8cb8a46b44fb6df4610fa
Author: Bart Feenstra <bart@mynameisbart.com>
Date:   Sun Jan 26 16:18:19 2014 +0100

    foo

diff --git a/core/core.services.yml b/core/core.services.yml
index f160803..612a7bd 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -66,6 +66,13 @@ services:
     factory_method: get
     factory_service: cache_factory
     arguments: [path]
+  cache.token:
+    class: Drupal\Core\Cache\CacheBackendInterface
+    tags:
+      - { name: cache.bin }
+    factory_method: get
+    factory_service: cache_factory
+    arguments: [token]
   config.cachedstorage.storage:
     class: Drupal\Core\Config\FileStorage
     factory_class: Drupal\Core\Config\FileStorageFactory
@@ -596,7 +603,7 @@ services:
     arguments: ['@module_handler']
   token:
     class: Drupal\Core\Utility\Token
-    arguments: ['@module_handler']
+    arguments: ['@module_handler', '@cache.token', '@language_manager']
   batch.storage:
     class: Drupal\Core\Batch\BatchStorage
     arguments: ['@database']
diff --git a/core/lib/Drupal/Core/Utility/Token.php b/core/lib/Drupal/Core/Utility/Token.php
index 29fef78..bbd3d2a 100644
--- a/core/lib/Drupal/Core/Utility/Token.php
+++ b/core/lib/Drupal/Core/Utility/Token.php
@@ -7,7 +7,10 @@
 
 namespace Drupal\Core\Utility;
 
+use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Language\Language;
+use Drupal\Core\Language\LanguageManagerInterface;
 
 /**
  * Drupal placeholder/token replacement system.
@@ -55,9 +58,23 @@
 class Token {
 
   /**
+   * The token cache.
+   *
+   * @var \Drupal\Core\Cache\CacheBackendInterface
+   */
+  protected $cache;
+
+  /**
+   * The language manager.
+   *
+   * @var \Drupal\Core\Language\LanguageManagerInterface
+   */
+  protected $languageManager;
+
+  /**
    * Token definitions.
    *
-   * @var array|null
+   * @var array[]|null
    *   An array of token definitions, or NULL when the definitions are not set.
    *
    * @see self::setInfo()
@@ -74,11 +91,18 @@ class Token {
   protected $moduleHandler;
 
   /**
-   * Constructor.
+   * Constructs a new class instance.
    *
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler.
+   * @param \Drupal\Core\Cache\CacheBackendInterface $cache
+   *   The token cache.
+   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
+   *   The language manager.
    */
-  public function __construct(ModuleHandlerInterface $module_handler) {
+  public function __construct(ModuleHandlerInterface $module_handler, CacheBackendInterface $cache, LanguageManagerInterface $language_manager) {
+    $this->cache = $cache;
+    $this->languageManager = $language_manager;
     $this->moduleHandler = $module_handler;
   }
 
@@ -280,8 +304,16 @@ public function findWithPrefix(array $tokens, $prefix, $delimiter = ':') {
    */
   public function getInfo() {
     if (is_null($this->tokenInfo)) {
-      $this->tokenInfo = $this->moduleHandler->invokeAll('token_info');
-      $this->moduleHandler->alter('token_info', $this->tokenInfo);
+      $cache_id = 'token_info:' . $this->languageManager->getCurrentLanguage(Language::TYPE_CONTENT)->id;
+      $cache = $this->cache->get($cache_id);
+      if ($cache) {
+        $this->tokenInfo = $cache->data;
+      }
+      else {
+        $this->tokenInfo = $this->moduleHandler->invokeAll('token_info');
+        $this->moduleHandler->alter('token_info', $this->tokenInfo);
+        $this->cache->set($cache_id, $this->tokenInfo);
+      }
     }
 
     return $this->tokenInfo;
@@ -305,5 +337,6 @@ public function setInfo(array $tokens) {
    */
   public function resetInfo() {
     $this->tokenInfo = NULL;
+    $this->cache->deleteAll();
   }
 }
diff --git a/core/tests/Drupal/Tests/Core/Utility/TokenUnitTest.php b/core/tests/Drupal/Tests/Core/Utility/TokenUnitTest.php
new file mode 100644
index 0000000..f2d0a2d
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Utility/TokenUnitTest.php
@@ -0,0 +1,118 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Utility\TokenUnitTest.
+ */
+
+namespace Drupal\Tests\Core\Utility;
+
+use Drupal\Core\Language\Language;
+use Drupal\Core\Utility\Token;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @coversDefaultClass \Drupal\Tests\Core\Utility\Token
+ */
+class TokenUnitTest extends UnitTestCase {
+
+  /**
+   * The cache used for testing.
+   *
+   * @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $cache;
+
+  /**
+   * The language manager used for testing.
+   *
+   * @var \Drupal\Core\Language\LanguageManager|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $languageManager;
+
+  /**
+   * The module handler service used for testing.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $moduleHandler;
+
+  /**
+   * The token service under test.
+   *
+   * @var \Drupal\Core\Utility\Token|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $token;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'description' => '',
+      'name' => '\Drupal\Tests\Core\Utility\Token unit test',
+      'group' => 'System',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    $this->cache = $this->getMock('\Drupal\Core\Cache\CacheBackendInterface');
+
+    $this->languageManager = $this->getMockBuilder('\Drupal\Core\Language\LanguageManager')
+      ->disableOriginalConstructor()
+      ->getMock();
+
+    $this->moduleHandler = $this->getMock('\Drupal\Core\Extension\ModuleHandlerInterface');
+
+    $this->token = new Token($this->moduleHandler, $this->cache, $this->languageManager);
+  }
+
+  /**
+   * @covers getInfo
+   */
+  public function testGetInfo() {
+    $token_info = array(
+      'types' => array(
+        'foo' => array(
+          'name' => $this->randomName(),
+        ),
+      ),
+    );
+
+    $language = $this->getMock('\Drupal\Core\Language\Language');
+    $language->id = $this->randomName();
+
+    $this->languageManager->expects($this->once())
+      ->method('getCurrentLanguage')
+      ->with(Language::TYPE_CONTENT)
+      ->will($this->returnValue($language));
+
+    // The persistent cache must only be hit once, after which the info is
+    // cached statically.
+    $this->cache->expects($this->once())
+      ->method('get');
+    $this->cache->expects($this->once())
+      ->method('set')
+      ->with('token_info:' . $language->id, $token_info);
+
+    $this->moduleHandler->expects($this->once())
+      ->method('invokeAll')
+      ->with('token_info')
+      ->will($this->returnValue($token_info));
+    $this->moduleHandler->expects($this->once())
+      ->method('alter')
+      ->with('token_info', $token_info);
+
+    // Get the information for the first time. The cache should be checked, the
+    // hooks invoked, and the info should be set to the cache should.
+    $this->token->getInfo();
+    // Get the information for the second time. The data must be returned from
+    // the static cache, so the persistent cache must not be accessed and the
+    // hooks must not be invoked.
+    $this->token->getInfo();
+  }
+
+}
