tholabs/creator

creator是一个简单的PHP依赖注入,支持类型提示

1.6.1 2023-06-01 12:56 UTC

README

creator是一个简单的PHP依赖注入,支持类型提示和反射类

安装

通过composer

$ composer require tholabs/creator

测试

通过phpunit

$ phpunit

基本用法

使用类常量

<?php
    $creator = new \Creator\Creator;
    $myInstance = $creator->create(MyClass::class);

使用递归依赖

假设我们的MyClass看起来像这样

<?php
    class MyClass {
        function __construct(AnotherClass $anotherClass) {
            $this->anotherClass = $anotherClass;
        }
    }

Creator会遍历依赖树并解决任何尚未有已知实例的类。

PSR-11容器使用

Creator支持PSR-11 psr/container标准。

<?php

    $container = new Creator\Container(new Creator\Creator());
    $myInstance = $container->get(MyClass::class);

$container->has()将返回true,如果

  • 使用$creator->registerPrimitiveResource()已注册具有给定$identifier的原始资源
  • 使用$creator->registerClassResource()已注册具有给定$identifier的类资源
  • 使用$creator->registerFactory()已注册具有$identifier的类资源工厂
  • $identifier是一个接口或抽象类,可以被实现(因为之前已注册了实现或继承的其他实例)

如果给定的$identifier 可能可实例化,则不会返回true

注入实例

Creator能够为一个单独的创建过程使用独立的资源注册表。

<?php
    $anotherClass = new AnotherClass();
    
    $creator = new Creator\Creator;
    $myClass = $creator->createInjected(MyClass::class)->with($anotherClass)->create();
    
    if ($myClass->anotherClass === $anotherClass) {
        echo 'We are the same!';
    }

MyClass的任何其他依赖都将按常规方式解决(即通过ResourceRegistry查找,如果尚无实例,则使用相同的注入资源进行创建。)Creator收集依赖签名,因此只重新创建真正需要注入依赖的实例。

调用闭包/可调用对象

Creator能够解决闭包和可调用对象的参数。它支持闭包和具有索引0的对象上下文数组可调用对象(当前不支持使用索引0的类名,并会导致Creator抛出Unresolvable异常)。

也支持注入实例。

<?php

    // Default invocation
    $creator->invoke(function(SimpleClass $simpleClass) {
        if ($simpleClass instanceof SimpleClass) {
            echo 'Everything works as expected.';
        }
    }
    
    // Injected invocation
    $simpleClassRoleModel = new SimpleClass();
    $creator->invokeInjected(function(SimpleClass $simpleClass) use($simpleClassRoleModel) {
        if ($simpleClass === $simpleClassRoleModel) {
            echo 'Injection is great';
        }
    })->with($simpleClassRoleModel)->invoke();

工厂

如果您有不能没有附加逻辑创建的资源,但同时也应该仅在另一个组件依赖于它们时才创建,您可以为此工厂注册一个工厂。工厂可以是一个可调用对象、Creator\Interfaces\Factory的实例或Factory类的字符串,并且可以注册为任何类资源,即接口、抽象或普通类。

全局工厂

<?php

    $simpleClass = new SimpleClass();
    $factory = function() use ($simpleClass) {
        return new ExtendedClass($simpleClass);
    };
    
    $creator->registerFactory($factory, ExtendedClass::class);
    
    $extendedClass = $creator->create(ExtendedClass::class);
    if ($extendedClass->getSimpleClass() === $simpleClass) {
        echo 'Factories are awesome!';
    }

这对于像数据库连接这样的东西特别有用,您只希望当组件真正依赖于它时才创建连接。

注入工厂

当然,您也可以将工厂注册为注入

<?php
    $simpleClass = new SimpleClass();
    $factory = function() use ($simpleClass) {
        return new ExtendedClass($simpleClass);
    };
    
    $extendedClass = $creator->createInjected(ExtendedClass:class)
        ->withFactory($factory, ExtendedClass::class)
        ->create();
    
    if ($extendedClass->getSimpleClass() === $simpleClass) {
        echo 'Factories are awesome!';
    }

注入工厂覆盖全局注册的工厂和全局注册的资源。然而,它们不会覆盖注入的资源。(创建顺序是:注入实例 -> 注入工厂 -> 全局实例 -> 全局工厂 -> 创建实例)

自工厂

为了避免需要全局工厂,类也可以实现Creator\Interfaces\SelfFactory接口。所有实现此接口的类将不会使用它们的构造函数进行构建;相反,它们必须返回一个工厂闭包

class MyDependency implements Creator\Interfaces\SelfFactory {

    static function createSelf () : callable {
        return function(AnotherDependency $a) : MyDependency {
            return new static($a->getFoo());
        }
    }
    
    function __construct (Foo $foo) {
        $this->foo = $foo;
    }
    
}

在此需要注意的是,不返回类的实例将引发一个InvalidFactoryResult异常。

延迟绑定工厂

如果您有应该在需要时才创建的工厂,您可以使用它的类名注册一个延迟工厂。

<?php

    $creator->registerFactory(SimpleFactory::class, SimpleClass::class);
    
    // Creates a SimpleClass with SimpleFactory
    $simpleClass = $creator->create(SimpleClass::class);

所有延迟绑定的工厂都存储在并从定义它们的资源注册表中读取。

<?php

    class SimpleFactory implements Factory {
        
        function __construct(SimpleClass $simpleClass) {
            $this->simpleClass = $simpleClass;
        }
        
        function createInstance() {
            return $this->simpleClass;
        }
        
    }

    $creator->registerFactory(SimpleFactory::class, SimpleClass::class);
    
    $simpleClass = new SimpleClass();
    $simpleFactory = new SimpleFactory($simpleClass);
    
    $creator->registerClassResource($simpleFactory);
    
    $generatedSimpleClass = $creator->create(SimpleClass::class);
    if ($simpleClass === $generatedSimpleClass) {
        echo 'Congratulations, this example is completely useless and works!';
    }

工厂结果缓存

所有工厂结果都注册到它们对应的ResourceRegistry中,即注入的工厂将它的结果存储到注入的注册表中,因此仅在创建过程中使创建的资源可用。唯一的例外是具有注入依赖的延迟绑定工厂;在这种情况下,工厂的结果将缓存在注入的注册表中。

<?php

    class ArbitraryFactory implements Factory {
        
        function __construct(SimpleClass $simpleClass) {
            $this->simpleClass = $simpleClass;
        }
        
        function createInstance() {
            return $this->simpleClass;
        }
        
    }

    $creator->registerFactory(ArbitraryFactory::class, ArbitraryClassWithSimpleClassDependency::class);
    
    $injectedArbitraryClass = $creator->createInjected(ArbitraryClassWithSimpleClassDependency::class)
        ->with(new SimpleClass())
        ->create();
    
    $anyArbitraryClass = $creator->create(ArbitraryClassWithSimpleClassDependency::class);

在上面的例子中,ArbitraryClassWithSimpleClassDependency的实例将不相同。创建者检测到SimpleClass是注册的工厂的依赖项,因此创建了一个带有注入的SimpleClass的新ArbitraryFactory实例。这个新的工厂实例存储到注入的注册表中,并且不会影响其他创建。

不可实例化的类

单例

如果单例实现了Creator\Interfaces\Singleton接口,则可以解析单例。

抽象类、接口

如果Creator遇到一个接口或抽象类,它将尝试查找是否有资源实现了该接口/抽象类。首先一个被提供。

注册资源

如果您想使创建者使用类的特定实例,您可以将任何对象注册到创建者。然后,它将使用此实例进行任何即将进行的创建 - 这是一种更“持久”的注入。

<?php
    $a = new stdClass;
    $a->foo = 'bar';
    
    $creator = new Creator\Creator;
    $creator->registerClassResource($a);
    
    $instance = $creator->create('stdClass'); // you should not use hardcoded strings as class names; always prefer the class constant
    echo $instance->foo; // bar

registerClassResource方法的可选第二个参数$classResourceKey绕过了对象的get_class确定。这可能会破坏代码完成和类型提示,因此请谨慎使用。

原始(标量)资源

Creator支持通过变量名注册标量值。

<?php

    class A {
        function __construct($foo) {
            echo $foo;
        }
    }
    
    $creator = new Creator\Creator;
    $creator->createInjected(A::class)
        ->with('bar', 'foo') // first value is the injection, second the resource key
        ->create();

在Creator的早期版本中,有一个方法可以在全局注册表中注册原始资源。这已经被移除,因为它可能会引起意外的行为并阻碍未来的开发。

然而,如果您确实需要它(但不要说没有人告诉你这是一个坏主意),您仍然可以通过将标量值注册到资源注册表中,并在构建您的Creator\Creator实例时传递这个注册表来实现这一点。请参阅测试用例以获取示例代码。

原始资源的具体信息

  • 如果一个参数有一个默认值,并且Creator找不到匹配的标量值,它将使用默认值。
  • 使用Creation::with()注册对象将始终导致类资源注册,即注册$creation->with($myInstance, 'foo');将只注册$myInstance为foo类,但永远不会作为原始资源。

异常

所有异常都源自Creator\Exceptions\CreatorException。在您的捕获块中使用此类来捕获与Creator相关的所有异常。

此外,还有更具体的异常

  • 如果Creator无法解析依赖项,它将抛出Creator\Exceptions\Unresolvable
  • 如果您注册了一个既不是callable也不是实现了Creator\Interfaces\Factory接口的类的实例或类名,它将抛出Creator\Exceptions\InvalidFactory
  • 如果一个自工厂返回的类不是自实例的实例,它将抛出Creator\Exceptions\InvalidFactoryResult