在拉拉维尔立面上使用依赖注入

我读过许多资料,暗示laravel立面的存在最终是为了方便,并且应该注入这些类以允许松散耦合。甚至泰勒·奥特韦尔(Taylor Otwell)也有一篇文章解释了如何做到这一点。似乎我不是唯一一个想知道这一点的人

use Redirect;

class Example class
{
    public function example()
    {
         return Redirect::route("route.name");
    }
}

会变成

use Illuminate\Routing\Redirector as Redirect;

class Example class
{
    protected $redirect;

    public function __constructor(Redirect $redirect)
    {
        $this->redirect = $redirect
    }

    public function example()
    {
         return $this->redirect->route("route.name");
    }
}

这很好,除了我开始发现一些构造函数和方法开始采用四个以上的参数。

由于Laravel IoC似乎只注入类构造函数和某些方法(控制器),即使我有相当精益的函数和类,我发现类的构造函数正在被所需的类打包,然后注入到所需的方法中。

现在我发现,如果我继续这种方法,我将需要自己的IoC容器,如果我使用像laravel这样的框架,感觉就像重新发明轮子一样?

例如,我使用服务来控制业务/视图逻辑,而不是处理它们的控制器 - 它们只是路由视图。所以一个控制器将首先获取其相应的 ,然后是其 url 中的 。一个服务函数还需要检查表单中的值,因此我需要 和 。就这样,我有四个参数。serviceparameterRequestValidator

// MyServiceInterface is binded using the laravel container
use Interfaces\MyServiceInterface;
use Illuminate\Http\Request;
use Illuminate\Validation\Factory as Validator;

...

public function exampleController(MyServiceInterface $my_service, Request $request, Validator $validator, $user_id) 
{ 
    // Call some method in the service to do complex validation
    $validation = $my_service->doValidation($request, $validator);

    // Also return the view information
    $viewinfo = $my_service->getViewInfo($user_id);

    if ($validation === 'ok') {
        return view("some_view", ['view_info'=>$viewinfo]);
    } else {
        return view("another_view", ['view_info'=>$viewinfo]);
    }
}

这是一个示例。实际上,我的许多构造函数已经注入了多个类(模型,服务,参数,外观)。我已经开始将构造函数注入(如果适用)“卸载”到方法注入,并让调用这些方法的类使用其构造函数来注入依赖项。

我被告知,作为经验法则,一个方法或类构造函数的四个以上参数是不好的做法/代码气味。然而,我看不出如果你选择注入拉拉维尔立面的路径,你怎么能真正避免这种情况。

我有没有这个想法弄错了?我的类/函数不够精益吗?我是否错过了 laravels 容器的重点,或者我真的需要考虑创建自己的 IoC 容器?其他一些答案似乎暗示了laravel容器能够消除我的问题?

也就是说,在这个问题上似乎没有明确的共识......


答案 1

这是构造函数注入的好处之一 - 当你的类做很多事情时,它变得很明显,因为构造函数参数变得太大了。

首先要做的是拆分具有太多职责的控制器。

假设您有一个页面控制器:

Class PageController
{

    public function __construct(
        Request $request,
        ClientRepositoryInterface $clientrepo,
        StaffRepositortInterface $staffRepo
        )
    {

     $this->clientRepository = $clientRepo;
     //etc etc

    }

    public function aboutAction()
    {
        $teamMembers = $this->staffRepository->getAll();
        //render view
    }

    public function allClientsAction()
    {
        $clients = $this->clientRepository->getAll();
        //render view
    }

    public function addClientAction(Request $request, Validator $validator)
    {
        $this->clientRepository->createFromArray($request->all() $validator);
        //do stuff
    }
}

这是拆分为两个控制器的主要候选者,以及 。ClientControllerAboutController

一旦你这样做了,如果你仍然有太多的*依赖关系,是时候寻找我将称之为间接依赖关系的东西了(因为我想不出它们的正确名称!) - 依赖类不直接使用的依赖关系,而是传递给另一个依赖关系。

这方面的一个例子是 - 它需要一个请求和一个验证器,只是为了将它们传递给.addClientActionclientRepostory

我们可以通过创建一个专门用于从请求创建客户端的新类来重构,从而减少我们的依赖性,并简化控制器和存储库:

//think of a better name!
Class ClientCreator 
{
    public function __construct(Request $request, validator $validator){}

    public function getClient(){}
    public function isValid(){}
    public function getErrors(){}
}

我们的方法现在变成:

public function addClientAction(ClientCreator $creator)
{ 
     if($creator->isValid()){
         $this->clientRepository->add($creator->getClient());
     }else{
         //handle errors
     }
}

对于多少个依赖项太多,没有硬性规定。好消息是,如果你使用松散耦合构建应用,则重构相对简单。

我宁愿看到一个具有6或7个依赖项的构造函数,而不是一个无参数的构造函数,以及隐藏在整个方法中的一堆静态调用。


答案 2

外观的一个问题是,在执行自动化单元测试时,必须编写额外的代码来支持它们。

至于解决方案:

1. 手动解析依赖关系

解决依赖关系的一种方法,如果您不希望通过。构造函数或方法注入,是直接调用 app():

/* @var $email_services App\Contracts\EmailServicesContract
$email_services = app('App\Contracts\EmailServicesContract');

2. 重构

有时,当我发现自己将太多的服务或依赖项传递到一个类中时,也许我违反了单一责任原则。在这些情况下,可能需要重新设计,通过将服务或依赖项分解为更小的类。我会使用另一个服务来包装一组相关的类,以用作外观。从本质上讲,它将是服务/逻辑类的层次结构。

示例:我有一项服务,可以生成推荐的产品并通过电子邮件发送给用户。我调用服务,它接受其他2个服务作为依赖关系 - 一个服务,它是生成建议的黑匣子(它有自己的依赖关系 - 也许是产品的存储库,一个或两个帮助者),以及一个可能将Mailchimp作为依赖关系的服务)。某些较低级别的依赖项(如重定向、验证程序等)将位于这些子服务中,而不是充当入口点的服务中。WeeklyRecommendationServicesRecommendationEmailService

3. 使用拉拉维尔全局函数

一些立面在Laravel 5中可作为函数调用使用。例如,您可以使用 代替 ,也可以代替 。我相信它和其他一些常用的立面是一样的。redirect()->back()Redirect::back()view('some_blade)View::make('some_blade')dispatch

(编辑为添加) 4.使用特征 当我今天处理排队的作业时,我还观察到注入依赖关系的另一种方法是使用特征。例如,Laravel中的DispathcesJobs特质具有以下行:

   protected function dispatch($job)
    {
        return app('Illuminate\Contracts\Bus\Dispatcher')->dispatch($job);
    }

使用这些特征的任何类都将有权访问受保护的方法,并有权访问依赖项。它比在构造函数或方法签名中具有许多依赖项更整洁,比全局变量更清晰(关于涉及哪些依赖项),并且比手动 DI 容器调用更易于自定义。缺点是每次调用函数时,都必须从 DI 容器中检索依赖项,


推荐