这必然要求设计问题,但我想在PHP中序列化或散列闭包,这样我就有了一个闭包的唯一标识符。
我不需要能够从中调用闭包,我只需要一个唯一的标识符,可以从闭包本身的内部和外部访问,即接受close的方法需要生成一个该闭包的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的东西。
答案 0 :(得分:5)
好的,这是我唯一能想到的:
<?php
$f = function() {
};
$rf = new ReflectionFunction($f);
$pseudounique = $rf->getFileName().$rf->getEndLine();
?>
如果您愿意,可以使用md5或诸如此类的方法对其进行哈希处理。但是,如果函数是从字符串生成的,则应使用uniqid()
答案 1 :(得分:5)
您可以根据需要编写自己的所有内容,使用getId()
或getHash()
或其他内容来创建自己的内容。
示例(Demo):
1: Hello world
2: Hello world
第一个闭包(ID:1),在调用上下文中读取ID。第二个闭包(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
,但不理解讨论为什么它没有,或者最终确实有效。
答案 2 :(得分:5)
我的解决方案更通用,并且尊重静态参数以进行关闭。为了实现这一点,您可以在闭包内传递对闭包的引用:
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>";
答案 3 :(得分:3)
PHP anonymous functions作为Closure class的实例公开。因为它们基本上是对象,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
这些标识符可能看起来相同,但它们在中间有一个字母。
标识符仅适用于该请求,因此期望它在调用之间更改,即使函数和任何use
'd变量不会更改。
答案 4 :(得分:2)
Superclosure提供了一个便利类,允许您序列化/反序列化闭包等。
答案 5 :(得分:1)
听起来你想要生成一个签名。如果闭包接受任何参数,则几乎不可能重现从闭包外部创建签名。传入的数据将更改生成的签名。
$someClass = new SomeClass();
$closure1 = $someClass->closure();
$closure1_id = md5(print_r($closure1, true));
即使你的闭包不接受参数,你仍然存在在闭包内存储和持久签名的问题。您可以在闭包内部使用静态变量执行某些操作,这样它只会初始化一次并保留“签名”。但是如何检索它会变得混乱。
听起来你真的想要一个课,而不是一个封闭课。它将解决所有这些问题。您可以在实例化时传入“salt”,并使用salt(即随机数)生成签名。这将使签名独一无二。然后,您可以保留该salt,使用完全相同的构造函数参数(即salt)重新创建一个类,并将其与已创建的类中的文件签名进行比较。
答案 6 :(得分: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__; };
}
}
答案 7 :(得分:0)
我相信您无法在PHP中以可靠的方式对Closure
实例进行哈希处理,因为您无法访问AST中属于函数体的大多数符号。
据我只能说出Closure
使用的外部作用域变量,类型为T_VARIABLE
的函数体中的符号($a
,$b
等),类型信息和功能签名可以通过多种方式进行说明。缺少有关函数主体的重要信息,将哈希函数应用于Closure
的实例时就不可能表现出幂等的行为。
spl_object_hash
或spl_object_id
不会挽救您-可能(几乎在现实世界的应用程序中经常更改)更改引用计数会使事情复杂化,因此这些功能通常也不是幂等的。
唯一可能散列Closure
实例的情况是在某个PHP源文件中的某个位置定义了该实例,而您的当前实例在其外部范围中没有使用其他Closure
实例。在这种情况下,您可以通过将Closure
实例包装在ReflectionFunction
实例中来获得一些成功。现在,您可以尝试获取声明Closure
的文件名和行号。然后,您可以加载源文件并提取行号之间的部分,将该部分转储为字符串并用token_get_all()
标记化。接下来,删除不属于Closure
声明的标记,并查看Closure
实例的外部范围,以获取其使用的任何外部范围变量的值。最后,以某种方式将所有这些内容组合在一起并哈希该数据。但是,当然,当您希望将函数传递给函数时,直到您开始质疑“ ..如果外部作用域变量也是Closure
实例又如何呢?” ..
为了测试PHP会发生什么,我使用了以下两个函数:
$zhash = function ($input, callable $hash = null, callable $ob_callback = null) {
if (\is_scalar($input)) {
return \is_callable($hash) ? $hash($input) : \hash('md5', $input);
}
\ob_start(
\is_callable($ob_callback) ? $ob_callback : null,
4096,
PHP_OUTPUT_HANDLER_STDFLAGS
);
\debug_zval_dump($input);
$dump = \ob_get_clean();
return \is_callable($hash) ? $hash($dump) : \hash('md5', $dump);
};
$zhash_algo_gz = function ($input, string $algo = 'sha256', int $compress = -1) use ($zhash) {
return $zhash(
$input,
function ($data) use ($algo) {
return \hash($algo, $data);
},
function ($data) use ($compress) {
return \gzcompress($data, $compress, ZLIB_ENCODING_GZIP);
}
);
};
使用debug_zval_dump
是为了避免循环引用和资源失败。 gzcompress
的使用是将输入数据压缩为哈希函数(如果它是非常大的类)。在遇到导致我首先遇到的确切问题(即$zhash_algo_gz
包含refcount而非函数体,导致不完整)之前,我用一个已满载的Magento2应用程序作为对debug_zval_dump
的输入进行了测试。 -幂等来自哈希函数)。
在测试中。
我们在此测试中使用的所有闭包中都设置了变量:
$b = 42;
在第一个示例中,两个调用的引用计数保持不变,因为我们的两个Closure
实例未绑定到变量,并且代码在全新的php -a
会话中执行:
$zhash_algo_gz(function ($a) use ($b) { return $a * $b + 5; });
$zhash_algo_gz(function ($a) use ($b) { return $a * $b + 6; });
输出:
a0cd0738ea01d667c9386d4d9fe085cbc81c0010f30d826106c44a884caf6184
a0cd0738ea01d667c9386d4d9fe085cbc81c0010f30d826106c44a884caf6184
休斯顿,我们有问题!
如前所述,我们无法推断有关Closure
实例的函数主体的重要信息。 +5
和+6
令牌将不会显示在任何命令输出中,print_r
,var_export
,var_dump
,debug_zval_dump
或其他命令中。>
这意味着散列两个具有相同签名,引用计数,外部作用域变量和参数但部分异构的函数主体的匿名函数将产生相同的哈希值。
如果我们开始一个全新的php -a
会话,但是现在先将我们的Closure
实例绑定到变量,那么乍一看可能看起来不错:
$f1 = function ($a) use ($b) { return $a * $b + 5; });
$f2 = function ($a) use ($b) { return $a * $b + 6; });
$zhash_algo_gz($f1);
$zhash_algo_gz($f2);
输出:
085323126d01f3e04dacdbb6791f230d99f16fbf4189f98bf8d831185ef13b6c
18a9c0b26bf6f6546d08911d7268abba72e1d12ede2e9619d782deded922ab65
嘿,不同的哈希值!但不要上当...
由于函数主体的更改,哈希的更改为 not ,而由于引用计数的更改!。那么该哈希没有太大用处吗?
除了所有这些,就像布伦特说的那样,您可能想要一个Class
,而不是Closure
..这仍然是PHP;)
答案 8 :(得分:0)
只需使用https://github.com/opis/closure,即可维护该库,并利用SplObjectStorage为该闭包对象创建包装器。您可以像这样使用序列化和反序列化:
use function Opis\Closure\{serialize as opisSerialize, unserialize as opisUnserialize};
$serialized = opisSerialize(new SerializableClosure($closure));
$wrapper = opisUnserialize($serialized);