服务容器

未匹配的标注
本文档最新版为 10.x,旧版本可能放弃维护,推荐阅读最新版!

服务容器#

简介#

Laravel 服务容器是管理类依赖和运行依赖注入的有力工具。依赖注入是一个花俏的名词,它实质上是指:类的依赖通过构造器或在某些情况下通过「setter」方法进行「注入」。

来看一个简单的例子:

<?php

namespace App\Http\Controllers;

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

class UserController extends Controller
{
    /**
     * User Repository 的实现。
     *
     * @var UserRepository
     */
    protected $users;

    /**
     * 创建新的控制器实例。
     *
     * @param  UserRepository  $users
     * @return void
     */
    public function __construct(UserRepository $users)
    {
        $this->users = $users;
    }

    /**
     * 显示指定用户的详细信息。
     *
     * @param  int  $id
     * @return Response
     */
    public function show($id)
    {
        $user = $this->users->find($id);

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

在这个例子中,控制器 UserController 需要从数据源中获取 users 。因此,我们要 注入 可以获取 users 的服务。在这种情况下, UserRepository 可能是通过使用 Eloquent 来从数据库中获取 user 信息。因为 UserRepository 是通过注入获取,所以我们可以容易地切换为其他实现。当测试应用程序时,我们还可以轻松地 「mock」 ,或创建假的 UserRepository 实例。

在构建强大的应用程序,和为 Laravel 核心贡献代码时,必须深入理解 Laravel 的服务容器。

绑定#

绑定基础#

几乎所有服务容器的绑定都是在 服务提供者 中进行的,所以下面的例子将示范在该情景中使用容器。

{tip} 但是,如果类没有依赖任何接口,那么就没有必要将类绑定到容器中了。容器绑定时,并不需要指定如何构建这些类,因为容器中会通过 PHP 的反射自动解析对象。

简单绑定#

在服务提供者中,你经常可以通过 $this->app 属性访问容器。我们可以通过 bind 方法注册一个绑定,通过传递注册类或接口的名称、及返回该实例的 Closure 作为参数:

$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 契约 的实现类。 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 方法来获得一个实例化的类。它接受你希望解析的类或是接口名称作为参数:

$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
{
    /**
     * user repository 实例。
     */
    protected $users;

    /**
     * 控制器构造方法。
     *
     * @param  UserRepository  $users
     * @return void
     */
    public function __construct(UserRepository $users)
    {
        $this->users = $users;
    }

    /**
     * 显示指定 ID 的用户信息。
     *
     * @param  int  $id
     * @return Response
     */
    public function show($id)
    {
        //
    }
}

容器事件#

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

$this->app->resolving(function ($object, $app) {
    // 解析任何类型的对象时都会调用该方法...
});

$this->app->resolving(HelpSpot\API::class, function ($api, $app) {
    // 解析「HelpSpot\API」类型的对象时调用...
});

如你所见,被解析的对象会被传递至回调中,让你在对象被传递到消费者前可以设置任何额外属性到对象上。

译者署名#

用户名 头像 职能 签名
@yangjingqzp 100 翻译 大神们请多多指教,@yangjingqzp at Github

{note} 欢迎任何形式的转载,但请务必注明出处,尊重他人劳动共创开源社区。

转载请注明:本文档由 Laravel China 社区 [laravel-china.org] 组织翻译,详见 翻译召集帖

文档永久地址: d.laravel-china.org

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 2
发起讨论 只看当前版本


Zealot
有人和我一样很难理解此章节吗?
4 个点赞 | 40 个回复 | 问答 | 课程版本 5.6