Using dependency injection over laravel facades

后端 未结 6 1314
鱼传尺愫
鱼传尺愫 2021-01-30 16:48

I have read a number of sources that hint that laravel facade\'s ultimately exist for convenience and that these classes should instead be injected to allow loose coupling. Even

相关标签:
6条回答
  • 2021-01-30 16:50

    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.

    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.

    0 讨论(0)
  • 2021-01-30 17:00

    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

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

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

    2. Refactoring

    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 Recommendation services 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 EmailService which 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.

    3. Use Laravel global functions

    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 dispatch and some other commonly used facades.

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

       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,

    0 讨论(0)
  • 2021-01-30 17:00

    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

    0 讨论(0)
  • 2021-01-30 17:05

    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, ClientController and 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.

    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

    0 讨论(0)
  • 2021-01-30 17:08

    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 lines presumably because it's not uncommon to require more than four parameters.

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

    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.

    0 讨论(0)
  • 2021-01-30 17:09

    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)

    0 讨论(0)
提交回复
热议问题