8

これは設計上の問題を引き起こすに違いありませんが、PHP でクロージャーをシリアル化またはハッシュして、そのクロージャーの一意の識別子を取得したいと考えています。

そこからクロージャーを呼び出す必要はありません。クロージャー自体の内外からアクセスできる一意の識別子が必要なだけです。つまり、クローザーを受け入れるメソッドは、その ID を生成する必要があります。クロージャーであり、クロージャー自体が同じ ID を生成できる必要があります。

私がこれまでに試したこと:

$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__; };
    }
}

Reflection API も、ID の作成に使用できるものを提供していないようです。

4

9 に答える 9

8

私の解決策はより一般的で、クロージャーの静的パラメーターを尊重します。トリックを行うには、クロージャー内のクロージャーへの参照を渡すことができます。

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>";
于 2013-01-31T07:33:52.803 に答える
6

必要なことはすべて独自に記述できます。独自のクロージャにはgetId()orなどがありgetHash()ます。

例 (デモ):

1: Hello world
2: Hello world

最初のクロージャー (ID: 1)、呼び出しコンテキストで読み取られた ID。2 番目のクロージャ (ID: 2)、クロージャ内から読み取られた ID (自己参照)。

コード:

<?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');

それがあなたのニーズに合っているかどうかはわかりませんが、いくつかのアイデアが得られるかもしれません。私は提案しspl_object_hashましたが、なぜそれが機能しないのか、または最終的に機能するのかについての議論をあまり理解していませんでした。

于 2012-12-21T05:43:52.060 に答える
5

わかりました、ここに私が考えることができる唯一のものがあります:

<?php
$f = function() {
};
$rf = new ReflectionFunction($f);
$pseudounique = $rf->getFileName().$rf->getEndLine();
?>

必要に応じて、md5 などでハッシュ化できます。ただし、関数が文字列から生成される場合は、それをシードする必要がありますuniqid()

于 2012-12-21T03:56:49.597 に答える
3

PHP匿名関数は、Closure クラスのインスタンスとして公開されます。それらは基本的にオブジェクトでspl_object_hashあるため、渡されると一意の識別子を返します。PHP 対話型プロンプトから:

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

これらの識別子は同じように見えるかもしれませんが、中央の 1 文字が異なります。

識別子はそのリクエストにのみ適しているため、関数とuse変数が変更されていなくても、呼び出し間で変更されることを期待してください。

于 2012-12-21T03:50:04.600 に答える
1

署名を生成したいようです。クロージャーがパラメーターを受け入れる場合、クロージャーの外部から署名を作成することはほぼ不可能です。渡されたデータは、生成された署名を変更します。

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

クロージャーがパラメーターを受け入れない場合でも、クロージャー内に署名を保存して永続化するという問題がまだあります。クロージャー内の静的変数を使用して何かを実行できる場合があるため、一度だけ初期化して「署名」を保持します。しかし、それを取得する方法が面倒です。

クロージャーではなく、クラスが必要なようです。これらの問題をすべて解決します。インスタンス化時に「ソルト」を渡し、ソルト (乱数) を使用して署名を生成させることができます。これにより、署名が一意になります。次に、そのソルトを保持し、まったく同じコンストラクターパラメーター (つまりソルト) を使用してクラスを再作成し、それを既に作成されたクラスのファイルの署名と比較できます。

于 2012-12-21T03:35:00.223 に答える
0

@hakre と @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__; };
    }
}
于 2012-12-21T04:30:14.820 に答える