Laravel 5.0 - Method Injection

(This is part of a series of posts on New Features in Laravel 5.0.)

  1. Laravel 5.0 - Form Requests
  2. Laravel 5.0 - ValidatesWhenResolved
  3. Laravel 5.0 - Directory structure and namespace
  4. Laravel 5.0 - Route Caching
  5. Laravel 5.0 - Cloud File Drivers
  6. Laravel 5.0 - Method Injection
  7. Laravel 5.0 - Route Annotations (removed)
  8. Laravel 5.0 - Event Annotations (removed)
  9. Laravel 5.0 - Middleware (Filter-style)
  10. Laravel 5.0 - Environment Detection & Environment Variables
  11. Laravel 5.0 - Event Scheduling
  12. Laravel 5.0 - Commands & Handlers
  13. Upgrading from Laravel 4 to Laravel 5
  14. Bringing Whoops Back to Laravel 5
  15. Laravel 5.0 - Events & Handlers
  16. Laravel 5.0 - Generating Missing Events
  17. Laravel 5.0 - Custom Error Pages
  18. Laravel 5.0 - Eloquent Attribute Casting

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

Matt Stauffer headshot

Hi, I'm Matt Stauffer.

I'm partner & technical director at Tighten Co.

You can find me on Twitter at @stauffermatt


Like what you're reading?

I wrote an entire 450+ page book for O'Reilly: Laravel: Up and Running.

You can order the eBook or print book today.