探索PHP 8.4中延迟对象的依赖注入

PHP

在现代PHP领域,8.4版本的发布引入了一项开创性的特性:延迟对象(Lazy Objects)。这些对象提供了一种全新的方式,可将初始化推迟到绝对必要时才进行,从而提升性能并减少资源使用。通过对ReflectionClass API的增强,该功能已深度融入PHP语言,这在《延迟对象的延迟初始化RFC》中有详细阐述。

RFC中的示例

为了说明延迟对象的潜力,来看直接取自该RFC的以下示例:

class MyClass
{
    public function __construct(private int $foo)
    {
        // 这里是繁重的初始化逻辑。
    }

    //...
}

$initializer = static function (MyClass $ghost): void {
    $ghost->__construct(123);
};

$reflector = new ReflectionClass(MyClass::class);
$object = $reflector->newLazyGhost($initializer);

// 此时,$object是一个延迟幽灵对象。

这种机制使开发者能够精细地控制初始化过程,确保仅在访问资源时才加载它们。

受此RFC启发,我着手构建一个符合PSR-11标准的依赖注入容器,利用延迟对象API实现最佳性能。

容器延迟对象的基础

我们容器的核心在于ContainerLazyObject类。借助它,你可以注册依赖项并延迟初始化它们,这意味着只有在实际需要时才会实例化这些依赖项。以下是执行此任务的主要方法:

public function set(string $id, object|string $concrete): void
{
    $reflector = new ReflectionClass($id);
    $initializer = $concrete;

    if (is_string($concrete)) {
        $initializer = function(object $instance) use ($concrete): void {
            $this->instances[$instance::class] = $concrete($this);
        };
    }

    if (is_object($concrete) &&!$concrete instanceof Closure) {
        $initializer = function(object $instance) use ($concrete): void {
            $this->instances[$instance::class] = $concrete;
        };
    }

    $this->instances[$id] = $reflector->newLazyProxy($initializer);
}

在容器中注册服务

我们的容器支持多种注册服务的方式,为开发者提供了灵活性。以下是一些示例:

$container = new ContainerLazyObject();
$container->set(DatabaseService::class, fn() => new DatabaseService(new LoggerService()));
$container->set(LoggerService::class, fn() => new LoggerService());

// 使用类名的替代方法
$container->set(DatabaseService::class, DatabaseService::class);
$container->set(LoggerService::class, LoggerService::class);

// 使用已实例化的对象
$container->set(DatabaseService::class, new DatabaseService(new LoggerService()));
$container->set(LoggerService::class, new LoggerService());

这种灵活性使ContainerLazyObject能够适应各种场景,无论是动态构建依赖项还是重用预先配置的对象。

从容器中检索服务 一旦在容器中注册了服务,你可以在需要时随时检索它们。容器确保服务是延迟实例化的,因此在实际请求之前不会创建它们。以下是如何检索已注册服务的示例:

// 从容器中检索服务
$loggerService = $container->get(LoggerService::class);
$databaseService = $container->get(DatabaseService::class);

ContainerLazyObject的核心 我们容器的核心在于ContainerLazyObject类。借助它,你可以注册依赖项并延迟初始化它们,即只有在实际使用时才会创建它们。以下是执行此任务的主要方法:

public function set(string $id, object|string $concrete): void
{
    $reflector = new ReflectionClass($id);
    $initializer = $concrete;

    if (is_string($concrete)) {
        $initializer = function(object $instance) use ($concrete): void {
            $this->instances[$instance::class] = $concrete($this);
        };
    }

    if (is_object($concrete) &&!$concrete instanceof Closure) {
        $initializer = function(object $instance) use ($concrete): void {
            $this->instances[$instance::class] = $concrete;
        };
    }

    $this->instances[$id] = $reflector->newLazyProxy($initializer);
}

PSR-11兼容性 ContainerLazyObject的另一个优点是它与PSR-11兼容,PSR-11是PHP依赖注入容器的标准。这确保了它与遵循该规范的库和框架的互操作性,使其成为一个轻量级且通用的解决方案。

与其他容器的性能比较 为了衡量我们容器的性能,我在受控环境中使用了PhpBench,并将其与流行的替代方案(Pimple、Illuminate和PHP-DI)进行了比较。结果令人鼓舞:

容器平均耗时(±标准差)
ContainerLazyObject0.100微秒(±33.33%)
benchPimple0.297微秒(±18.84%)
benchIlluminate0.503微秒(±9.07%)
benchPhpDI0.161微秒(±41.57%)

在简单的依赖解析场景中,我们的容器表现出卓越的性能,明显比诸如Illuminate容器和PHP - DI等功能更强大的替代方案更快。

完整类定义

class ContainerLazyObject implements ContainerInterface
{

    /**
     * @var array
     */
    private array $instances;

    /**
     * @var array
     */
    protected static array $reflectionCache = [];


    /**
     * @param  string  $class
     *
     * @return ReflectionClass
     * @throws ReflectionException
     */
    protected function getReflectionClass(string $class): ReflectionClass
    {
        if(!isset(self::$reflectionCache[$class])) {
            self::$reflectionCache[$class] = new ReflectionClass($class);
        }

        return self::$reflectionCache[$class];
    }

    /**
     * @param  string         $class
     * @param  object|string  $concrete
     *
     * @return void
     * @throws ReflectionException
     */
    public function set(string $class, object|string $concrete): void
    {
        $initializer = $concrete;
        if(is_string($concrete)) {
            $initializer = function(object $instance) use ($concrete): void {
                $this->instances[$instance::class] = $concrete($this);
            };
        }

        if(is_object($concrete) &&!$concrete instanceof Closure) {
            $initializer = function(object $instance) use ($concrete): void {
                $this->instances[$instance::class] = $concrete;
            };
        }

        $this->instances[$class] = $this->getReflectionClass($class)->newLazyProxy($initializer);
    }

    /**
     * @param  string  $id
     *
     * @return object
     */
    public function get(string $id): object
    {
        if(!isset($this->instances[$id])) {
            throw new \InvalidArgumentException("Reference '{$id}' not found in container.");
        }
        return $this->instances[$id];
    }

    /**
     * @param  string  $id
     *
     * @return bool
     */
    public function has(string $id): bool
    {
        return isset($this->instances[$id]);
    }

    /**
     * @return void
     */
    public function clear(): void
    {
        $this->instances = [];
    }

    /**
     * @param  string  $id
     *
     * @return void
     */
    public function remove(string $id): void
    {
        if($this->has($id)) {
            unset($this->instances[$id]);
        }
    }
}

结论

PHP 8.4及其延迟对象为简化和优化依赖注入开辟了新的可能性。我们的ContainerLazyObject除了轻量级、高效且灵活之外,还符合PSR-11标准,确保了与其他库和框架的互操作性。

尝试这种方法,看看它如何在你的下一个项目中简化依赖管理!

PHP
Publish on 2025-01-10,Update on 2025-02-10