Browse Source

ZF-9979 & ZF-9422: allow function cache to use all types of callbacks

git-svn-id: http://framework.zend.com/svn/framework/standard/trunk@22503 44c647ce-9c0f-0410-b52a-842ac1e357ba
mabe 15 years ago
parent
commit
fee3b300f1

+ 7 - 5
library/Zend/Cache/Backend/Test.php

@@ -103,10 +103,13 @@ class Zend_Cache_Backend_Test extends Zend_Cache_Backend implements Zend_Cache_B
     public function load($id, $doNotTestCacheValidity = false)
     {
         $this->_addLog('get', array($id, $doNotTestCacheValidity));
+
         if ( $id == 'false'
           || $id == 'd8523b3ee441006261eeffa5c3d3a0a7'
           || $id == 'e83249ea22178277d5befc2c5e2e9ace'
-          || $id == '40f649b94977c0a6e76902e2a0b43587')
+          || $id == '40f649b94977c0a6e76902e2a0b43587'
+          || $id == '88161989b73a4cbfd0b701c446115a99'
+          || $id == '205fc79cba24f0f0018eb92c7c8b3ba4')
         {
             return false;
         }
@@ -116,10 +119,9 @@ class Zend_Cache_Backend_Test extends Zend_Cache_Backend implements Zend_Cache_B
         if ($id=='serialized2') {
             return serialize(array('headers' => array(), 'data' => 'foo'));
         }
-        if (($id=='71769f39054f75894288e397df04e445') or ($id=='615d222619fb20b527168340cebd0578')) {
-            return serialize(array('foo', 'bar'));
-        }
-        if (($id=='8a02d218a5165c467e7a5747cc6bd4b6') or ($id=='648aca1366211d17cbf48e65dc570bee')) {
+        if ( $id == '71769f39054f75894288e397df04e445' || $id == '615d222619fb20b527168340cebd0578'
+          || $id == '8a02d218a5165c467e7a5747cc6bd4b6' || $id == '648aca1366211d17cbf48e65dc570bee'
+          || $id == '4a923ef02d7f997ca14d56dfeae25ea7') {
             return serialize(array('foo', 'bar'));
         }
         return 'foo';

+ 55 - 17
library/Zend/Cache/Frontend/Function.php

@@ -72,25 +72,29 @@ class Zend_Cache_Frontend_Function extends Zend_Cache_Core
     /**
      * Main method : call the specified function or get the result from cache
      *
-     * @param  string $name             Function name
-     * @param  array  $parameters       Function parameters
-     * @param  array  $tags             Cache tags
-     * @param  int    $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime)
-     * @param  int   $priority         integer between 0 (very low priority) and 10 (maximum priority) used by some particular backends
+     * @param  callback $callback         A valid callback
+     * @param  array    $parameters       Function parameters
+     * @param  array    $tags             Cache tags
+     * @param  int      $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime)
+     * @param  int      $priority         integer between 0 (very low priority) and 10 (maximum priority) used by some particular backends
      * @return mixed Result
      */
-    public function call($name, $parameters = array(), $tags = array(), $specificLifetime = false, $priority = 8)
+    public function call($callback, array $parameters = array(), $tags = array(), $specificLifetime = false, $priority = 8)
     {
+        if (!is_callable($callback, true, $name)) {
+            Zend_Cache::throwException('Invalid callback');
+        }
+
         $cacheBool1 = $this->_specificOptions['cache_by_default'];
         $cacheBool2 = in_array($name, $this->_specificOptions['cached_functions']);
         $cacheBool3 = in_array($name, $this->_specificOptions['non_cached_functions']);
         $cache = (($cacheBool1 || $cacheBool2) && (!$cacheBool3));
         if (!$cache) {
-            // We do not have not cache
-            return call_user_func_array($name, $parameters);
+            // Caching of this callback is disabled
+            return call_user_func_array($callback, $parameters);
         }
 
-        $id = $this->_makeId($name, $parameters);
+        $id = $this->_makeId($callback, $parameters);
         if ( ($rs = $this->load($id)) && isset($rs[0], $rs[1])) {
             // A cache is available
             $output = $rs[0];
@@ -99,7 +103,7 @@ class Zend_Cache_Frontend_Function extends Zend_Cache_Core
             // A cache is not available (or not valid for this frontend)
             ob_start();
             ob_implicit_flush(false);
-            $return = call_user_func_array($name, $parameters);
+            $return = call_user_func_array($callback, $parameters);
             $output = ob_get_contents();
             ob_end_clean();
             $data = array($output, $return);
@@ -113,20 +117,54 @@ class Zend_Cache_Frontend_Function extends Zend_Cache_Core
     /**
      * Make a cache id from the function name and parameters
      *
-     * @param  string $name       Function name
+     * @param  callback $callback A valid callback
      * @param  array  $parameters Function parameters
      * @throws Zend_Cache_Exception
      * @return string Cache id
      */
-    private function _makeId($name, $parameters)
+    private function _makeId($callback, array $args)
     {
-        if (!is_string($name)) {
-            Zend_Cache::throwException('Incorrect function name');
+        if (!is_callable($callback, true, $name)) {
+            Zend_Cache::throwException('Invalid callback');
+        }
+
+        // functions, methods and classnames are case-insensitive
+        $name = strtolower($name);
+
+        // generate a unique id for object callbacks
+        if (is_object($callback)) { // Closures & __invoke
+            $object = $callback;
+        } elseif (isset($callback[0])) { // array($object, 'method')
+            $object = $callback[0];
+        }
+        if (isset($object)) {
+            try {
+                $tmp = @serialize($callback);
+            } catch (Exception $e) {
+                Zend_Cache::throwException($e->getMessage());
+            }
+            if (!$tmp) {
+                $lastErr = error_get_last();
+                Zend_Cache::throwException("Can't serialize callback object to generate id: {$lastErr['message']}");
+            }
+            $name.= '__' . $tmp;
         }
-        if (!is_array($parameters)) {
-            Zend_Cache::throwException('parameters argument must be an array');
+
+        // generate a unique id for arguments
+        $argsStr = '';
+        if ($args) {
+            try {
+                $argsStr = @serialize(array_values($args));
+            } catch (Exception $e) {
+                Zend_Cache::throwException($e->getMessage());
+            }
+            if (!$argsStr) {
+                $lastErr = error_get_last();
+                throw Zend_Cache::throwException("Can't serialize arguments to generate id: {$lastErr['message']}");
+            }
         }
-        return md5($name . serialize($parameters));
+
+        return md5($name . $argsStr);
     }
 
 }

+ 59 - 8
tests/Zend/Cache/FunctionFrontendTest.php

@@ -37,6 +37,21 @@ function foobar($param1, $param2) {
     return "foobar_return($param1, $param2)";
 }
 
+class fooclass {
+    private static $_instanceCounter = 0;
+
+    public function __construct()
+    {
+        self::$_instanceCounter++;
+    }
+
+    public function foobar($param1, $param2)
+    {
+        return foobar($param1, $param2)
+               . ':' . self::$_instanceCounter;
+    }
+}
+
 /**
  * @category   Zend
  * @package    Zend_Cache
@@ -156,24 +171,60 @@ class Zend_Cache_FunctionFrontendTest extends PHPUnit_Framework_TestCase {
         $this->assertEquals('foobar_output(param1, param2)', $data);
     }
 
-    public function testCallWithABadSyntax1()
+    public function testCallObjectMethodCorrectCall1()
     {
-        try {
-            $this->_instance->call(1, array());
-        } catch (Zend_Cache_Exception $e) {
-            return;
+        // cacheByDefault = true
+        // nonCachedFunctions = array('foobar')
+        $this->_instance->setOption('cache_by_default', true);
+        $this->_instance->setOption('non_cached_functions', array('foobar'));
+        ob_start();
+        ob_implicit_flush(false);
+        $object = new fooclass();
+        $return = $this->_instance->call(array($object, 'foobar'), array('param1', 'param2'));
+        $data = ob_get_contents();
+        ob_end_clean();
+        ob_implicit_flush(true);
+        $this->assertEquals('foobar_return(param1, param2):1', $return);
+        $this->assertEquals('foobar_output(param1, param2)', $data);
+    }
+
+    public function testCallObjectMethodCorrectCall2()
+    {
+        // cacheByDefault = true
+        // nonCachedFunctions = array('foobar')
+        $this->_instance->setOption('cache_by_default', true);
+        $this->_instance->setOption('non_cached_functions', array('foobar'));
+        ob_start();
+        ob_implicit_flush(false);
+        $object = new fooclass();
+        $return = $this->_instance->call(array($object, 'foobar'), array('param1', 'param2'));
+        $data = ob_get_contents();
+        ob_end_clean();
+        ob_implicit_flush(true);
+        $this->assertEquals('foobar_return(param1, param2):2', $return);
+        $this->assertEquals('foobar_output(param1, param2)', $data);
+    }
+
+    public function testCallClosureThrowsException()
+    {
+        if (version_compare(PHP_VERSION, '5.3', '<')) {
+            $this->markTestSkipped();
         }
-        $this->fail('Zend_Cache_Exception was expected but not thrown');
+
+        $this->setExpectedException('Zend_Cache_Exception');
+        eval('$closure = function () {};'); // no parse error on php < 5.3
+        $this->_instance->call($closure);
     }
 
-    public function testCallWithABadSyntax2()
+    public function testCallWithABadSyntax1()
     {
         try {
-            $this->_instance->call('foo', 1);
+            $this->_instance->call(1, array());
         } catch (Zend_Cache_Exception $e) {
             return;
         }
         $this->fail('Zend_Cache_Exception was expected but not thrown');
     }
+
 }