Laravel 的容器绑定机制有何不同?

How Laravel's container binding mechanisms differ?

我正在查看 Laravel 的 service container docs,特别是绑定部分。

每种绑定有什么区别以及我应该在什么时候使用它们?文档提到:

首先,让我们看看它到底是什么:

IoC container is a component that knows how instances are created. It also knows of all their underlying dependencies and how to resolve them.

容器关于实例创建和依赖解析的知识可能是由程序员指导的。这就是 Laravel 的容器为我和你提供各种绑定 API 的原因。

"解析出容器"这句话你read/hear很多。这意味着您告诉容器根据您之前给她的 [可选] 指导为您制作一些东西。

在您继续阅读绑定之前,我强烈建议您阅读这个答案:
What is Laravel IoC Container in simple words?

简单绑定

app()->bind(DatabaseWriter::class, function ($app) {
    return new DatabaseWriter(
        $app->make(DatabaseAdapterInterface)
    );
});

你对容器说,当你想解析 DatabaseWriter class 的实例时,请遵循我刚刚在闭包中告诉你的逻辑,因为我知道得更多。每次你想解决class,你必须按照这个给我一个新实例

你一直在使用这种类型的绑定。您正在为容器提供有关如何为您制作东西的小食谱。

单例绑定

与简单绑定相同,只有一处明显不同。您是在告诉容器我在整个应用程序中只需要这个 class 的一个实例。第一次解析 class 时,请遵循我传递给您的闭包中的逻辑,但请确保您只是 return 每隔一次您想要解析它的唯一实例。给我 唯一一个你被允许制作的实例

是单例吧?一旦解决了单例绑定,相同的对象实例将在后续调用容器时被 returned。

显然,当您想使用单例模式时,您会使用这种类型的绑定。这些天很少见。

实例绑定

这就像帮容器一个忙。你不告诉她如何实例化某个class,你自己做,把实例给她就行了。她为您保留它,并在随后调用容器时 return 发送它。

进行单元测试时特别方便。如果您将模拟实例绑定到某些 class 的容器,所有对 app()->make() 的后续调用都将 return 为您模拟。因此,当使用实际 class 时,您实际上是在整个应用程序中注入 class 模拟。

class QuestionsControllerTest extends TestCase 
{  
    public function testQuestionListing()
    {
        $questionMock = Mockery::mock('Question')
           ->shouldReceive('latest')
           ->once()
           ->getMock();

        // You're telling the container that everytime anyone
        // wants a Question instance, give them this mock I just
        // gave you.
        $this->app->instance('Question', $this->questionMock);

        // whatever...
    }
}

原始绑定

Laravel 的容器提供了一个 DSL,让你告诉她如何解析原语。你说什么时候BillingControllerclass要一个$taxRate变量没传,就给0.2。这就像从很远很远的地方设置默认值!

app()->when('App\Http\Controllers\BillingController')
     ->needs('$taxRate')
     ->give(.2);

用例可能很少见,但您可能偶尔需要它们。这个例子可能更感性一点:

app()->when('App\Http\Controllers\CustomerController')
    ->needs('$customers')
    ->give(function() {
        return Customer::paying();
    });

接口绑定

当您想将接口绑定到具体实现时使用它。

在阅读了十几篇关于 SOLID 以及如何成为更好的程序员的文章后,您决定遵循 依赖倒置 原则,而不是依赖于具体实例,而是依赖于抽象。

毕竟,无论在 Laravel 之内还是之外,这都是一种很好的做法。

class DatabaseWriter {

    protected $db;

    // Any concrete implementation of this interface will do
    // Now, that I depend on this DatabaseAdapterInterface contract,
    // I can work with MySQL, MongoDB and WhatevaDB! Awesome!
    public function __construct(DatabaseAdapterInterface $db)
    {
        $this->db = $db;
    }

    public function write()
    {
        $this->db->query('...');
    }

}

没有Laravel的容器,你首先需要创建一个DatabaseAdapterInterface的具体实现,并通过DatabaseWriter的构造函数传递给它,以便能够实例化它:

$dbWriter = new DatabaseWriter(new MongodbAdapter)

如果 MongodbAdapter 有自己的依赖项,您可能会在这里结束:

// Looks familiar, right? 
// These are those recipes you used to give to Laravel container 
// through simple binding. 
$dbWriter = new DatabaseWriter(new MongodbAdapter(new MongodbConnection))

但是有了 Laravel 的容器,你告诉她,当有人要求 DatabaseAdapterInterface 的具体实现时,不要再问了,给他们一个 MongodbAdapter

app()->bind(DatabaseAdapterInterface::class, MongodbAdapter::class)

然后你继续从容器中解析 DatabaseWriter 的实例,就像老板一样:

$dbWriter = app()->make(DatabaseWriter::class)

更简单、更干净,对吧?您删除所有明显的混乱并将其移动到其他地方。你的 AppServiceProvider 也许吧。

好吧,让我们看看她在这个场景中是如何工作的。首先,她探测 DatabaseWriter 可能的依赖关系(通过 reflection),发现它需要 DatabaseAdapterInterface。检查她的笔记本,记得你告诉她 MongodbAdapter 是那个接口的具体实现。做一个交给DatabaseWriter.

如果您坚持依赖倒置原则,您几乎一直都在使用这些类型的绑定。

好了,废话少说,让我们看看她是如何工作的:
https://github.com/laravel/framework/blob/5.3/src/Illuminate/Container/Container.php#L627