I'm trying to write a memoization function, and I just realized this solution does not work when the callback is not a simple string. For example, PHP can accept a callback in the form array($this,'memberFunc')
, which is not amenable to serialization.
Then I realized that we don't really want to hash/serialize the whole callback function/object anyway, we just need a unique ID for it so we can check for reference equality. I thought spl_object_hash
would do the trick, but it doesn't work on arrays.
Is there another way to generate a unique reference ID for a callable?
You can always cast to object for hashing purposes:
<?php
class Foo{
public function __construct(){
$foo = array($this,'memberFunc');
var_dump( spl_object_hash((object)$foo) );
var_dump( spl_object_hash((object)$foo) );
}
}
new Foo;
string(32) "00000000532ba9fd000000000cc9b0a5"
string(32) "00000000532ba9fd000000000cc9b0a5"
I wrote this function to get a hash for callables specifically:
/**
* @param callable $func
* @return string Unique string identifier for callable instance
* @throws Exception
*/
private static function callable_hash($func) {
if(!is_callable($func)) throw new Exception("Function must be a callable");
if(is_string($func)) {
return $func;
} elseif(is_array($func)) {
if(count($func) !== 2) throw new Exception("Array-callables must have exactly 2 elements");
if(!is_string($func[1])) throw new Exception("Second element of array-callable must be a string function name");
if(is_object($func[0])) return spl_object_hash($func[0]).'::'.$func[1];
elseif(is_string($func[0])) return implode('::',$func);
throw new Exception("First element of array-callable must be a class name or object instance");
} elseif(is_object($func)) {
return spl_object_hash($func);
}
throw new Exception("Unhandled callable type");
}
But if Alvaro's solution works... that's much simpler and more flexible.
Based on Alvaro's answer, I came up with these two functions:
function memoized() {
static $cache = array();
$args = func_get_args();
$func = array_shift($args);
$key = sha1(serialize(array_merge(array(spl_object_hash((object)$func)),$args)));
if(!isset($cache[$key])) {
$cache[$key] = call_user_func_array($func, $args);
}
return $cache[$key];
}
function memoized_func($func) {
return function() use ($func) {
static $cache = array();
$args = func_get_args();
$key = sha1(serialize($args));
if(!isset($cache[$key])) {
$cache[$key] = call_user_func_array($func, $args);
}
return $cache[$key];
};
}
Usage:
$f = function($n) {
for($i=0; $i<$n; ++$i);
return $n;
};
$result = memoized($f,50000);
$func = memoized_func($f);
$result2 = $func(50000);