🦄 2024 独立开发者训练营,一起创业!查看介绍 / 立即报名(剩余10个优惠名额) →

Laravel 5 中文手册(十):服务容器 Service Container

介绍

Laravel Service Container 是管理类的依赖的强大的工具。依赖注入(Dependency injection)其实就是使用类的构造函数或者 setter 方法,把类所依赖的东西注入到类里面。

来看个简单的示例:

<?php namespace App\Handlers\Commands;

use App\User;
use App\Commands\PurchasePodcast;
use Illuminate\Contracts\Mail\Mailer;

class PurchasePodcastHandler {

    /**
     * The mailer implementation.
     */
    protected $mailer;

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

    /**
     * Purchase a podcast.
     *
     * @param  PurchasePodcastCommand  $command
     * @return void
     */
    public function handle(PurchasePodcastCommand $command)
    {
        //
    }

}

在上面例子里,PurchasePodcast 命令处理器需要在有人购买了 podcast 以后去发送 e-mail 。所以,我们要去注入一个可以发送邮件的服务。因为这个服务是注入进来的,所以我们就可以很容易把它换成其它的实施。 在 “mock” 的时候也会很容易,或者在测试应用的时候,去创建一个假的 mailer 实施。

要去创建大型的应用,或者为 Laravel 核心做贡献,都需要深入的理解 Laravel Service Container 。

基本的使用

Binding

基本上你所有的 Service Container 的绑定都是在 Service Providers 里面注册的,下面所有这些示例都是在这个上下文里面演示使用容器的。如果在你的应用的其它的地方需要一个容器的实例,比如 factory ,你需要 type-hint Illuminate\Contracts\Container\Container Contract,这样容器的实例就会为你注入进来,你可以使用 App facade 来访问这个容器。

注册一个基本的 Resolver

在 Service Provider 里面,你可以通过 $this->app 实例变量来访问到容器。

有几种方法可以注册 Service Container 的依赖,比如 Closure 回调,还有在实施上绑定界面。首先,我们看一下 Closure 回调。在容器里注册的 Closure Resolver,会带一个 key(一般就是类的名字) ,还有一个可以返回值的 Closure 。

$this->app->bind('FooBar', function($app)
{
    return new FooBar($app['SomethingElse']);
});

注册一个 Singleton

有时候,你可能想要去绑定只会 Resolve 一次的东西到容器上,在后面调用这个容器的时候会返回这个实例:

$this->app->singleton('FooBar', function($app)
{
    return new FooBar($app['SomethingElse']);
});

在容器上绑定已有的实例

使用 instance 方法,你可以在容器上绑定一个已有的对象实例。这个实例会一直在随后调用容器的时候返回:

$fooBar = new FooBar(new SomethingElse);

$this->app->instance('FooBar', $fooBar);

Resolving

有几种方法可以从容器里 resolve 些东西出来。比如使用 make 方法:

$fooBar = $this->app->make('FooBar');

你也可以在容器上使用数组访问,因为它实施了 PHP 的 ArrayAccess 接口:

$fooBar = $this->app['FooBar'];

最后,也是最重要的, 你只需要在类的构造函数里边 type-hint 依赖,包括控制器,事件监听器(Event Listeners),队列工作(Queue jobs),过滤器等等。容器会自动注入依赖:

<?php namespace App\Http\Controllers;

use Illuminate\Routing\Controller;
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)
    {
        //
    }

}

绑定接口到实施

注入固定的依赖

Service Container 很强大的一个功能就是可以绑定接口到一个实施上。比如,你的应用可以要整合 Pusher 这个 web service,它可以发送跟接收实时的事件。如果你使用 Pusher 的 PHP SDK,我们可以注入一个 Pusher 客户端的实例到一个类里面:

<?php namespace App\Handlers\Commands;

use App\Commands\CreateOrder;
use Pusher\Client as PusherClient;

class CreateOrderHandler {

    /**
     * The Pusher SDK client instance.
     */
    protected $pusher;

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

    /**
     * Execute the given command.
     *
     * @param  CreateOrder  $command
     * @return void
     */
    public function execute(CreateOrder $command)
    {
        //
    }

}

在上面这个例子里,注入类的依赖是个不错的主意,不过,我们紧耦合到了 Pusher SDK。如果 Pusher SDK 方法更了,或者我们想使用其它的事件服务,我们需要修改 CreateOrderHandler 的代码。

编码到一个接口

为了可以让 CreateOrderHandler 跟事件推送绝缘,我们可以去定义一个 EventPusher 接口,还有一个 PusherEventPusher 的实施:

<?php namespace App\Contracts;

interface EventPusher {

    /**
     * Push a new event to all clients.
     *
     * @param  string  $event
     * @param  array  $data
     * @return void
     */
    public function push($event, array $data);

}

编写了实施这个接口的 PusherEventPusher 以后,我们像这样把它跟 Service Container 注册一下:

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

上面告诉容器当类需要一个 EventPusher 实施的时候要注入 PusherEventPusher 。现在我们可以在构造函数里去 type-hint 一下 EventPusher :

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

上下文绑定

有时候你会有两个使用同一接口的类,不过你想注入不同的实施到每个类里面。比如,当我们的系统收到一个新的订单,我们可能想要通过 PubNub 发送一个事件,而不是使用 Pusher。Laravel 提供了一个方便流畅的接口去定义这样的行为:

$this->app->when('App\Handlers\Commands\CreateOrderHandler')
          ->needs('App\Contracts\EventPusher')
          ->give('App\Services\PubNubEventPusher');

打标签

有时候你需要去 resolve 一个特定类型的所有的绑定。比如,你可能要创建一个报告聚合工具,可以接收来自不同的 Report 接口实施的数组,你可以使用 tag 方法给它们打上一个标签:

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

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

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

服务被打上标签以后,你就可以很容易使用 tagged 方法去 resolve 它们了:

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

实际应用

Laravel 提供了一些使用 Service Container 去提高应用灵活性还有可测性的方法。一个主要的例子就是当去 resolving 控制器的时候。所有的控制器都会通过 Service Container 去 resolved ,意思就是,你可以在控制器构造函数里去 type-hint 依赖,它们会自动被注入进来。

<?php namespace App\Http\Controllers;

use Illuminate\Routing\Controller;
use App\Repositories\OrderRepository;

class OrdersController extends Controller {

    /**
     * The order repository instance.
     */
    protected $orders;

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

    /**
     * Show all of the orders.
     *
     * @return Response
     */
    public function index()
    {
        $all = $this->orders->all();

        return view('orders', ['all' => $all]);
    }

}

在上面的例子里,OrderRepository 类会被自动注入到控制器里。这样当做单元测试的时候,一个 OrderRepository 的 mock 可能会绑定到容器上,这样与数据层进行交互会更容易一些。

其它的使用 Container 的示例

在上面提到过, Laravel 通过 Service Container 去 resolve 的不仅仅只有控制器的类。你也可以在 Closure 路由,队列工作,事件监听器这些东西上面去 type-hint 依赖。在这些上下文里面使用 Service Container 可以参考它们相关的文档。

容器事件

注册一个 Resolving 监听器

容器在每次 resolve 对象的时候都会触发一个事件。你可以使用 resolving 方法去监听这个事件:

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

$this->app->resolving(function(FooBar $fooBar, $app)
{
    // Called when container resolves objects of type "FooBar"...
});

被 resolved 的对象会被传递到一个回调里面。

Laravel Laravel5 中文手册 Service Container
微信好友

用微信扫描二维码,
加我好友。

微信公众号

用微信扫描二维码,
订阅宁皓网公众号。

240746680

用 QQ 扫描二维码,
加入宁皓网 QQ 群。

统计

15260
分钟
0
你学会了
0%
完成

社会化网络

关于

微信订阅号

扫描微信二维码关注宁皓网,每天进步一点