一个轻量级的PSR-11依赖注入容器,位于Pimple和Laravel容器之间

1.7.0 2023-03-28 18:41 UTC

This package is auto-updated.

Last update: 2024-08-28 22:49:33 UTC


README

Travis PHP from Packagist

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]);

因此,可以同时使用 resolveresolveMethod 来创建一个全新解析的对象并执行一个方法。

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。