jakewhiteley / hodl
一个轻量级的PSR-11依赖注入容器,位于Pimple和Laravel容器之间
Requires
- php: >=7.4.0
- psr/container: ^2.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^2.11
- phpunit/phpunit: ~9.0
- squizlabs/php_codesniffer: ~3.0
README
Hodl为控制反转提供了完整的自动装配功能,但足够简单,可以作为独立的服务容器使用,无需任何麻烦。
这是一个简单的ArrayAccess
服务容器,它从Laravel和Pimple(Symfony)中汲取灵感,但处于中间位置。
基本用法
可以通过提供类名和服务定义将服务添加到容器中。
$hodl->add('Some\Namespace\Foo', function() { return new Foo(); });
你应该始终使用完整的类名注册服务。这样,自动装配就可以正常工作,并且类可以无障碍地注入依赖项。
获取服务
非常简单
$foo = $hodl->get('Some\Namespace\Foo');
检查服务是否存在
由于所有服务都通过你定义的键进行引用,因此你可以使用has()
来检查该键是否已定义
use Namespace\Foo; // using the ::class shorthand $hodl->add(Foo::class, function() { return new Foo(); }); $hodl->has(Foo::class); // true $hodl->has('some\other\class'); // false
删除服务
由于容器实现了ArrayAccess,因此你可以使用unset()
或remove()
方法来删除类
$hodl['foo'] = function(){ return new Foo(); }; $hodl->has('foo'); // true $hodl->remove('foo'); $hodl->has('foo'); // false
删除服务还将删除任何别名或绑定的接口(下面有更多介绍)。
ArrayAccess样式
由于Hodl实现了ArrayAccess
,你可以像这样实现上述功能
// add $hodl['Some\Namespace\Foo'] = function(){ return new Foo(); }; // get $foo = $hodl['Some\Namespace\Foo']; // check if (isset($hodl['Some\Namespace\Foo')) // ... // remove unset($hodl['Some\Namespace\Foo']);
服务定义
当添加新的服务定义时,返回类的callable
将传递一个Hodl
的实例,可以用来传递来自容器中已存在的服务的参数。 注意 你不应该直接将服务传递到你的服务构造函数中。为此,我们有自动装配的魔力。
$hodl->get('Baz', function($hodl) { return new Baz($hodl->get('Some\Namespace\Foo')->someProp); });
单例
上面的示例将在每次获取时返回服务的新实例。你也可以使用addFactory()
方法指定每次都应该返回相同的实例。
$hodl->addSingleton(Bar::class, function() { return new Bar(); });
实例
你也可以将特定的实例作为服务添加。由于它已经启动,Hodl
可以很容易地推导出类的名称,因此无需提供。
$instance = new Foo\Bar(); $instance->prop = 'foobar'; $hodl->addInstance($instance); // ... $hodl->get('Foo\Bar')->prop // equals 'foobar'
别名
有时输入完整的类名以访问服务很麻烦。幸运的是,你也可以为服务定义一个别名以快速检索
// using the ::class shorthand $hodl->add(Foo::class, function() { return new Foo(); }); // Adda alias. $hodl->alias(Foo:class, 'myAlias'); $hodl->has(Foo::class); // true $hodl->has('myAlias'); // true
删除别名
如果在某个时候你需要删除别名或绑定(下面有更多介绍),则可以使用removeAlias($alias)
方法。
自动装配(解决依赖项)
除了作为传递对象的容器外,它还可以使用反射API自动解决对象,实现控制反转。
考虑以下对象
namespace Foo; class Foo { function __construct( Bar $bar ) { $this->bar = $bar; } }
当此对象被创建时,它需要传递一个Foo\Bar
实例作为第一个参数。
使用resolve()
方法,这非常简单
$foo = $hodl->resolve('Foo\Foo');
对象将被创建,并且将自动传递一个Foo\Bar
实例。
这可以递归地进行,因此Foo\Bar
的任何依赖项都将神奇地解决。
传递参数
resolve方法还接受第二个参数,它是一个数组,包含你想传递给对象构造函数的额外参数。
该数组的键必须是参数的变量名。
// using the Foo class in the previous example, but with a following constructor: // function __construct( Bar $bar, int $someInt, string $someString = 'string' ) { ... // by not passing someString as a key, the default of 'string' will be used $foo = $hodl->resolve('Foo\Foo', [ 'someInt' => 42 ]);
使用服务解析
上述示例中有一个空容器,因此所有服务都被注入为该类的新的泛型实例。但如果容器中已存在该服务,则会使用该服务而不是新实例 - 这允许您将特定的实例或持久对象传递给需要它的任何对象。
class Foo { public $var = 'foo'; } class Bar { public $foo; public function __construct(Foo $foo) { $this->foo = $foo; } } // Add Foo as a singleton $hodl->addSingleton('Foo', function() { return new Foo(); }); $hodl['Foo']->var = 'changed!'; $var = $hodl->resolve('Bar')->foo->var; // equals 'changed!'
在向容器添加服务定义时,也不必害怕使用 resolve()
!
$hodl->add('Bar', function($hodl) { return $hodl->resolve('Bar'); // All of Bar's dependencies will be injected as soon as it is fetched });
将实现绑定到接口
在使用自动装配功能时,一个非常有用的功能是能够在构造函数中指定一个接口,并由 Hodl 处理将正确的实现传递给解析的类。
请考虑以下内容
// Basic interface interface HelloWorld { public function output(); } // Service class NeedsResolving { public function __construct(HelloWorld $writer) { $this->writer = $writer; } public function output() { $this->writer->output(); } }
我们知道 NeedsResolving
类需要某种 HelloWorld
实现才能实际工作。我们可以使用 bind()
方法让 Hodl 知道是哪一个。
class MyPrinter implements HelloWorld { public function output() { echo 'Hello world!'; } } $hodl->bind(MyPrinter::class, HellowWorld::class); // Correctly gets an instance of MyPrinter $foo = $hodl->resolve(NeedsResolving::class); $foo->output(); // Outputs 'Hello world!'
移除绑定
由于底层的 bind()
是 alias()
的别名,因此 removeAlias($interface)
方法将移除一个绑定。如果您需要将实现热插拔到另一个实现,这非常有用。
解析方法
resolveMethod($class, $methodName, $args)
方法允许以与 resolve()
在类上工作相同的方式自动装配类成员。
$hodl->resolveMethod(Foo::class, 'someMethod');
resolveMethod
将调用提供的函数,递归注入依赖关系,并允许您根据上述 resolve
示例传递额外的非对象参数。这同样适用于静态方法以及公共方法。
解析实例方法
上述示例显示了在 Foo
的新实例上执行和返回 someMethod
,但您也可以传递一个特定的实例而不是类名。
$foo = new Foo(); $return = $hodl->resolveMethod($foo, 'someMethod', ['amount_of_awesome' => 100]);
因此,可以同时使用 resolve
和 resolveMethod
来创建一个全新解析的对象并执行一个方法。
class Bar { public $foo; public function __construct(Foo $foo) { $this->foo = $foo; } public function methodName(Foo\Baz $baz) { return $this->foo->var * $baz->var; } } // Fully resolves methodName and returns an instance of Foo\Baz $resolvedBaz = $hodl->resolveMethod( $hodl->resolve('Bar'), 'methodName' );
结论
通过向 Hodl 添加服务,您的代码可以实现完全的控制反转,并在全局范围内管理类,无需使用单个 new
关键字或单例。
贡献
如果您有任何改进、错误或功能请求,请随时提出问题或 PR。