浏览代码

[HttpKernel] added a StoreInterface

Fabien Potencier 14 年之前
父节点
当前提交
2c4355460e

+ 3 - 3
src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php

@@ -67,17 +67,17 @@ class HttpCache implements HttpKernelInterface
      *                            (see RFC 5861).
      *
      * @param HttpKernelInterface $kernel An HttpKernelInterface instance
-     * @param Store               $store  A Store instance
+     * @param StoreInterface      $store  A Store instance
      * @param Esi                 $esi    An Esi instance
      * @param array                                             $options        An array of options
      */
-    public function __construct(HttpKernelInterface $kernel, Store $store, Esi $esi = null, array $options = array())
+    public function __construct(HttpKernelInterface $kernel, StoreInterface $store, Esi $esi = null, array $options = array())
     {
         $this->store = $store;
         $this->kernel = $kernel;
 
         // needed in case there is a fatal error because the backend is too slow to respond
-        register_shutdown_function(array($this->store, '__destruct'));
+        register_shutdown_function(array($this->store, 'cleanup'));
 
         $this->options = array_merge(array(
             'debug'                  => false,

+ 11 - 12
src/Symfony/Component/HttpKernel/HttpCache/Store.php

@@ -23,7 +23,7 @@ use Symfony\Component\HttpFoundation\HeaderBag;
  *
  * @author Fabien Potencier <fabien.potencier@symfony-project.com>
  */
-class Store
+class Store implements StoreInterface
 {
     protected $root;
     protected $keyCache;
@@ -44,7 +44,10 @@ class Store
         $this->locks = array();
     }
 
-    public function __destruct()
+    /**
+     * Cleanups storage.
+     */
+    public function cleanup()
     {
         // unlock everything
         foreach ($this->locks as $lock) {
@@ -234,7 +237,7 @@ class Store
      *
      * @return Boolean true if the the two environments match, false otherwise
      */
-    public function requestsMatch($vary, $env1, $env2)
+    protected function requestsMatch($vary, $env1, $env2)
     {
         if (empty($vary)) {
             return true;
@@ -261,7 +264,7 @@ class Store
      *
      * @return array An array of data associated with the key
      */
-    public function getMetadata($key)
+    protected function getMetadata($key)
     {
         if (false === $entries = $this->load($key)) {
             return array();
@@ -291,13 +294,11 @@ class Store
     /**
      * Loads data for the given key.
      *
-     * Don't use this method directly, use lookup() instead
-     *
      * @param string $key  The store key
      *
      * @return string The data associated with the key
      */
-    public function load($key)
+    protected function load($key)
     {
         $path = $this->getPath($key);
 
@@ -307,12 +308,10 @@ class Store
     /**
      * Save data for the given key.
      *
-     * Don't use this method directly, use write() instead
-     *
      * @param string $key  The store key
      * @param string $data The data to store
      */
-    public function save($key, $data)
+    protected function save($key, $data)
     {
         $path = $this->getPath($key);
         if (!is_dir(dirname($path)) && false === @mkdir(dirname($path), 0777, true)) {
@@ -337,7 +336,7 @@ class Store
         chmod($path, 0644);
     }
 
-    public function getPath($key)
+    protected function getPath($key)
     {
         return $this->root.DIRECTORY_SEPARATOR.substr($key, 0, 2).DIRECTORY_SEPARATOR.substr($key, 2, 2).DIRECTORY_SEPARATOR.substr($key, 4, 2).DIRECTORY_SEPARATOR.substr($key, 6);
     }
@@ -349,7 +348,7 @@ class Store
      *
      * @return string A key for the given Request
      */
-    public function getCacheKey(Request $request)
+    protected function getCacheKey(Request $request)
     {
         if (isset($this->keyCache[$request])) {
             return $this->keyCache[$request];

+ 86 - 0
src/Symfony/Component/HttpKernel/HttpCache/StoreInterface.php

@@ -0,0 +1,86 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * This code is partially based on the Rack-Cache library by Ryan Tomayko,
+ * which is released under the MIT license.
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpKernel\HttpCache;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpFoundation\HeaderBag;
+
+/**
+ *
+ *
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ */
+interface StoreInterface
+{
+    /**
+     * Locates a cached Response for the Request provided.
+     *
+     * @param Request $request A Request instance
+     *
+     * @return Response|null A Response instance, or null if no cache entry was found
+     */
+    function lookup(Request $request);
+
+    /**
+     * Writes a cache entry to the store for the given Request and Response.
+     *
+     * Existing entries are read and any that match the response are removed. This
+     * method calls write with the new list of cache entries.
+     *
+     * @param Request  $request  A Request instance
+     * @param Response $response A Response instance
+     *
+     * @return string The key under which the response is stored
+     */
+    function write(Request $request, Response $response);
+
+    /**
+     * Invalidates all cache entries that match the request.
+     *
+     * @param Request $request A Request instance
+     */
+    function invalidate(Request $request);
+
+    /**
+     * Locks the cache for a given Request.
+     *
+     * @param Request $request A Request instance
+     *
+     * @return Boolean|string true if the lock is acquired, the path to the current lock otherwise
+     */
+    function lock(Request $request);
+
+    /**
+     * Releases the lock for the given Request.
+     *
+     * @param Request $request A Request instance
+     */
+    function unlock(Request $request);
+
+    /**
+     * Purges data for the given URL.
+     *
+     * @param string $url A URL
+     *
+     * @return Boolean true if the URL exists and has been purged, false otherwise
+     */
+    function purge($url);
+
+    /**
+     * Cleanups storage.
+     */
+    function cleanup();
+}

+ 1 - 0
src/Symfony/Component/HttpKernel/Resources/bin/packager.php

@@ -60,6 +60,7 @@ ClassCollectionLoader::load(array(
     'Symfony\\Component\\HttpKernel\\Kernel',
     'Symfony\\Component\\HttpKernel\\HttpKernelInterface',
     'Symfony\\Component\\HttpKernel\\HttpCache\\HttpCache',
+    'Symfony\\Component\\HttpKernel\\HttpCache\\StoreInterface',
     'Symfony\\Component\\HttpKernel\\HttpCache\\Store',
     'Symfony\\Component\\HttpKernel\\HttpCache\\Esi',
 

+ 26 - 10
src/Symfony/Component/HttpKernel/bootstrap_cache.php

@@ -402,11 +402,11 @@ class HttpCache implements HttpKernelInterface
     protected $traces;
     protected $store;
     protected $esi;
-    public function __construct(HttpKernelInterface $kernel, Store $store, Esi $esi = null, array $options = array())
+    public function __construct(HttpKernelInterface $kernel, StoreInterface $store, Esi $esi = null, array $options = array())
     {
         $this->store = $store;
         $this->kernel = $kernel;
-                register_shutdown_function(array($this->store, '__destruct'));
+                register_shutdown_function(array($this->store, 'cleanup'));
         $this->options = array_merge(array(
             'debug'                  => false,
             'default_ttl'            => 0,
@@ -688,7 +688,23 @@ namespace Symfony\Component\HttpKernel\HttpCache
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpFoundation\HeaderBag;
-class Store
+interface StoreInterface
+{
+    function lookup(Request $request);
+    function write(Request $request, Response $response);
+    function invalidate(Request $request);
+    function lock(Request $request);
+    function unlock(Request $request);
+    function purge($url);
+    function cleanup();
+}
+}
+namespace Symfony\Component\HttpKernel\HttpCache
+{
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpFoundation\HeaderBag;
+class Store implements StoreInterface
 {
     protected $root;
     protected $keyCache;
@@ -702,7 +718,7 @@ class Store
         $this->keyCache = new \SplObjectStorage();
         $this->locks = array();
     }
-    public function __destruct()
+    public function cleanup()
     {
                 foreach ($this->locks as $lock) {
             @unlink($lock);
@@ -810,7 +826,7 @@ class Store
             }
         }
     }
-    public function requestsMatch($vary, $env1, $env2)
+    protected function requestsMatch($vary, $env1, $env2)
     {
         if (empty($vary)) {
             return true;
@@ -825,7 +841,7 @@ class Store
         }
         return true;
     }
-    public function getMetadata($key)
+    protected function getMetadata($key)
     {
         if (false === $entries = $this->load($key)) {
             return array();
@@ -840,12 +856,12 @@ class Store
         }
         return false;
     }
-    public function load($key)
+    protected function load($key)
     {
         $path = $this->getPath($key);
         return file_exists($path) ? file_get_contents($path) : false;
     }
-    public function save($key, $data)
+    protected function save($key, $data)
     {
         $path = $this->getPath($key);
         if (!is_dir(dirname($path)) && false === @mkdir(dirname($path), 0777, true)) {
@@ -865,11 +881,11 @@ class Store
         }
         chmod($path, 0644);
     }
-    public function getPath($key)
+    protected function getPath($key)
     {
         return $this->root.DIRECTORY_SEPARATOR.substr($key, 0, 2).DIRECTORY_SEPARATOR.substr($key, 2, 2).DIRECTORY_SEPARATOR.substr($key, 4, 2).DIRECTORY_SEPARATOR.substr($key, 6);
     }
-    public function getCacheKey(Request $request)
+    protected function getCacheKey(Request $request)
     {
         if (isset($this->keyCache[$request])) {
             return $this->keyCache[$request];

+ 4 - 1
tests/Symfony/Tests/Component/HttpKernel/HttpCache/HttpCacheTest.php

@@ -593,7 +593,10 @@ class HttpCacheTest extends HttpCacheTestCase
         $tmp = unserialize($values[0]);
         $time = \DateTime::createFromFormat('U', time());
         $tmp[0][1]['expires'] = $time->format(DATE_RFC2822);
-        $this->store->save('md'.sha1('http://localhost/'), serialize($tmp));
+        $r = new \ReflectionObject($this->store);
+        $m = $r->getMethod('save');
+        $m->setAccessible(true);
+        $m->invoke($this->store, 'md'.sha1('http://localhost/'), serialize($tmp));
 
         // build subsequent request; should be found but miss due to freshness
         $this->request('GET', '/');

+ 40 - 16
tests/Symfony/Tests/Component/HttpKernel/HttpCache/StoreTest.php

@@ -42,17 +42,17 @@ class StoreTest extends \PHPUnit_Framework_TestCase
 
     public function testReadsAnEmptyArrayWithReadWhenNothingCachedAtKey()
     {
-        $this->assertEmpty($this->store->getMetadata('/nothing'));
+        $this->assertEmpty($this->getStoreMetadata('/nothing'));
     }
 
     public function testRemovesEntriesForKeyWithPurge()
     {
         $request = Request::create('/foo');
         $this->store->write($request, new Response('foo'));
-        $this->assertNotEmpty($this->store->getMetadata($this->store->getCacheKey($request)));
+        $this->assertNotEmpty($this->getStoreMetadata($request));
 
         $this->assertTrue($this->store->purge('/foo'));
-        $this->assertEmpty($this->store->getMetadata($this->store->getCacheKey($request)));
+        $this->assertEmpty($this->getStoreMetadata($request));
 
         $this->assertFalse($this->store->purge('/bar'));
     }
@@ -61,13 +61,13 @@ class StoreTest extends \PHPUnit_Framework_TestCase
     {
         $cacheKey = $this->storeSimpleEntry();
 
-        $this->assertNotEmpty($this->store->getMetadata($cacheKey));
+        $this->assertNotEmpty($this->getStoreMetadata($cacheKey));
     }
 
     public function testSetsTheXContentDigestResponseHeaderBeforeStoring()
     {
         $cacheKey = $this->storeSimpleEntry();
-        $entries = $this->store->getMetadata($cacheKey);
+        $entries = $this->getStoreMetadata($cacheKey);
         list ($req, $res) = $entries[0];
 
         $this->assertEquals('ena94a8fe5ccb19ba61c4c0873d391e987982fbbd3', $res['x-content-digest'][0]);
@@ -103,7 +103,7 @@ class StoreTest extends \PHPUnit_Framework_TestCase
     {
         $this->storeSimpleEntry();
         $this->assertNotNull($this->response->headers->get('X-Content-Digest'));
-        $path = $this->store->getPath($this->response->headers->get('X-Content-Digest'));
+        $path = $this->getStorePath($this->response->headers->get('X-Content-Digest'));
         @unlink($path);
         $this->assertNull($this->store->lookup($this->request));
     }
@@ -113,14 +113,14 @@ class StoreTest extends \PHPUnit_Framework_TestCase
         $this->storeSimpleEntry();
         $response = $this->store->lookup($this->request);
 
-        $this->assertEquals($response->headers->all(), array_merge(array('content-length' => 4, 'x-body-file' => array($this->store->getPath($response->headers->get('X-Content-Digest')))), $this->response->headers->all()));
+        $this->assertEquals($response->headers->all(), array_merge(array('content-length' => 4, 'x-body-file' => array($this->getStorePath($response->headers->get('X-Content-Digest')))), $this->response->headers->all()));
     }
 
     public function testRestoresResponseContentFromEntityStoreWithLookup()
     {
         $this->storeSimpleEntry();
         $response = $this->store->lookup($this->request);
-        $this->assertEquals($this->store->getPath('en'.sha1('test')), $response->getContent());
+        $this->assertEquals($this->getStorePath('en'.sha1('test')), $response->getContent());
     }
 
     public function testInvalidatesMetaAndEntityStoreEntriesWithInvalidate()
@@ -163,11 +163,11 @@ class StoreTest extends \PHPUnit_Framework_TestCase
         $res3 = new Response('test 3', 200, array('Vary' => 'Foo Bar'));
         $this->store->write($req3, $res3);
 
-        $this->assertEquals($this->store->getPath('en'.sha1('test 3')), $this->store->lookup($req3)->getContent());
-        $this->assertEquals($this->store->getPath('en'.sha1('test 2')), $this->store->lookup($req2)->getContent());
-        $this->assertEquals($this->store->getPath('en'.sha1('test 1')), $this->store->lookup($req1)->getContent());
+        $this->assertEquals($this->getStorePath('en'.sha1('test 3')), $this->store->lookup($req3)->getContent());
+        $this->assertEquals($this->getStorePath('en'.sha1('test 2')), $this->store->lookup($req2)->getContent());
+        $this->assertEquals($this->getStorePath('en'.sha1('test 1')), $this->store->lookup($req1)->getContent());
 
-        $this->assertEquals(3, count($this->store->getMetadata($key)));
+        $this->assertEquals(3, count($this->getStoreMetadata($key)));
     }
 
     public function testOverwritesNonVaryingResponseWithStore()
@@ -175,19 +175,19 @@ class StoreTest extends \PHPUnit_Framework_TestCase
         $req1 = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar'));
         $res1 = new Response('test 1', 200, array('Vary' => 'Foo Bar'));
         $key = $this->store->write($req1, $res1);
-        $this->assertEquals($this->store->getPath('en'.sha1('test 1')), $this->store->lookup($req1)->getContent());
+        $this->assertEquals($this->getStorePath('en'.sha1('test 1')), $this->store->lookup($req1)->getContent());
 
         $req2 = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Bling', 'HTTP_BAR' => 'Bam'));
         $res2 = new Response('test 2', 200, array('Vary' => 'Foo Bar'));
         $this->store->write($req2, $res2);
-        $this->assertEquals($this->store->getPath('en'.sha1('test 2')), $this->store->lookup($req2)->getContent());
+        $this->assertEquals($this->getStorePath('en'.sha1('test 2')), $this->store->lookup($req2)->getContent());
 
         $req3 = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar'));
         $res3 = new Response('test 3', 200, array('Vary' => 'Foo Bar'));
         $key = $this->store->write($req3, $res3);
-        $this->assertEquals($this->store->getPath('en'.sha1('test 3')), $this->store->lookup($req3)->getContent());
+        $this->assertEquals($this->getStorePath('en'.sha1('test 3')), $this->store->lookup($req3)->getContent());
 
-        $this->assertEquals(2, count($this->store->getMetadata($key)));
+        $this->assertEquals(2, count($this->getStoreMetadata($key)));
     }
 
     protected function storeSimpleEntry($path = null, $headers = array())
@@ -201,4 +201,28 @@ class StoreTest extends \PHPUnit_Framework_TestCase
 
         return $this->store->write($this->request, $this->response);
     }
+
+    protected function getStoreMetadata($key)
+    {
+        $r = new \ReflectionObject($this->store);
+        $m = $r->getMethod('getMetadata');
+        $m->setAccessible(true);
+
+        if ($key instanceof Request) {
+            $m1 = $r->getMethod('getCacheKey');
+            $m1->setAccessible(true);
+            $key = $m1->invoke($this->store, $key);
+        }
+
+        return $m->invoke($this->store, $key);
+    }
+
+    protected function getStorePath($key)
+    {
+        $r = new \ReflectionObject($this->store);
+        $m = $r->getMethod('getPath');
+        $m->setAccessible(true);
+
+        return $m->invoke($this->store, $key);
+    }
 }