在PHP中序列化或散列闭包
这必然会引起设计问题,但我想在PHP中序列化或散列一个闭包,这样我就有了该闭包的唯一标识符 我不需要能够从中调用闭包,我只需要它的唯一标识符,该标识符可以从闭包本身的内部和外部访问,即接受闭包的方法需要为该闭包生成id,闭包本身需要能够生成相同的id 到目前为止我已经尝试过的事情:在PHP中序列化或散列闭包,php,serialization,closures,Php,Serialization,Closures,这必然会引起设计问题,但我想在PHP中序列化或散列一个闭包,这样我就有了该闭包的唯一标识符 我不需要能够从中调用闭包,我只需要它的唯一标识符,该标识符可以从闭包本身的内部和外部访问,即接受闭包的方法需要为该闭包生成id,闭包本身需要能够生成相同的id 到目前为止我已经尝试过的事情: $someClass = new SomeClass(); $closure1 = $someClass->closure(); print $closure1(); // Outputs: I am a
$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__; };
}
}
反射API似乎也没有提供我可以用来创建ID的任何东西。听起来像是要生成签名。如果闭包接受任何参数,那么从闭包外部创建签名几乎不可能重现。传入的数据将更改生成的签名
$someClass = new SomeClass();
$closure1 = $someClass->closure();
$closure1_id = md5(print_r($closure1, true));
即使您的闭包不接受参数,您仍然存在在闭包中存储和持久化签名的问题。您可能可以使用闭包中的静态变量执行某些操作,这样它只初始化一次并保留“签名”。但是如何取回它会变得很麻烦
听起来你真的想要一门课,而不是结束。它将解决所有这些问题。您可以在实例化时传入一个“salt”,并让它使用salt生成一个签名(即一个随机数)。这将使签名独一无二。然后,您可以保留该salt,使用完全相同的构造函数参数(即salt)重新创建一个类,并将其与已创建的类中的文件签名进行比较。PHP作为的实例公开。因为它们基本上都是对象,所以当递给它们时,它们将返回一个唯一的标识符。从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
这些标识符可能看起来相同,但是它们在中间有一个字母不同。
标识符仅适用于该请求,因此,即使函数和任何
使用的'd变量没有改变,也希望它在调用之间发生变化。好的,我能想到的只有一件事:
<?php
$f = function() {
};
$rf = new ReflectionFunction($f);
$pseudounique = $rf->getFileName().$rf->getEndLine();
?>
如果你喜欢,你可以用md5或者其他什么东西来散列它。但是,如果函数是从字符串生成的,则应使用uniqid()
在@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__; };
}
}
您可以编写自己需要的所有代码,您自己的闭包有一个getId()
或getHash()
或任何东西
示例():
第一个闭包(ID:1),在调用上下文中读取ID。第二个闭包(ID:2),从闭包内读取的ID(自引用)
代码:
我的解决方案更通用,并且尊重闭包的静态参数。要实现此技巧,可以在闭包内传递对闭包的引用:
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>";
类ClosureHash
{
/**
*哈希表
*
*@var SplObjectStorage
*/
受保护的静态$hashes=null;
/**
*返回闭包的哈希值
*
*@param可调用$closure
*
*@返回字符串
*/
来自的公共静态函数(Closure$Closure)
{
如果(!self::$hashes){
self::$hashes=new SplObjectStorage();
}
if(!isset(self::$hashes[$closure])){
$ref=新的ReflectionFunction($closure);
$file=newsplfileobject($ref->getFileName());
$file->seek($ref->getstartine()-1);
$content='';
而($file->key()<$ref->getEndLine()){
$content.=$file->current();
$file->next();
}
self::$hashes[$closure]=md5(json_编码(数组(
$content,
$ref->getStaticVariables()
)));
}
返回self::$hash[$closure];
}
}
课堂测试{
公共功能hello($greeting)
{
$closure=函数($message)使用($greeting,&$closure){
echo“Inside:”,ClosureHash::from($closure),PHP_EOL,“
”;
};
返回$closure;
}
}
$obj=新测试();
$closure=$obj->hello('hello');
$closure('PHP');
echo“Outside:”,ClosureHash::from($closure),PHP_EOL,“
”;
$other=$obj->hello('Bonjour');
$other('PHP');
echo“Outside:”,ClosureHash::from($other),PHP_EOL,“
”;
提供了一个方便的类,允许您序列化/取消序列化闭包等。我相信您不能在PHP中以可靠的方式散列闭包
实例,因为您无法访问属于函数体的AST中的大多数符号
就我所知,只有闭包使用的外部范围变量,类型T_变量的函数体中的符号($a
,$b
等),类型信息和函数签名可以以各种方式进行说明。如果没有关于函数体的重要信息,当应用于闭包的实例时,哈希函数不可能以幂等方式运行
spl\u object\u hash
或spl\u object\u id
不会保存您的数据——可能(在实际应用程序中几乎经常)更改refcount会使问题复杂化,因此这些函数通常也不是幂等函数
散列闭包
实例的唯一可能情况是,它已在某个PHP源文件中定义,并且您当前的实例不使用其外部作用域中的其他闭包
实例。在这种情况下,通过将Closure
实例包装到ReflectionFunction
实例中,您可能会获得一些成功。现在,您可以尝试获取声明闭包的文件名和行号。然后,您可以加载源文件并提取行号之间的部分,将该部分转储为字符串,并使用token\u get\u all()
标记它。接下来移除不属于Closure
声明的标记,并查看外部标记
<?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');
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>";
$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);
}
);
};
$b = 42;
$zhash_algo_gz(function ($a) use ($b) { return $a * $b + 5; });
$zhash_algo_gz(function ($a) use ($b) { return $a * $b + 6; });
a0cd0738ea01d667c9386d4d9fe085cbc81c0010f30d826106c44a884caf6184
a0cd0738ea01d667c9386d4d9fe085cbc81c0010f30d826106c44a884caf6184
$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
use function Opis\Closure\{serialize as opisSerialize, unserialize as opisUnserialize};
$serialized = opisSerialize(new SerializableClosure($closure));
$wrapper = opisUnserialize($serialized);