bhittani / container
PSR-11 依赖注入容器实现,具有自动解析、服务提供者、外观和宏功能。
Requires
- php: ^7.2
- psr/container: ^1.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^2.18
- phpunit/phpunit: ^8.5
Provides
- psr/container-implementation: 1.0.0
README
PSR-11 依赖注入容器实现,具有自动解析、服务提供者、外观和宏功能。本包不依赖于任何外部库。
安装
您可以使用 composer 安装此包。
$ composer require bhittani/container --prefer-dist
使用
PSR-11 实现
此包实现了 PSR-11 容器接口,因此您可以轻松地将任何现有实现与该包中提供的容器进行交换。
容器
在其最简单形式中,容器存储键值对,以便在您的应用程序生命周期中稍后访问。
<?php require_once __DIR__ . '/vendor/autload.php'; $container = new \Bhittani\Container\Container; $container->add('foo', 'bar'); echo $container->get('foo'); // 'bar'
绑定解析
实际上,依赖注入容器更有用,因为它存储类工厂/实例,以便它们可以自动解析。
<?php require_once __DIR__ . '/vendor/autload.php'; class FooDatabase { // ... } $container = new Bhittani\Container\Container; $container->add(FooDatabase::class, new FooDatabase); $db = $container->get(FooDatabase::class);
正在使用的键 FooDatabase
非常重要。此键将在解析绑定参数时(在类构造函数、方法、闭包、可调用对象等)作为查找任何类类型提示的依据。
仍然感到困惑?让我们通过一个实际示例来更深入地了解。
<?php require_once __DIR__ . '/vendor/autload.php'; class FooDatabase { // ... } class Query { protected $db; public function __construct(FooDatabase $db) { $this->db = $db; } } $container = new Bhittani\Container\Container; $container->add(FooDatabase::class, new FooDatabase); $query = $container->get(Query::class); // Query
在这里,$db
被容器自动解析,因为它使用容器已知的 'FooDatabase' 类进行了类型提示。
自动依赖解析
绑定解析非常方便,但我们可以做得更好,改进我们的第一次迭代。
如果我们仔细查看前面的代码示例,我们会看到我们显式地将 FooDatabase
类绑定到容器中,但 Query
类是隐式解析的,没有显式绑定。
这意味着,我们也应该能够隐式解析 FooDatabase
类。
让我们应用我们的第一次重构。
<?php require_once __DIR__ . '/vendor/autload.php'; class FooDatabase { // ... } class Query { protected $db; public function __construct(FooDatabase $db) { $this->db = $db; } } $container = new Bhittani\Container\Container; $query = $container->get(Query::class); // Query
如果您没有注意到,代码行 $container->add(FooDatabase::class, new FooDatabase);
完全被删除,因为不需要绑定。
这是如何工作的?让我们暂时深入幕后看看实际上发生了什么。
当您调用容器的 get
方法时,
- 容器识别出键为一个存在的类。
- 它查看构造函数参数,并注意到有一个参数类型提示为
FooDatabase
类。 - 为了解析此参数,它使用类型提示作为键重复步骤 1 和 2。
- 它看不到
FooDatabase
类的任何构造函数,因此它实例化它并使用该实例来实例化Query
类。
绑定将优先于新实例化。
接口解析
如果我们能够实现到一个接口,以便我们可以轻松地交换底层实现,那岂不是很好?
<?php require_once __DIR__ . '/vendor/autload.php'; interface DatabaseInterface { // ... } class FooDatabase implements DatabaseInterface { // ... } class BarDatabase implements DatabaseInterface { // ... } class Query { public $db; public function __construct(DatabaseInterface $db) { $this->db = $db; } } $container = new Bhittani\Container\Container; $container->add(DatabaseInterface::class, new FooDatabase); $query = $container->get(Query::class); echo $query->db instanceof FooDatabase; // true $container->add(DatabaseInterface::class, new BarDatabase); $query = $container->get(Query::class); echo $query->db instanceof BarDatabase; // true
我们已轻松地将底层数据库实现从 FooDatabase
更换为 BarDatabase
。
可调用解析
要解析可调用对象/闭包,我们可以直接调用它。
$container = new Bhittani\Container\Container; class Acme { // ... } $container->call(function (Acme $acme) { echo $acme instanceof Acme; // true });
自定义参数解析
我们可以以两种方式解析需要自定义参数的实体。
- 将自定义参数绑定到容器中。
- 传递显式参数。
class Acme { public $foo; public function __construct($foo) { $this->foo = $foo; } } $container = new Bhittani\Container\Container; $container->add('foo', 'bar'); $acme = $container->get(Acme::class); echo $acme->foo; // bar $acme = $container->get(Acme::class, ['foo' => 'baz']); echo $acme->foo; // baz
显式参数将优先于绑定。
工厂绑定
工厂绑定允许懒加载实例。这意味着只有在需要时才会进行解析。
<?php require_once __DIR__ . '/vendor/autload.php'; interface DatabaseInterface { // ... } class FooDatabase implements DatabaseInterface { // ... } class Query { public function __construct(DatabaseInterface $db) { // ... } } $container = new Bhittani\Container\Container; $container->add(DatabaseInterface::class, function () { return new FooDatabase; }); $query = $container->get(Query::class); // will trigger the factory closure above.
这样,您可以在容器中添加任意数量的绑定,但只有当需要时才触发/解析它们。因此,它是懒加载的。
共享绑定
共享绑定允许在访问时解析相同的实例,而不是每次需要时都创建新实例。
$container = new Bhittani\Container\Container; // This will be shared as we are not using a factory. $container->add(DatabaseInterface::class, new FooDatabase); // This will be shared as we are using the method 'share' explicitly. $container->share(DatabaseInterface::class, function () { return new FooDatabase; }); // This will NOT be shared as we are using a factory. $container->add(DatabaseInterface::class, function () { return new FooDatabase; });
代理
委托容器充当后备容器,在容器中找不到绑定时用于绑定解析。
use Bhittani\Container\Container; $container = new Container; $container->has('foo'); // false $delegateContainer = new Container; // Or any PSR-11 container. $delegateContainer->add('foo', 'bar'); $container->delegate($delegateContainer); $container->has('foo'); // true
服务提供者
此包还附带了一个服务提供者容器,允许注册服务提供者(例如 Laravel 服务提供者),以便能够轻松流畅地进行应用开发。
为了使用服务提供者,我们将使用 ServiceContainer
而不是简单的 Container
。
use Bhittani\Container\ServiceContainer; use Bhittani\Container\ServiceProvider; class DatabaseServiceProvider extends ServiceProvider { public function boot($container) { // This method will be called when all service providers are registered. } public function register($container) { $container->share(DatabaseInterface::class, function () { return new SqliteDatabase; }); } } $container = new ServiceContainer; $container->addServiceProvider(DatabaseServiceProvider::class); $container->bootstrap(); $db = $container->get(DatabaseInterface::class); echo $db instanceof SqliteDatabase; // true
外观
门面通过将自定义属性分配到服务容器来扩展容器。当访问时,它将从中解析出来。
use Bhittani\Container\ServiceContainer; $container = new ServiceContainer; $container->share(DatabaseInterface::class, function () { return new SqliteDatabase; }); $container->db = DatabaseInterface::class; echo $container->db instanceof SqliteDatabase; // true
宏
宏通过将自定义方法分配到服务容器来扩展容器。
use Bhittani\Container\ServiceContainer; $container = new ServiceContainer; $container->share(DatabaseInterface::class, function () { return new SqliteDatabase; }); $container->macro('query', function ($sql) { // $this will be set to the underlying ServiceContainer. return $this->get(DatabaseInterface::class)->query($sql); }); echo $container->query('SELECT * FROM users'); // Invokes the 'query' macro.
延迟服务提供者
服务提供者可以被延迟,以便可以懒加载服务。
use Bhittani\Container\ServiceContainer; use Bhittani\Container\ServiceProvider; class DatabaseServiceProvider extends ServiceProvider { // A non empty $provides array will defer this service provider. protected $provides = [ DatabaseInterface::class, // If setting a facade, use the facade key as the index. // 'db' => DatabaseInterface::class, ]; // A non empty $macros array will defer this service provider as well. protected $macros = [ 'query', ]; public function register($container) { $container->share(DatabaseInterface::class, function () { return new SqliteDatabase; }); $container->macro('query', function ($sql) { return $this->get(DatabaseInterface::class)->query($sql); }); } } $container = new ServiceContainer; $container->addServiceProvider(DatabaseServiceProvider::class); $container->bootstrap(); // This will register and boot the service provider. $container->query('SELECT * FROM users');
使用延迟服务提供者是一种高效构建应用的方式,因为这些服务将被懒加载,并且作为即插即用模块运行,同时对应用性能的影响最小。
更新日志
有关更改的更多信息,请参阅 变更日志。
测试
git clone https://github.com/kamalkhan/container cd container composer install composer test
贡献
安全
如果您发现任何与安全相关的问题,请通过电子邮件 [email protected]
而不是使用问题跟踪器。
灵感
致谢
许可证
MIT 许可证 (MIT)。有关更多信息,请参阅 许可文件。