movephp / callback-container
回调和闭包的分析与序列化
Requires
- php: >=7.1
- psr/container: ~1.0
Requires (Dev)
- phpunit/phpunit: ^6.2
This package is auto-updated.
Last update: 2024-09-25 20:12:25 UTC
README
回调容器
这个库是一个小的类包装器,覆盖了PHP内置的callable
类型。
这个容器的主要特点是实现了Serializable
接口。由于并非所有的callable
都可以序列化,容器提供了一个isSerializable()
方法来方便地检查序列化的可能性。
目录
使用示例
通过工厂方法make()
创建容器对象
use \Movephp\CallbackContainer\Container;
$factory = new Container();
$callback = $factory->make('my_callback');
然后可以序列化
if($callback->isSerializable()){
var_dump(serialize($callback));
}
或获取相应的闭包
call_user_func($callback->closure());
尝试序列化包含不可序列化回调的容器将抛出异常
\Movephp\CallbackContainer\Exception\NonSerializableException
重要: 当序列化包含基于对象的回调
[$object, 'method']
的CallbackContainer时,仅保存对象类的名称。一方面,这样可以降低序列化/反序列化过程中的开销,并排除由于传入的$object
可能不可序列化而导致的问题。另一方面,需要注意在反序列化后,需要能够自动根据类名恢复这个$object
(如果它有一个无参数或使用PSR-容器构建的构造函数,则可以实现这一点)。
允许的回调参数
下表列出了方法make()
可以作为参数接受的不同类型的值
与PSR容器一起使用
如果项目中使用了实现PSR-11接口的DI容器,则可以将类似普通callable
的数组作为方法make()
的参数传递,其中第一个元素是用于在PSR容器中请求所需对象的键。
首先需要将PSR容器绑定到该库
use \Movephp\CallbackContainer\Container;
$factory = new Container($psrContiner);
$callback = $factory->make(['psr_container_key', 'method']);
PSR容器的键可以是,例如,接口名称或任何字符串,其中$psrContiner->has($key)
返回TRUE
。
反序列化后绑定PSR容器
序列化CallbackContainer时仅保存原始回调的简化表示。因此,如果最初使用了PSR容器,则在反序列化后需要将其重新绑定到CallbackContainer。
这可以为每个反序列化的CallbackContainer单独进行
$callback = unserialize($serialized);
$callback->setPsrContainer($psrContiner);
或者可以将PSR容器全局设置,以便为所有将来直接创建(new Container()
)或反序列化创建的CallbackContainer设置,通过调用预定义的静态方法setPsrContainerGlobal()
use \Movephp\CallbackContainer\Container;
Container::setPsrContainerGlobal($psrContiner);
$callback = unserialize($serialized);
分析回调参数
此外,CallbackContainer还有一个parameters()
方法,它返回一个对象数组,这些对象代表由给定回调接受的参数,属于类Movephp\CallbackContainer\Parameter
。数组的键是参数的名称。
parameters()
方法通过Reflection API分析参数,并在序列化时保存结果,从而提高整体性能,例如,在从缓存中提取CallbackContainer数组时。
Movephp\CallbackContainer\Parameter
类提供了一些获取参数详细信息的方法
不可序列化的回调
如果您想使用CallbackContainer的序列化功能,例如,为了提高应用程序的性能而进行缓存,那么您不仅要通过isSerializable()
检查每个元素的可序列化性,还要考虑哪些类型的回调不能被序列化。这包括
- 闭包,即内置类
\Closure
的对象,通常通过声明匿名函数创建。 - 任何与匿名类相关的
callable
,因为这些类没有固定名称,并且在反序列化后无法恢复。
实际应用示例
假设存在一个用于应用的路由机制,它将路由模板与类方法关联起来,并且结构相当复杂,因此有理由尝试缓存它。以下是一个示例,说明这可能如何实现:
use Movephp\CallbackContainer\Container;
Container::setPsrContainerGlobal($psrContainer);
if ($cache->isHit()) {
$routes = $cache->get();
} else {
$callbackFactory = new Container();
$rules = getRoutingRulesOverWholeProject(); // Собираем по всему проекту шаблоны роутов и связанные с ними калбеки
$routes = [];
foreach ($rules as $rule) {
try {
$callback = $callbackFactory->make($rule->callback);
} catch (\Exception $e) {
$logger->error($e);
continue;
}
$routes[] = [
'template' => $rule->template,
'callback' => $callback
];
}
// Проверяем, что все калбеки сериализуемые
$serializable = array_reduce(
$routes,
function ($result, $route) {
return $result && $route['callback']->isSerializable();
},
true
);
if ($serializable) {
// Кешируем результат
$cache->set($routes);
$cachePool->save($cache);
}
}