[译]Laravel 5.0 之方法注入

本文译自 Matt Stauffer系列文章.


Laravel 5.0 中, 容器可以对其解析的方法进行自动分析, 然后根据类型限制把方法所需要的依赖项自动注入. 本文将介绍这一机制的原理, 何时解析, 如何注入等.

依赖注入的背景知识

在现代编程实践中, PHP 开发者要学会的首要知识之一就是使用依赖注入. 这就是 SOLID(单一功能, 开闭原则, 里氏替换, 接口隔离以及依赖反转) 中的 依赖反转(Dependency Inversion).

Laravel 的 容器 被称为 IOC(Inversion of Control) 容器, 之所以如此命名, 是因为它允许开发者掌控应用底层所发生的事件: 在顶层代码(controllers, 扩展类等)中请求一个实例, 比如 “mailer”, 容器就会返回一个 “mailer” 的实例. 这样, 顶层代码不关注底层到底是由哪个服务来发送邮件–不管是 Mandrill, Mailgun 还是 SendMail, 都不重要, 因为所有 mailer 类都实现相同的接口.

Laravel 4 中的构造函数注入

下面是一个以前的依赖注入的示例:

...
class Listener
{
    protected $mailer;

    public function __construct(Mailer $mailer)
    {
        $this->mailer = $mailer;
    }

    public function userWasAdded(User $user)
    {
        // Do some stuff...

        $this->mailer->send('emails.welcome', ['user' => $user], function($message)
        {
            $message->to($user->email, $user->name)->subject('Welcome!');
        });
    }
}

从例子中可以看到, 可以通过构造函数把 Mailer 类注入到对象. Laravel 的容器让实例化这样的一个类变得很容易, 因为它会自动把依赖项注入构造函数. 比如, 我们可以创建该类的一个新实例, 但不需要传入 Mailer. 因为 Laravel 自动分析构造函数, 知道并且自动替我们注入了这个对象.

$listener = App::make('Listener');

这很方便, 因为

  1. 在应用中可以只定义一次 Mailer 的具体实现, 而不是每次都要指定.
  2. 由于采用了依赖注入, 更便于进行测试.

冲突

假如只是对象中的某一个方法需要用到注入的类呢? 构造函数会因为很多只用到一次的注入变得非常凌乱.

另一种情况, 假如需要通过注入类执行某些操作, 但只针对特定的方法执行呢? 比如 FormRequests 和 ValidatesUponResolved.

解决方案

上述问题的解决方案就是方法注入: 类似构造函数注入, 但允许容器要调用某个方法的时候直接给该方法注入依赖项.

我觉得方法注入最普遍的应用场景就是控制器(controllers). 比如前文提到的 FormRequests 就是一个最好的例子. 但有关 FormRequests 之前已经有详细的介绍, 所以这次我们举点别的例子:

...
class DashboardController extends Controller
{
    public function showMoneyDashboard(MoneyRepository $money)
    {
        $usefulMoneyStuff = $money->getUsefulStuff();
        return View::make('dashboards.money')
            ->with('stuff', $usefulMoneyStuff);
    }

    public function showTasksDashboard(TasksRepository $tasks)
    {
        $usefulTasksStuff = $tasks->getUsefulStuff();
        return View::make('dashboards.tasks')
            ->with('stuff', $usefulTasksStuff);        
    }

    public function showSupervisionDashboard(SupervisionRepository $supervision)
    {
        $usefulSupervisionStuff = $supervision->getUsefulStuff();
        return View::make('dashboards.supervision')
            ->with('stuff', $usefulSupervisionStuff);
    }
}

我们把控制器的 public methods 映射到路由, 用户访问对应的路由时, 容器会调用这些方法, 并自动注入指定的依赖项. 非常棒, 非常简洁.

容器在什么时候会解析方法

前文介绍的控制器方法会被容器解析. ServiceProvider 的 boot 方法也会. 实际上你可以根据你的需要指定容器对任何方法进行解析. 比如:

...
class ThingDoer
{
    public function doThing($thing_key, ThingRepository $repository)
    {
        $thing = $repository->getThing($thing_key);
        $thing->do();
    }
}

然后可以在控制器中通过 App::call() 来调用它. App::call() 的第二个参数是可选的, 它接受以数组方式提供的被调用方法所需的参数:

namespace App\Http\Controllers;

use Illuminate\Contracts\Container\Container;
use Illuminate\Routing\Controller;

class ThingController extends Controller
{
    public function doThing(Container $container)
    {
        $thingDoer = $container->make('ThingDoer');

        // 调用 $thingDoer 对象的 doThing 方法, 并传入 $thing_key 参数,
        // 参数的值是 'awesome-parameter-here'
        $container->call(
            [$thingDoer, 'doThing'],
            ['thing_key' => 'awesome-parameter-here']
        );
    }
}

写在最后

在 Laravel 核心代码中, 用方法注入实现了一些有用的系统功能, 比如 FormRequest. 但别让这些案例局限了你的思维. 它只是让代码保持精简的一个手段, 而我们都需要简洁的代码.

               

[译]Laravel 5.0 之方法注入》上有1条评论

  1. Pingback引用通告: Laravel 5.0 之命令及处理程序 - 乱七八糟 - 裁纸刀下

评论已关闭。