Using dependency injection over laravel facades
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
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,