在 laravel 外观上使用依赖注入

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/35011364/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-14 13:06:54  来源:igfitidea点击:

Using dependency injection over laravel facades

phplaraveldependency-injectionlaravel-facade

提问by myol

I have read a number of sources that hint that laravel facade's ultimately exist for convenience and that these classes should instead be injectedto allow loose coupling. Even Taylor Otwell has a postexplaining how to do this. It seems I am not the only one to wonder this.

我已经阅读了许多资料,这些资料暗示 laravel 门面的最终存在是为了方便,而这些类应该被注入以允许松散耦合。甚至Taylor Otwell 也有一篇文章解释了如何做到这一点。似乎我不是唯一一个想知道这一点的人

use Redirect;

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

would become

会成为

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");
    }
}

This is fine except that I am starting to find that some constructors and methods are beginning to take four+ parameters.

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

Since the Laravel IoC seemsto only inject into class constructors and certain methods (controllers), even when I have fairly lean functions and classes, I am finding that constructors of the classes are becoming packed out with the needed classes that then get injected into the needed methods.

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

Now I am finding that if I continue down this approach that I will need my own IoC container, which feels like reinventing the wheel if I am using a framework like laravel?

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

For example I use services to control the business / view logic rather than controllers dealing with them - they simply route the views. So a controller will first take its corresponding service, then then the parameterin its url. One service function also needs to check the values from a form, so then I need Requestand Validator. Just like that, I have four parameters.

例如,我使用服务来控制业务/视图逻辑,而不是处理它们的控制器——它们只是路由视图。因此,控制器将首先获取其对应的service,然后是parameter其 url 中的 。一个服务功能还需要检查表单中的值,因此我需要RequestValidator。就像那样,我有四个参数。

// 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]);
    }
}

This is a single example. In reality, many of my constructors already have multiple classes being injected (Models, Services, Parameters, Facades). I have started to 'offload' the constructor injection (when applicable) to method injection, and have the classes calling those methods use their constructors to inject dependencies instead.

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

I have been told that more than four parameters for a method or class constructor as a rule of thumb is bad practice / code smell. However I cannot see how you can really avoid this if you choose the path of injecting laravel facades.

有人告诉我,根据经验,方法或类构造函数的四个以上参数是不好的做法/代码异味。但是,如果您选择注入 laravel 外观的路径,我看不出如何真正避免这种情况。

Have I got this idea wrong? Are my classes / functions not lean enough? Am I missing the point of laravels container or do I really need to think of creating my own IoC container? Someothers answersseems to hint at the laravel container being able to eliminate my issue?

我这个想法错了吗?我的课程/功能不够精简吗?我是不是错过了 laravel 容器的重点,还是我真的需要考虑创建自己的 IoC 容器?一些其他的答案似乎在laravel容器能消除我的问题暗示?

That said, there doesn't seem to be a definitive consensus on the issue...

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

采纳答案by Steve

This is one of the benefits of constructor injection - it becomes obvious when you class is doing to much, because the constructor parameters grow too large.

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

1st thing to do is split up controllers that have too many responsibilities.

要做的第一件事是拆分具有太多职责的控制器。

Say you have a page controller:

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

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
    }
}

This is a prime candidate for splitting into two controllers, ClientControllerand AboutController.

这是拆分为两个控制器的主要候选者,ClientController并且AboutController.

Once you have done that, if you still have too many* dependencies, its time to look for what i will call indirect dependancies (because i cant think of the proper name for them!) - dependencies that are not directly used by the dependant class, but instead passed on to another dependency.

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

An example of this is addClientAction- it requires a request and a validator, just to pass them to the clientRepostory.

一个例子是addClientAction- 它需要一个请求和一个验证器,只是为了将它们传递给clientRepostory.

We can re factor by creating a new class specifically for creating clients from requests, thus reducing our dependencies, and simplifying both the controller and the repository:

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

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

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

Our method now becomes:

我们的方法现在变成:

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

There is no hard and fast rule as to what number of dependencies are too many. The good news is if you have built your app using loose-coupling, re-factoring is relatively simple.

关于依赖项的数量太多,没有硬性规定。好消息是,如果您使用松耦合构建应用程序,重构相对简单。

I would much much rather see a constructor with 6 or 7 dependencies than a parameterless one and a bunch of static calls hidden throughout the methods

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

回答by Extrakun

One issue with facades is that additional code has to be written to support them when doing automated unit testing.

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

As for solutions:

至于解决方案:

1. Resolving dependencies manually

1.手动解析依赖

One way of resolving dependencies, if you do not wish to do it via. constructors or methods injection, is to call app() directly:

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

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

2. Refactoring

2. 重构

Sometimes when I find myself passing too many services, or dependencies into a class, maybe I have violated the Single Responsibility Principe. In those cases, maybe a re-design is needed, by breaking the service or dependency into smaller classes. I would use another service to wrap up a related group of classes to serve something as a facade. In essence, it'll be a hierarchy of services/logic classes.

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

Example: I have a service that generate recommended products and send it out to users via email. I call the service WeeklyRecommendationServices, and it takes in 2 other services as dependency - a Recommendationservices which is a black-box for generating the recommendations (and it has its own dependencies -- perhaps a repo for products, a helper or two), and an EmailServicewhich maybe has Mailchimp as a dependency). Some lower-level dependencies, such as redirects, validators, etc. will be in those child services instead of the service that acts as the entry point.

示例:我有一项服务,可以生成推荐产品并通过电子邮件将其发送给用户。我调用了 service WeeklyRecommendationServices,它接受了其他 2 个服务作为依赖项——一个Recommendation服务,它是一个用于生成推荐的黑盒(它有自己的依赖项——可能是一个产品的仓库,一个或两个助手),以及一个EmailService它可能将 Mailchimp 作为依赖项)。一些较低级别的依赖项,例如重定向、验证器等,将位于这些子服务中,而不是充当入口点的服务中。

3. Use Laravel global functions

3. 使用 Laravel 全局函数

Some of the Facades are available as function calls in Laravel 5. For instance, you can use redirect()->back()instead of Redirect::back(), as well as view('some_blade)instead of View::make('some_blade'). I believe it's the same for dispatchand some other commonly used facades.

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

(Edited to Add) 4. Using traitsAs I was working on queued jobs today, I also observe that another way to inject dependencies is by using traits. For instance, the DispathcesJobstrait in Laravel has the following lines:

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

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

Any class that uses the traits will have access to the protected method, and access to the dependency. It's neater than having many dependencies in the constructor or method signatures, is clearer (about what dependencies are involved) than globals and easier to customize than manual DI container calls. The drawback is that each time you invoke the function you have to retrieve the dependency from the DI container,

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

回答by tjbp

Class methods that form a part of the routing mechanism in Laravel (middleware, controllers, etc.) also have their type-hints used to inject dependencies- they don't all need to be injected in the constructor. This may help to keep your constructor slim, even though I'm not familiar with any four parameter limit rule of thumb; PSR-2 allows for the method definition to be stretched over multiple linespresumably because it's not uncommon to require more than four parameters.

构成 Laravel 路由机制一部分的类方法(中间件、控制器等)也有用于注入依赖项的类型提示——它们不需要全部注入到构造函数中。这可能有助于使您的构造函数保持苗条,即使我不熟悉任何四参数限制经验法则;PSR-2 允许将方法定义扩展到多行,大概是因为需要四个以上的参数并不少见。

In your example you could inject the Requestand Validatorservices in the constructor as a compromise, since they're often used by more than one method.

在您的示例中,您可以在构造函数中注入RequestValidator服务作为妥协,因为它们经常被不止一种方法使用。

As for establishing a consensus - Laravel would have to be more opinionated for applications to be similar enough to utilise a one-size-fits-all approach. An easier call though is that I think facades will go the way of the dodo in a future version.

至于建立共识 - Laravel 必须更加自以为是,以便应用程序足够相似以使用一刀切的方法。不过,更简单的说法是,我认为外墙将在未来版本中采用渡渡鸟的方式。

回答by myol

Not so much an answer but some food for thought after talking to my colleagues who have made some very valid points;

与其说是答案,不如说是在与提出一些非常有效观点的同事交谈后,深思熟虑;

  1. If the internal structure of laravel is changed between versions (which has happened in the past apparently), injecting the resolved facade class paths would break everything on an upgrade - while using the default facades and helper methods mostly (if not completely) avoids this issue.

  2. Although decoupling code is generally a good thing, the overhead of injecting these resolved facade class paths makes classes cluttered - For developers taking over the project, more time is spent trying to follow the code which could be spent better on fixing bugs or testing. New developers have to remember which injected classes are a developers and which are laravels. Developers unfamiliar with laravel under the hood have to spend time looking up the API. Ultimately the likelihood of introducing bugs or missing key functionality increases.

  3. Development is slowed and testability isn't really improved since facades are already testable. Rapid development is a strong-point of using laravel in the first place. Time is always a constraint.

  4. Most of the other projects use laravel facades. Most people with experience using laravel use facades. Creating a project that doesn't follow the existing trends of previous projects slows things down in general. Future inexperienced (or lazy!) developers may ignore facade injection and the project may end up with a mixed format. (Even code reviewers are human)

  1. 如果 laravel 的内部结构在不同版本之间发生变化(这显然在过去发生过),注入解析的门面类路径将破坏升级时的一切 - 而使用默认的门面和辅助方法(如果不是完全的话)可以避免这个问题.

  2. 尽管解耦代码通常是一件好事,但注入这些已解析的外观类路径的开销会使类变得混乱 - 对于接管项目的开发人员来说,将更多的时间花在尝试跟踪代码上,而这些时间本可以更好地用于修复错误或测试。新开发人员必须记住哪些注入的类是开发人员,哪些是 laravel。不熟悉 Laravel 底层的开发人员必须花时间查找 API。最终引入错误或丢失关键功能的可能性会增加。

  3. 由于外观已经是可测试的,因此开发速度变慢,可测试性并没有真正提高。快速开发首先是使用 laravel 的强项。时间总是一种约束。

  4. 大多数其他项目使用 Laravel 外观。大多数有使用 laravel 经验的人都使用 Facades。创建一个不遵循以前项目的现有趋势的项目通常会减慢速度。未来缺乏经验(或懒惰!)的开发人员可能会忽略外观注入,项目最终可能会采用混合格式。(即使是代码员也是人)

回答by Tzook Bar Noy

Well your thoughts and concerns and correct and I had them as well. There are some benefits of Facades ( I generally dont use them ), but if you do use just I would suggest using them only in the controllers, as the controllers are just entry and exit points for me at least.

好吧,你的想法和担忧是正确的,我也有。Facades 有一些好处(我通常不使用它们),但如果你只使用它们,我建议只在控制器中使用它们,因为控制器至少对我来说只是入口和出口点。

For the example you gave I'll show how I generally handle it:

对于您给出的示例,我将展示我通常如何处理它:

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

...
class ExampleController {

    protected $request;

    public function __constructor(Request $request) {
        // Do this if all/most your methods need the Request
        $this->request = $request;
    }

    public function exampleController(MyServiceInterface $my_service, Validator $validator, $user_id) 
    { 
        // I do my validation inside the service I use,
        // the controller for me is just a funnel for sending the data
        // and returning response

        //now I call the service, that handle the "business"
        //he makes validation and fails if data is not valid
        //or continues to return the result

        try {
            $viewinfo = $my_service->getViewInfo($user_id);
            return view("some_view", ['view_info'=>$viewinfo]);
        } catch (ValidationException $ex) {
            return view("another_view", ['view_info'=>$viewinfo]);
        }
    }
}



class MyService implements MyServiceInterface {

    protected $validator;

    public function __constructor(Validator $validator) {
        $this->validator = $validator;
    }

    public function getViewInfo($user_id, $data) 
    { 

        $this->validator->validate($data, $rules);
        if  ($this->validator->fails()) {
            //this is not the exact syntax, but the idea is to throw an exception
            //with the errors inside
            throw new ValidationException($this->validator);
        }

        echo "doing stuff here with $data";
        return "magic";
    }
}

Just remember to break your code to small individual pieces that each one handles his own responsibility. When you properly break your code, in most cases you will not have so many constructor parameters, and code will be easily testable and mocked.

只需记住将您的代码分解成小块,每个小块处理自己的责任。当你正确地破坏你的代码时,在大多数情况下你不会有这么多构造函数参数,并且代码将很容易测试和模拟。

Just one last note, if you are building a small application or even a page in a huge application for example a "contact page" and "contact page submit", you can surely do everything in the controller with facades, it simply depends on the complexity of the project.

最后一个注意事项,如果您正在构建一个小型应用程序甚至是大型应用程序中的一个页面,例如“联系页面”和“联系页面提交”,您肯定可以在带有外观的控制器中完成所有操作,这仅取决于项目的复杂性。

回答by ujwal dhakal

I love the laravel due to its beautiful architecture.Now as from my approach i wouldnt inject all the facades in to the controller method only why? Injecting Redirect facades only in controller wrong practices as it might need in other. And mainly the things that are mostly used should be declared for all while for those who uses some or only then its best practice to inject them via method as when you declare at top it will hamper in your memory optimization as well as the speed of your code. Hope this would help

我喜欢 laravel 因为它漂亮的架构。现在从我的方法来看,我不会将所有的外观注入控制器方法,为什么?仅在控制器错误的实践中注入重定向外观,因为它可能需要在其他实践中。并且主要是最常使用的东西应该一直声明,而对于那些使用某些或仅然后通过方法注入它们的最佳实践,因为当您在顶部声明时,它会妨碍您的内存优化以及您的速度代码。希望这会有所帮助