movephp/callback-container

回调和闭包的分析与序列化

v2.0.0 2017-11-30 19:54 UTC

This package is auto-updated.

Last update: 2024-09-25 20:12:25 UTC


README

Build Status Coverage Status

回调容器

这个库是一个小的类包装器,覆盖了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);
    }
}