This is bound to beg design questions, but I want to serialize or hash a closure in PHP such that I have a unique identifier for that closure.
I don't need to be able to call the closure from that, I just need a unique identifier for it that is accessible from inside and outside of the closure itself, i.e. a method that accepts a closer will need to generate an id for that closure, and the closure itself will need to be able to generate that same id
Things I've tried so far:
$someClass = new SomeClass();
$closure1 = $someClass->closure();
print $closure1();
// Outputs: I am a closure: {closure}
print $someClass->closure();
// Outputs: Catchable fatal error: Object of class Closure could not be converted to string
print serialize($closure1);
// Outputs: Fatal error: Uncaught exception 'Exception' with message 'Serialization of 'Closure' is not allowed'
class SomeClass
{
function closure()
{
return function () { return 'I am a closure: ' . __FUNCTION__; };
}
}
The Reflection API doesn't seem to offer anything I might be able to use to create an ID either.
My solution is more general and respects static parameters for closure. To make the trick, you can pass a reference to the closure inside the closure:
class ClosureHash
{
/**
* List of hashes
*
* @var SplObjectStorage
*/
protected static $hashes = null;
/**
* Returns a hash for closure
*
* @param callable $closure
*
* @return string
*/
public static function from(Closure $closure)
{
if (!self::$hashes) {
self::$hashes = new SplObjectStorage();
}
if (!isset(self::$hashes[$closure])) {
$ref = new ReflectionFunction($closure);
$file = new SplFileObject($ref->getFileName());
$file->seek($ref->getStartLine()-1);
$content = '';
while ($file->key() < $ref->getEndLine()) {
$content .= $file->current();
$file->next();
}
self::$hashes[$closure] = md5(json_encode(array(
$content,
$ref->getStaticVariables()
)));
}
return self::$hashes[$closure];
}
}
class Test {
public function hello($greeting)
{
$closure = function ($message) use ($greeting, &$closure) {
echo "Inside: ", ClosureHash::from($closure), PHP_EOL, "<br>" ;
};
return $closure;
}
}
$obj = new Test();
$closure = $obj->hello('Hello');
$closure('PHP');
echo "Outside: ", ClosureHash::from($closure), PHP_EOL, "<br>";
$another = $obj->hello('Bonjour');
$another('PHP');
echo "Outside: ", ClosureHash::from($another), PHP_EOL, "<br>";
You could all that you need write your own, your own closures having a getId()
or getHash()
or whatever.
Example (Demo):
1: Hello world
2: Hello world
First closure (ID: 1), ID read in calling context. Second closure (ID: 2), ID read from within the closure (where self-reference).
Code:
<?php
/**
* @link http://stackoverflow.com/questions/13983714/serialize-or-hash-a-closure-in-php
*/
class IdClosure
{
private $callback;
private $id;
private static $sequence = 0;
final public function __construct(Callable $callback) {
$this->callback = $callback;
$this->id = ++IdClosure::$sequence;
}
public function __invoke() {
return call_user_func_array($this->callback, func_get_args());
}
public function getId() {
return $this->id;
}
}
$hello = new IdClosure(function($text) { echo "Hello $text\n";});
echo $hello->getId(), ": ", $hello('world');
$hello2 = new IdClosure(function($text) use (&$hello2) { echo $hello2->getId(), ": Hello $text\n";} );
$hello2('world');
I have no clue if that suits your needs, maybe it gives you some ideas. I suggested spl_object_hash
but didn't understood the discussion much why it does not or in the end then does work.
Ok, here is the only thing I can think of:
<?php
$f = function() {
};
$rf = new ReflectionFunction($f);
$pseudounique = $rf->getFileName().$rf->getEndLine();
?>
If you like, you can hash it with md5 or whatnot. If the function is generated from a string however, you should seed that with a uniqid()
PHP anonymous functions are exposed as instances of the Closure class. As they're basically objects, spl_object_hash
will return a unique identifier when handed one. From the PHP interactive prompt:
php > $a = function() { echo "I am A!"; };
php > $b = function() { echo "I am B!"; };
php >
php >
php > echo spl_object_hash($a), "\n", spl_object_hash($b), "\n";
000000004f2ef15d000000003b2d5c60
000000004f2ef15c000000003b2d5c60
Those identifiers might look the same, but they differ by one letter in the middle.
The identifier is good only for that request, so expect it to change between calls, even if the function and any use
'd variables don't change.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With