Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Serialize or Hash a Closure in PHP

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.

like image 762
Toby Avatar asked Dec 21 '12 02:12

Toby


4 Answers

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>";
like image 176
lisachenko Avatar answered Sep 22 '22 19:09

lisachenko


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.

like image 29
hakre Avatar answered Sep 22 '22 19:09

hakre


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()

like image 30
dualed Avatar answered Sep 19 '22 19:09

dualed


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.

like image 42
Charles Avatar answered Sep 21 '22 19:09

Charles