首页 » Laravel 5.3 中文文档 » 正文

「Laravel 5.3 中文文档」核心概念 – 服务容器

介绍

Laravel 服务容器是一个管理类的依赖及提供依赖注入的强大的工具。依赖注入是个花俏的名词,事实上是指:类的依赖通过构造函数或在某些类中通过 setter 方法将类依赖注入到类中。

让我们看一个简单的例子:

<?php

namespace App\Http\Controllers;

use App\User;
use App\Repositories\UserRepository;
use App\Http\Controllers\Controller;

class UserController extends Controller
{
    /**
     * The user repository implementation.
     *
     * @var UserRepository
     */
    protected $users;

    /**
     * Create a new controller instance.
     *
     * @param  UserRepository  $users
     * @return void
     */
    public function __construct(UserRepository $users)
    {
        $this->users = $users;
    }

    /**
     * Show the profile for the given user.
     *
     * @param  int  $id
     * @return Response
     */
    public function show($id)
    {
        $user = $this->users->find($id);

        return view('user.profile', ['user' => $user]);
    }
}

在这个例子中,UserController 需要从一个数据源获取数据。所以我们需要注入一个能够获取用户的服务。这里,UserRepository 应该是使用 Eloquent 从数据库获取用户信息。然而,repository 是注入的,所以我们很容易使用另一个实现来替换它。当测试应用时,我们能够轻松的“mock”或创建一个 UserRepository 的模拟实现。

要创建一个强大的应用,以及为 Laravel 核心代码做贡献,深入了解 Laravel 服务容器都是必要的。

绑定

基础绑定

几乎所有的服务容器绑定都需要使用服务提供者进行绑定。所以大部分的例子都会示范使用容器。

如果类不依赖任何接口的话,那就没有必要绑定类到服务容器。。并不需要为容器指定如何建构这些对象,因为它会通过 PHP 的反射服务自动解析具体的对象。

简单绑定

服务提供者中,你经常需要通过 $this->app 属性访问容器。我们可以使用 bind 注册一个绑定,传递我们希望注册的类或接口名称,并连同返回该类实例的闭包

$this->app->bind('HelpSpot\API', function ($app) {
    return new HelpSpot\API($app->make('HttpClient'));
});

注意到我们接受容器本身作为解析器的一个参数,然后我们可以使用该容器来解析我们正在构建的对象的子依赖。

绑定一个单例

singleton 方法绑定一个只需要解析一次的类或接口到容器,一旦单例绑定被取出,接下来对容器的调用将会返回同一个实例:

$this->app->singleton('HelpSpot\API', function ($app) {
    return new HelpSpot\API($app->make('HttpClient'));
});

绑定实例

你还可以通过 instance 方法绑定一个已存在的对象实例到服务容器。后面的调用都会从容器中返回指定的实例:

$api = new HelpSpot\API(new HttpClient);

$this->app->instance('HelpSpot\Api', $api);

原始绑定

有时候你可能需要一个类接收一些注入的类,并且也需要一个原始注入值,如整型。你也可以轻松的使用上下文绑定来注入你的类可能需要的任何值:

$this->app->when('App\Http\Controllers\UserController')
          ->needs('$variableName')
          ->give($value);

绑定接口到实现

服务容器一个强大的功能是它绑定接口到实现的能力。例如,假定我们有一个 EventPusher 接口和一个 RedisEventPusher 实现。一旦我们为这个接口编写了 RedisEventPusher 实现,就可以这样将它注册至服务容器:

$this->app->bind(
    'App\Contracts\EventPusher',
    'App\Services\RedisEventPusher'
);

这段代码告诉容器当一个类需要 EventPusher 的实现时将会注入 RedisEventPusher,现在我们可以在构造器或者任何其它通过服务容器注入依赖的地方进行 EventPusher 接口的类型提示:

use App\Contracts\EventPusher;

/**
 * Create a new class instance.
 *
 * @param  EventPusher  $pusher
 * @return void
 */
public function __construct(EventPusher $pusher)
{
    $this->pusher = $pusher;
}

上下文绑定

有时侯你可能有两个类需要使用同一个接口,但你希望在每个类中注入不同实现。例如,两个控制器可能依赖 Illuminate\Contracts\Filesystem\Filesystem contract 不同的实现。Laravel 定义了一个简单、平滑的方式来定义这种行为:

use Illuminate\Support\Facades\Storage;
use App\Http\Controllers\PhotoController;
use App\Http\Controllers\VideoController;
use Illuminate\Contracts\Filesystem\Filesystem;

$this->app->when(PhotoController::class)
          ->needs(Filesystem::class)
          ->give(function () {
              return Storage::disk('local');
          });

$this->app->when(VideoController::class)
          ->needs(Filesystem::class)
          ->give(function () {
              return Storage::disk('s3');
          });

标签

偶尔你可能需要获取一个指定“分类”的所有绑定,例如你可能绑定一个报告聚合器接收一组不同 Report 接口实现。注册 Report 实现后,可以通过 tag 方法给它们分配一个标签:

$this->app->bind('SpeedReport', function () {
    //
});

$this->app->bind('MemoryReport', function () {
    //
});

$this->app->tag(['SpeedReport', 'MemoryReport'], 'reports');

当服务被打上标签后,你可以通过 tagged 方法轻松获取它们:

$this->app->bind('ReportAggregator', function ($app) {
    return new ReportAggregator($app->tagged('reports'));
});

获取

make 方法

你可以使用 make 方法从容器中获取一个类的实例。make 方法接收你想要获取的类或接口的名字作为参数:

$api = $this->app->make('HelpSpot\API');

如果你想在一个不能访问 $app 变量的地方编码,你可以使用全局 resolve 辅助方法:

$api = resolve('HelpSpot\API');

自动注入

或者,最重要的,你可以在类的构造器简单地对依赖使用「类型提示」,类将会从容器中进行解析,包含 控制器事件侦听器队列任务中间件 等等。在实际情形中,这就是为何大部分的对象都是在容器中获取的。

举个例子,你可以在控制器的构造器中对应用程序定义的 repository 进行类型提示。repository 会自动被解析及注入至类中:

<?php

namespace App\Http\Controllers;

use App\Users\Repository as UserRepository;

class UserController extends Controller
{
    /**
     * The user repository instance.
     */
    protected $users;

    /**
     * Create a new controller instance.
     *
     * @param  UserRepository  $users
     * @return void
     */
    public function __construct(UserRepository $users)
    {
        $this->users = $users;
    }

    /**
     * Show the user with the given ID.
     *
     * @param  int  $id
     * @return Response
     */
    public function show($id)
    {
        //
    }
}

容器事件

每当服务容器获取一个对象时就会触发事件。你可以使用 resolving 方法监听这个事件:

$this->app->resolving(function ($object, $app) {
    // Called when container resolves object of any type...
});

$this->app->resolving(HelpSpot\API::class, function ($api, $app) {
    // Called when container resolves objects of type "HelpSpot\API"...
});

正如你所看到的,获取的对象会被传递给回调,从而允许你在对象被传递给消费者之前为其设置额外属性。


该篇属于专题:《Laravel 5.3 中文文档

本文共 2 个回复

  • c850241964 2016/11/03 11:31

    求问:larval的构造函数内的依赖注入值通过什么原理实现的。底层代码在哪里 ?

发表评论