Home » Php » serialization – Serialize or Hash a Closure in PHP

serialization – Serialize or Hash a Closure in PHP

Posted by: admin July 12, 2020 Leave a comment

Questions:

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.

How to&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>";

Answer:

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.

Answer:

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

Answer:

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.

Answer:

Superclosure provides a convenience class that allows you to serialize/unserialize closures, among other things.

Answer:

It sounds like you want to generate a signature. Creating a signature from outside the closure will be nearly impossible to reproduce if the closure accepts any parameters. The data passed in will change the generated signature.

$someClass = new SomeClass();
$closure1 = $someClass->closure();
$closure1_id = md5(print_r($closure1, true));

Even if your closure doesn’t accept parameters, you still have the problem of storing and persisting the signature inside the closure. You might be able to do something with a static variable inside the closure so it only initializes once and retains the “signature”. But that gets messy on how to retrieve it.

It really sounds like you want a class, not a closure. It would solve all of these problems. You could pass in a “salt” on instantiation and have it generate a signature using the salt (i.e. a random number). That would make the signature unique. You could then retain that salt, recreate a class using the exact same constructor parameters (i.e. salt) and compare that with the signature on file in the already created class.

Answer:

Possible solution arrived at with the help of @hakre and @dualed:

$someClass = new SomeClass();

$closure = $someClass->closure();
$closure2 = $someClass->closure2();

$rf = new ReflectionFunction($closure);
$rf2 = new ReflectionFunction($closure2);

print spl_object_hash($rf); // Outputs: 000000007ddc37c8000000003b230216
print spl_object_hash($rf2); // Outputs: 000000007ddc37c9000000003b230216

class SomeClass
{
    function closure()
    {
        return function () { return 'I am closure: ' . __FUNCTION__; };
    }

    function closure2()
    {
        return function () { return 'I am closure: ' . __FUNCTION__; };
    }
}