laravel 核心概念 —— 服务容器

一、简介

Laravel 服务容器是一个用于管理类依赖和执行依赖注入的强大工具。依赖注入听上去很花哨,其实质是经过构造函数或者某些状况下经过 set 方法将类依赖注入到类中。php

让咱们看一个简单的例子:laravel

<?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 从数据库获取用户信息。注入 UserRepository 后,咱们能够在其基础上封装其余实现,也能够模拟或者建立一个假的 UserRepository 实现用于测试。数据库

深刻理解 Laravel 服务容器对于构建功能强大的大型 Laravel 应用而言相当重要,对于贡献代码到 Laravel 核心也颇有帮助。api

二、绑定

绑定基础

几乎全部的服务容器绑定都是在服务提供者中完成。所以本章节的演示例子用到的容器都是在服务提供者中绑定。闭包

注:若是一个类没有基于任何接口那么就没有必要将其绑定到容器。容器并不须要被告知如何构建对象,由于它会使用 PHP 的反射服务自动解析出具体的对象。app

简单的绑定ide

在一个服务提供者中,能够经过 $this->app 变量访问容器,而后使用 bind 方法注册一个绑定,该方法须要两个参数,第一个参数是咱们想要注册的类名或接口名称,第二个参数是返回类的实例的闭包:函数

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

注意到咱们将容器自己做为解析器的一个参数,而后咱们可使用该容器来解析咱们正在构建的对象的子依赖。工具

绑定一个单例post

singleton 方法绑定一个只须要解析一次的类或接口到容器,而后接下来对容器的调用将会返回同一个实例:

$this->app->singleton('FooBar', function ($app) {
    return new FooBar($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;

/**
 * 建立一个新的类实例
 *
 * @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\VideoController;
use App\Http\Controllers\PhotoControllers;
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 方法,该方法接收你想要解析的类名或接口名做为参数:

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

若是你所在的代码位置访问不了$app变量,可使用辅助函数app

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

自动注入

最后,也是最经常使用的,你能够简单的经过在类的构造函数中对依赖进行类型提示来从容器中解析对象,控制器、事件监听器、队列任务、中间件等都是经过这种方式。在实践中,这是大多数对象从容器中解析的方式。

容器会自动为其解析类注入依赖,例如,你能够在控制器的构造函数中为应用定义的仓库进行类型提示,该仓库会自动解析并注入该类:

<?php

namespace App\Http\Controllers;

use Illuminate\Routing\Controller;
use App\Users\Repository as UserRepository;

class UserController extends Controller{
    /**
     * 用户仓库实例
     */
    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) {
    // 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"...
});

正如你所看到的,被解析的对象将会传递给回调函数,从而容许你在对象被传递给消费者以前为其设置额外属性。

相关文章
相关标签/搜索