jeremeamia / superclosure
Requires
- php: >=5.4
- nikic/php-parser: ^1.2|^2.0|^3.0|^4.0
- symfony/polyfill-php56: ^1.0
Requires (Dev)
- phpunit/phpunit: ^4.0|^5.0
README
一个用于序列化闭包和匿名函数的 PHP 库。
不再维护
此软件不再维护。请考虑使用 opis/closure。
README 的其余部分将保持软件废弃前的状态。
简介
曾经,我尝试序列化一个 PHP Closure
对象。正如你可能猜到的,它根本不起作用。实际上,你会从 PHP 运行时得到一个非常具体的错误信息
未捕获的异常 'Exception',消息为 '序列化 'Closure' 是不允许的'
尽管 PHP 不允许序列化闭包,但 SuperClosure 库使其成为可能。以下是使用方法
use SuperClosure\Serializer; $serializer = new Serializer(); $greeting = 'Hello'; $hello = function ($name = 'World') use ($greeting) { echo "{$greeting}, {$name}!\n"; }; $hello('Jeremy'); //> Hello, Jeremy! $serialized = $serializer->serialize($hello); // ... $unserialized = $serializer->unserialize($serialized); $unserialized('Jeremy'); //> Hello, Jeremy!
是的,非常酷,对吧?
特性
SuperClosure 包含两个不同的 闭包分析器,每个分析器都支持不同的闭包序列化特性。《TokenAnalyzer》的鲁棒性不如《AstAnalyzer》,但它大约快20-25倍。使用下面的表格,并考虑你的闭包代码的样子,你应该选择一个最 快速 的分析器,它支持你需要的特性。
支持特性 | 通过 AstAnalyzer |
通过 TokenAnalyzer |
---|---|---|
普通闭包(匿名函数)$fn = function (...) {...};
|
是 | 是 |
有上下文的闭包$fn = function () use ($a, $b, ...) {...};
|
是 | 是 |
递归闭包$fn = function () use (&$fn, ...) {...};
|
是 | 是 |
绑定到对象的闭包$fn = function () {$this->something(); ...};
|
是 | 是 |
作用于对象的闭包$fn = function () {self::something(); ...};
|
是 | 是 |
静态闭包(即保留 `static` 属性)$fn = static function () {...};
|
是 | -- |
参数中有类名的闭包$fn = function (Foo $foo) {...};
|
是 | -- |
体中有类名的闭包$fn = function () {$foo = new Foo; ...};
|
是 | -- |
有魔术常量的闭包$fn = function () {$file = __FILE__; ...};
|
是 | -- |
性能 | 慢 | 快 |
注意事项
- 对于任何使用引用的变量(例如,
function () use (&$vars, &$like, &$these) {…}
),序列化后引用不会保留。唯一的例外是递归闭包引用。 - 如果你在单行上定义了两个闭包(你为什么要这样做?),你将无法序列化任何一个,因为无法确定应该解析哪个闭包的代码(毕竟它们是匿名函数)。
- 警告:使用
eval()
函数对闭包进行反序列化是必要的。许多人都认为这个函数很危险,因此在使用这个库时,您需要评估可能需要采取哪些预防措施。您应该只从可信来源反序列化闭包,否则您可能会面临代码注入攻击。如果计划存储或传输序列化闭包,最好对它们进行签名。下面将详细介绍如何进行签名。 - 不能序列化在
eval()
代码内部定义的闭包。这包括重新序列化已反序列化的闭包。
分析器
在实例化 Serializer
时,您可以选择要使用的分析器。如果您没有指定,默认使用 AstAnalyzer
,因为它具有最多的功能。
use SuperClosure\Serializer; use SuperClosure\Analyzer\AstAnalyzer; use SuperClosure\Analyzer\TokenAnalyzer; // Use the default analyzer. $serializer = new Serializer(); // Explicitly choose an analyzer. $serializer = new Serializer(new AstAnalyzer()); // OR $serializer = new Serializer(new TokenAnalyzer());
如果只是想要对闭包对象进行一些反射,分析器也有其自身的用途。查看使用 AstAnalyzer
返回的内容。
use SuperClosure\Analyzer\AstAnalyzer; class Calculator { public function getAdder($operand) { return function ($number) use ($operand) { return $number + $operand; }; } } $closure = (new Calculator)->getAdder(5); $analyzer = new AstAnalyzer(); var_dump($analyzer->analyze($closure)); // array(10) { // 'reflection' => class ReflectionFunction#5 (1) {...} // 'code' => string(68) "function ($number) use($operand) { // return $number + $operand; // };" // 'hasThis' => bool(false) // 'context' => array(1) { // 'operand' => int(5) // } // 'hasRefs' => bool(false) // 'binding' => class Calculator#2 (0) {...} // 'scope' => string(10) "Calculator" // 'isStatic' => bool(false) // 'ast' => class PhpParser\Node\Expr\Closure#13 (2) {...} // 'location' => array(8) { // 'class' => string(11) "\Calculator" // 'directory' => string(47) "/Users/lindblom/Projects/{...}/SuperClosureTest" // 'file' => string(58) "/Users/lindblom/Projects/{...}/SuperClosureTest/simple.php" // 'function' => string(9) "{closure}" // 'line' => int(11) // 'method' => string(22) "\Calculator::{closure}" // 'namespace' => NULL // 'trait' => NULL // } // }
签名闭包
SuperClosure 2.1+ 版本允许您在实例化 Serializer 时指定签名密钥。这样做将配置您的 Serializer 对序列化的任何闭包进行签名,并验证反序列化的任何闭包的签名。这样做可以帮助您免受可能由于有人篡改序列化闭包而发生的代码注入攻击的影响。请记住,您的签名密钥是保密的。
$serializer1 = new SuperClosure\Serializer(null, $yourSecretSigningKey); $data = $serializer1->serialize(function () {echo "Hello!\n";}); echo $data . "\n"; // %rv9zNtTArySx/1803fgk3rPS1RO4uOPPaoZfTRWp554=C:32:"SuperClosure\Serializa... $serializer2 = new SuperClosure\Serializer(null, $incorrectKey); try { $fn = $serializer2->unserialize($data); } catch (SuperClosure\Exception\ClosureUnserializationException $e) { echo $e->getMessage() . "\n"; } // The signature of the closure's data is invalid, which means the serialized // closure has been modified and is unsafe to unserialize.
安装
要使用 Composer 在您的项目中安装 Super Closure 库,只需使用 Composer 引入项目
$ composer require jeremeamia/superclosure
当然,如果您愿意,也可以手动更新您的 require 块
{ "require": { "jeremeamia/superclosure": "^2.0" } }
有关如何使用 Composer 的更多信息,请访问 Composer 主页
我为什么需要序列化闭包呢?
既然您正在查看这个 README,您可能已经有了用例。尽管这个概念最初是一个实验,但在实际应用中已经出现了一些用例。
例如,在 UserScape 发表的关于 Laravel 和 IronMQ 的 视频 中,大约在 7:50 的时候,他们展示了如何将闭包推送到队列作为作业,以便由工作进程执行。这样做的好处是,您不必为可能非常简单的作业创建整个类。
或者,您可能有一个依赖注入容器或路由对象,是通过编写闭包构建的。如果您想对其进行缓存,您就需要能够序列化它。
然而,通常情况下,应该避免序列化闭包。
告诉我这个项目是如何开始的
一切始于2010年初,当时PHP 5.3开始受到关注。我着手证明尽管PHP不允许这样做,但仍然可以序列化闭包。我在前雇主的博客HTMList上发表了一篇名为使用序列化和反射扩展PHP 5.3闭包的文章,展示了如何实现它。我还将代码发布到了GitHub。
从那时起,我对代码进行了一些迭代,最新的迭代由于使用了出色的nikic/php-parser库,变得更加健壮。
谁在使用SuperClosure?
- Laravel - 将闭包序列化,以便可能推送到作业队列。
- HTTP Mock for PHP - 将闭包序列化,在测试工作流程中发送到远程服务器。
- Jumper - 将闭包序列化,在远程主机上通过SSH运行。
- nicmart/Benchmark - 使用
ClosureParser
显示基准测试的闭包代码。 - florianv/business - 将特殊日子序列化以存储工作日定义。
- zumba/json-serializer - 将PHP变量序列化为JSON格式。
- PHP-DI - 将闭包定义编译为优化的PHP代码。
- 请告诉我您的项目是否使用Super Closure以及如何使用。
替代方案
今年推出了Opis Closure库,它也提供了序列化闭包的能力。您也应该检查一下它,看看哪个最适合您的需求。
如果您希望将闭包作为可执行PHP代码导出,可以查看brick/varexporter库。