bhittani/container

PSR-11 依赖注入容器实现,具有自动解析、服务提供者、外观和宏功能。

0.6.0 2021-01-28 10:17 UTC

This package is auto-updated.

Last update: 2024-08-28 17:49:39 UTC


README

Build Status Packagist Downloads License

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 方法时,

  1. 容器识别出键为一个存在的类。
  2. 它查看构造函数参数,并注意到有一个参数类型提示为 FooDatabase 类。
  3. 为了解析此参数,它使用类型提示作为键重复步骤 1 和 2。
  4. 它看不到 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
});

自定义参数解析

我们可以以两种方式解析需要自定义参数的实体。

  1. 将自定义参数绑定到容器中。
  2. 传递显式参数。
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)。有关更多信息,请参阅 许可文件