diff --git a/core/lib/Drupal/Core/Cache/CacheBackendInterface.php b/core/lib/Drupal/Core/Cache/CacheBackendInterface.php
index 87b0a1b..b73f377 100644
--- a/core/lib/Drupal/Core/Cache/CacheBackendInterface.php
+++ b/core/lib/Drupal/Core/Cache/CacheBackendInterface.php
@@ -120,6 +120,7 @@ public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = array
    *     ),
    *   );
    *   @endcode
+   *   Backend implementations must keep the order of items.
    */
   public function setMultiple(array $items);
 
diff --git a/core/lib/Drupal/Core/PhpStorage/CacheStorage.php b/core/lib/Drupal/Core/PhpStorage/CacheStorage.php
new file mode 100644
index 0000000..ccd38dc
--- /dev/null
+++ b/core/lib/Drupal/Core/PhpStorage/CacheStorage.php
@@ -0,0 +1,198 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\PhpStorage\CacheStorage.
+ */
+
+namespace Drupal\Core\PhpStorage;
+
+use Drupal\Component\PhpStorage\PhpStorageInterface;
+use Drupal\Core\Cache\DatabaseBackend;
+use Drupal\Core\Cache\DatabaseCacheTagsChecksum;
+use Drupal\Core\Database\Database;
+use Drupal\Core\StreamWrapper\StreamWrapperForCacheStorage;
+
+/**
+ * This class stores PHP classes in a cache storage keeping it opcacheable.
+ */
+class CacheStorage implements PhpStorageInterface {
+
+  /**
+   * The backend.
+   *
+   * @var \Drupal\Core\Cache\CacheBackendInterface
+   */
+  protected $cacheBackend;
+
+  /**
+   * The configuration array passed to the constructor.
+   *
+   * @var array $configuration
+   */
+  protected $configuration;
+
+  /**
+   * @param array $configuration
+   *   The configuration containing bin, secret and an optional callable
+   *   cache_backend_factory.
+   *
+   * @see \Drupal\Core\PhpStorage\PhpStorageFactory::get()
+   */
+  public function __construct(array $configuration) {
+    $this->configuration = $configuration;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function exists($name) {
+    $key = $this->getKeyFromFilename($name);
+    $cids = [$key, "$key:mtime"];
+    $this->cacheBackend()->getMultiple($cids);
+    return !$cids;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function load($name) {
+    $key = $this->getKeyFromFilename($name);
+    $cached = $this->cacheBackend()->get("$key:mtime");
+    if (!$cached) {
+      return FALSE;
+    }
+    // Load the data from cache.
+    // Hook in the fake phar wrapper. Opcode included in PHP 5.5 hardwires file
+    // and phar as the only two stream wrappers which can be opcode cached.
+    // The file protocol is used to read local files and will be triggered
+    // multiple times by the classloader as the container class is loaded.
+    // So for better performance use the phar protocol.
+    stream_wrapper_unregister('phar');
+    stream_wrapper_register('phar', 'Drupal\Core\StreamWrapper\StreamWrapperForCacheStorage');
+    StreamWrapperForCacheStorage::init($this, $cached->data);
+    $return = (include "phar://$name") !== FALSE;
+    // Restore the system wrapper.
+    stream_wrapper_restore('phar');
+    return $return;
+  }
+
+  /**
+   * @param $name
+   * @return bool|resource
+   */
+  public function open($name) {
+    $key = $this->getKeyFromFilename(substr($name, 7));
+    if (!$cached = $this->cacheBackend()->get($key)) {
+      return FALSE;
+    }
+    // Copy it into a file in memory.
+    if (!$handle = fopen('php://memory', 'rwb')) {
+      return FALSE;
+    }
+    if (fwrite($handle, $cached->data) === FALSE || fseek($handle, 0) === -1) {
+      fclose($handle);
+      return FALSE;
+    }
+    return $handle;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function save($name, $code) {
+    $key = $this->getKeyFromFilename($name);
+    // We do not need a real mtime, we just need a timestamp that changes when
+    // the code changes.
+    $hash = hash('sha256', $code);
+    $mtime = hexdec(substr($hash, 0, 7));
+    $this->cacheBackend()->setMultiple([
+      $key => ['data' => $code],
+      "$key:mtime" => ['data' => $mtime],
+    ]);
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function writeable() {
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function delete($name) {
+    $return = $this->exists($name);
+    $key = $this->getKeyFromFilename($name);
+    // Delete nonetheless because between exists and delete the entry might've
+    // been written.
+    $this->cacheBackend()->delete($key);
+    return $return;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function deleteAll() {
+    $this->cacheBackend()->deleteAll();
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFullPath($name) {
+    return FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function listAll() {
+    // Only PhpBackEnd::invalidateAll() uses this method and that's not
+    // compatible anyways since it relies on getFullPath().
+    throw new \BadMethodCallException('CacheStorage::listall() is not implemented.');
+  }
+
+  /**
+   * @return \Drupal\Core\Cache\CacheBackendInterface
+   */
+  protected function cacheBackend() {
+    if (!isset($this->cacheBackend)) {
+      if (isset($this->configuration['cache_backend_factory'])) {
+        $this->cacheBackend = call_user_func($this->configuration['cache_backend_factory'], $this->configuration);
+      }
+      else {
+        $this->cacheBackend = static::getDatabaseBackend($this->configuration);
+      }
+    }
+    return $this->cacheBackend;
+  }
+
+  /**
+   * Construct a database cache backend.
+   */
+  protected static function getDatabaseBackend($configuration) {
+    $connection = Database::getConnection();
+    return new DatabaseBackend($connection, new DatabaseCacheTagsChecksum($connection), 'php_' . $configuration['bin']);
+  }
+
+  /**
+   * Return a secret key based on the filename.
+   *
+   * By using a secret key, a SQL injection does not lead immediately to
+   * arbitrary PHP inclusion.
+   *
+   * @param string $filename
+   *   The filename.
+   *
+   * @return string
+   *   The secret hash.
+   */
+  protected function getKeyFromFilename($filename) {
+    return hash_hmac('sha256', $filename, $this->configuration['secret']);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/StreamWrapper/StreamWrapperForCacheStorage.php b/core/lib/Drupal/Core/StreamWrapper/StreamWrapperForCacheStorage.php
new file mode 100644
index 0000000..ba8a743
--- /dev/null
+++ b/core/lib/Drupal/Core/StreamWrapper/StreamWrapperForCacheStorage.php
@@ -0,0 +1,113 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\StreamWrapper\StreamWrapperForCacheStorage.
+ */
+
+namespace Drupal\Core\StreamWrapper;
+
+use Drupal\Core\PhpStorage\CacheStorage;
+
+/**
+ * The stream wrapper for \Drupal\Core\PhpStorage\CacheStorage .
+ *
+ * This class is not usable as a generic stream wrapper, it is specifically
+ * written to work with \Drupal\Core\PhpStorage\CacheStorage and allows us
+ * to manually set the mtime of the "file" the stream is wrapping.
+ */
+class StreamWrapperForCacheStorage {
+
+  /**
+   * @var \Drupal\Core\PhpStorage\CacheStorage
+   */
+  protected static $storage;
+
+  /**
+   * The (fake) modified timestamp of the file this class wraps.
+   *
+   * @var int
+   */
+  protected static $mtime;
+
+  /**
+   * Stream context resource set by PHP and ignored by this class.
+   *
+   * @var resource
+   */
+  public $context = NULL;
+
+  /**
+   * The file handle of the in-memory stream.
+   *
+   * @var resource
+   */
+  protected static $handle = NULL;
+
+  /**
+   * Initialize the wrapper
+   *
+   * @param $storage
+   *   The corresponding \Drupal\Core\PhpStorage\CacheStorage instance. This
+   *   wil be used by stream_open().
+   * @param $mtime
+   *   The (fake) modified timestamp of the wrapped file.
+   */
+  public static function init(CacheStorage $storage, $mtime) {
+    static::$storage = $storage;
+    static::$mtime = $mtime;
+  }
+
+  public function stream_open($path) {
+    static::$handle = static::$storage->open($path);
+    return (bool) static::$handle;
+  }
+
+  public function stream_close() {
+    return fclose(static::$handle);
+  }
+
+  public function stream_eof() {
+    return feof(static::$handle);
+  }
+
+  public function stream_read($count) {
+    return fread(static::$handle, $count);
+  }
+
+  public function stream_flush() {
+    // This is called on every file close even if there is nothing to flush
+    // and we do not write anything so we do not actually need to flush
+    // anything.
+    return TRUE;
+  }
+
+  public function stream_stat() {
+    // When the file is not yet opcode cached, the mtime is read through
+    // stream_stat() during file compile. The stat() results are not dependent
+    // on the position in the file and we know which file we are working on so
+    // using $handle is not necessary and just calling url_stat() to return the
+    // fake mtime is both correct and necessary.
+    return static::url_stat();
+  }
+
+  public function url_stat() {
+    $return = [
+      'dev' => 0,
+      'ino' => 0,
+      'mode' => 0,
+      'nlink' => 0,
+      'uid' => 0,
+      'gid' => 0,
+      'rdev' => 0,
+      'size' => 0,
+      'atime' => 0,
+      'mtime' => static::$mtime,
+      'ctime' => 0,
+      'blksize' => -1,
+      'blocks' => -1,
+    ];
+    return $return + array_values($return);
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Component/PhpStorage/PhpStorageTestBase.php b/core/tests/Drupal/Tests/Component/PhpStorage/PhpStorageTestBase.php
index 53492d8..5493f02 100644
--- a/core/tests/Drupal/Tests/Component/PhpStorage/PhpStorageTestBase.php
+++ b/core/tests/Drupal/Tests/Component/PhpStorage/PhpStorageTestBase.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Tests\Component\PhpStorage;
 
+use Drupal\Component\PhpStorage\PhpStorageInterface;
 use Drupal\Tests\UnitTestCase;
 use org\bovigo\vfs\vfsStream;
 
@@ -48,6 +49,7 @@ public function assertCRUD($php) {
     $this->assertTrue($success, 'Saved php file');
     $php->load($name);
     $this->assertTrue($GLOBALS[$random], 'File saved correctly with correct value');
+    $this->additionalAssertCRUD($php, $name);
 
     // If the file was successfully loaded, it must also exist, but ensure the
     // exists() method returns that correctly.
@@ -62,4 +64,16 @@ public function assertCRUD($php) {
     $this->assertFalse($php->delete($name), 'Delete fails on missing file');
   }
 
+  /**
+   * Additional asserts to be run.
+   *
+   * @param \Drupal\Component\PhpStorage\PhpStorageInterface $php
+   *   The PHP storage object.
+   * @param string $name
+   *   The name of an object. It should exist in the storage.
+   */
+  protected function additionalAssertCRUD(PhpStorageInterface $php, $name) {
+
+  }
+
 }
diff --git a/core/tests/Drupal/Tests/Core/PhpStorage/CacheStorageTest.php b/core/tests/Drupal/Tests/Core/PhpStorage/CacheStorageTest.php
new file mode 100644
index 0000000..d7a36c8
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/PhpStorage/CacheStorageTest.php
@@ -0,0 +1,95 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\PhpStorage\CacheStorageTest.
+ */
+
+namespace Drupal\Tests\Core\PhpStorage;
+
+use Drupal\Component\PhpStorage\PhpStorageInterface;
+use Drupal\Core\PhpStorage\CacheStorage;
+use Drupal\Tests\Component\PhpStorage\PhpStorageTestBase;
+
+/**
+ * @coversDefaultClass \Drupal\Core\PhpStorage\CacheStorage
+ * @group Drupal
+ * @group PhpStorage
+ */
+class CacheStorageTest extends PhpStorageTestBase {
+
+  /**
+   * The contents of the cache backend.
+   *
+   * @var array
+   */
+  protected $cache;
+
+  /**
+   * Tests basic load/save/delete operations.
+   *
+   * @covers ::load
+   * @covers ::save
+   * @covers ::exists
+   * @covers ::delete
+   */
+  public function testCRUD() {
+    $secret = $this->randomMachineName();
+    $storage = new CacheStorage([
+      'secret' => $secret,
+      'cache_backend_factory' => function (array $configuration) {
+        return $this->getBackend($configuration['bin']);
+      },
+      'bin' => 'test'
+    ]);
+    $this->assertCRUD($storage);
+  }
+
+  /**
+   * @param $bin
+   * @return \PHPUnit_Framework_MockObject_MockObject|\Drupal\Core\Cache\CacheBackendInterface
+   */
+  public function getBackend($bin) {
+    $cache_backend = $this->getMock('Drupal\Core\Cache\CacheBackendInterface');
+    // This is the save() call.
+    $cache_backend->expects($this->once())
+      ->method('setMultiple')
+      ->willReturnCallback(function ($data) {
+          $this->cache = $data;
+      });
+    // mtime wil be retrieved on both loads. If opcache.enable_cli is on then
+    // the file itself is loaded only once.
+    $opcache_cli_enabed = ini_get('opcache.enable_cli');
+    $cache_backend->expects($this->exactly(4 - $opcache_cli_enabed))
+      ->method('get')
+      ->willReturnCallback(function ($cid) {
+        return (object) $this->cache[$cid];
+      });
+    // Two direct exists() calls and two exists() calls from delete.
+    $cache_backend->expects($this->exactly(4))
+      ->method('getMultiple')
+      ->willReturnCallback(function (&$cids) {
+        $return = array_intersect_key($this->cache, array_flip($cids));
+        $cids = array_diff_key($cids, array_keys($this->cache));
+        return $return;
+      });
+    // Two delete() cals.
+    $cache_backend->expects($this->exactly(2))
+      ->method('delete')
+      ->willReturnCallback(function ($cid) {
+        unset($this->cache[$cid]);
+      });
+    // Nothing else happens.
+    $cache_backend->expects($this->exactly(11 - $opcache_cli_enabed))
+      ->method($this->anything());
+    return $cache_backend;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function additionalAssertCRUD(PhpStorageInterface $php, $name) {
+    $php->load($name);
+  }
+
+}
