Laravel 5.0 - Method Injection
This is a series of posts on New Features in Laravel 5.0.
!Warning: This post is over a year old. I don't always update old posts with new information, so some of this information may be out of date.
Laravel 5.0 introduces the ability for the Container to resolve depencies injected into any methods that are resolved by the Container. Read on to learn how, when, and why, it works.
The basics of Dependency Injection
One of the first things PHP developers learn as they start growing in modern coding practices is to use dependency injection in order to follow the D in SOLID: Dependency Inversion.
Laravel's Container is called an IOC ("Inversion of Control") Container, and that's the case because it allows your control to happen at the top level of the app: you ask in your low-level code (controllers, implementation classes, etc.) for an instance of "mailer", and the container gives you one. Your low-level code doesn't care about which service is actually sending your mail--Mandrill? Mailgun? Sendmail? It doesn't matter, as long as the interface to the mailer class is the same.
Constructor injection in Laravel 4
Here's a quick sample of traditional dependency injection.
...
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!');
});
}
}
As you can see, we inject the Mailer class into the object using the constructor. And Laravel's Container makes it simple to instantiate this class, because it automates injection into the constructor. Notice that I can creat a new Listener without passing in a Mailer; that's because Laravel resolves it for me, and injects it in.
$listener = App::make('Listener');
This is great because A) I can now make that decision about which Mailer I want once in the app, rather than every time, and B) this makes testing this class much easier.
The conflict
But what if you only need to use the injected class in a single method? Your constructor can get quite cluttered with single use injections.
Or what if you need to perform a particular action upon injection, but only want it to operate on that particular method? (FormRequests and ValidatesUponResolved)
Solution
Intro method injection: It's just like constructor injection, but it allows you to inject dependencies right into your methods--when those methods are called by the Container.
My guess is that the most common use case for method injection will be controllers. Like I mentioned above, the new FormRequests are a perfect example. But that's already been documented, so let's look at something else.
...
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);
}
}
Since public controller methods are called by the Container (when you map a route to them and the user visits that route), these dependencies will be auto-injected as soon as you hit that route. Nice and clean.
When else will the Container resolve a method?
So, we now know that controller methods are resolved by the Container. ServiceProvider's boot
methods are, too.
But you can arbitrarily choose to have the Container resolve any method you'd like.
...
class ThingDoer
{
public function doThing($thing_key, ThingRepository $repository)
{
$thing = $repository->getThing($thing_key);
$thing->do();
}
}
... and we can call it from our Controller using App::call()
, which optionally can take a second parameter which is an array of parameters:
<?php 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');
// Calls the $thingDoer object's doThing method with one parameter
// ($thing_key) with a value of 'awesome-parameter-here'
$container->call(
[$thingDoer, 'doThing'],
['thing_key' => 'awesome-parameter-here']
);
}
}
DO CONCLUDE
Method injection is, at its core, an enabler of some helpful system features like FormRequest--but don't let that stop you from using it. It's just one more way to clean up your code. And we all need cleaner code.
Comments? I'm @stauffermatt on Twitter
Tags: laravel • 5.0 • laravel 5
This is part of a series of posts on New Features in Laravel 5.0:
-
Sep 10, 2014 | laravel, 5.0, laravel 5
-
Sep 10, 2014 | laravel, 5.0, laravel 5
-
Sep 12, 2014 | laravel, laravel 5, 5.0
-
Sep 20, 2014 | laravel, 5.0, laravel 5
-
Sep 28, 2014 | laravel, laravel 5, 5.0
-
Sep 30, 2014 | laravel, 5.0, laravel 5
-
Oct 9, 2014 | laravel, 5.0, laravel 5
-
Oct 10, 2014 | laravel, 5.0, laravel 5
-
Oct 10, 2014 | laravel, 5.0, laravel 5
-
Oct 16, 2014 | laravel, 5.0, laravel 5
-
Nov 20, 2014 | laravel, 5.0, laravel 5
-
Jan 2, 2015 | laravel, 5.0, commands, laravel 5
-
Jan 16, 2015 | laravel, laravel 5
-
Jan 19, 2015 | laravel 5, laravel
-
Jan 21, 2015 | laravel, events, 5.0, laravel 5
-
Jan 26, 2015 | laravel, laravel 5
-
Feb 1, 2015 | laravel, laravel 5
-
Feb 14, 2015 | laravel 5, laravel, eloquent