tholabs / creator
creator是一个简单的PHP依赖注入,支持类型提示
Requires
- php: >=7.2
- psr/container: ^1.0 || ^2.0
- tholabs/creator-interfaces: ^1.1
Requires (Dev)
- phpunit/phpunit: ^8.5.21
Provides
- psr/container-implementation: 1.0.0
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
。