Laravel 4: Where to put bindings

If you make judicious use of IoC containers (and Dependency Injection), you'll likely end up with lots of Bindings, some of which may look like:

App::bind('Anvil', function()
{
   return new Acme\Product\AnvilHeavy;
});

or

App::bind('Acme\Product\Anvil\AnvilInterface', 'Acme\Product\Anvil\AnvilHeavy');

Yes, I have lots of bindings!

Me too. Let's figure out what to do with them.

Let's say Acme is an accomplished business, and has quite a few products. Amongst them: Anvils and TNT. You likely have classes for all of these. Since I usually create an application library for business logic, my code structure might look like this:

Acme
    app
        lib
            Acme
                Product
                    Anvil
                        AnvilInterface.php
                        AnvilHeavy.php
                        AnvilLight.php
                    Tnt
                        TntInterface.php
                        TntHighyield.php
                        TntLowyield.php
    commands
    config
    …etc…
    filters.php
    routes.php

This means you may have these bindings in your code somewhere. And that's the key - At this stage, very few articles explain where to put that code.

//Somewhere in your code
App::bind('Acme\Product\Anvil\AnvilInterface', 'Acme\Product\Anvil\AnvilHeavy');

App::bind('Tnt', function()
{
   return new Acme\Product\TntHighyield;
});

These will work merrily. We can type-hint AnvilInterface in a controller and know it will get AnvilHeavy. Addtionally, we can resolve our Tnt using App::make('Tnt') anywhere in our application.

As we add products in our contrived example here, we will end up with more and more bindings. These can add up. How can we organize our code to make these less obstrusive? Where is the "home" for these bindings?

A home for your bindings

The answer is to use Service Providers. These nifty classes provide a way to add bindings and use Laravel containers for your libraries.

To use these, I'll create a ServiceProvider for each Acme product:

Acme
    app
        lib
            Acme
                Product
                    Anvil
                        AnvilInterface.php
                        AnvilHeavy.php
                        AnvilLight.php
                        AnvilServiceProvider.php  # New file <--
                    Tnt
                        TntInterface.php
                        TntHighyield.php
                        TntLowyield.php
                        TntServiceProvider.php  # New file <--

The code to add the bindings for our classes are simple:

// app/lib/Acme/Product/Anvil/AnvilServiceProvider.php
<?php namespace Acme\Product\Anvil;

use Illuminate\Support\ServiceProvider;

class AnvilServiceProvider extends ServiceProvider {

    /**
     * Register the binding
     *
     * @return void
     */
    public function register()
    {
        $app = $this->app;

        $app->bind('Acme\Product\Anvil\AnvilInterface', 'Acme\Product\Anvil\AnvilHeavy');
    }

}

// app/lib/Acme/Product/Tnt/TntServiceProdiver.php
<?php namespace Acme\Product\Tnt;

use Illuminate\Support\ServiceProvider;

class TntServiceProvider extends ServiceProvider {

    /**
     * Register the binding
     *
     * @return void
     */
    public function register()
    {
        $app = $this->app;

        $app['Tnt'] = function() {
            return new TntHighyield;
        };
    }

}

Now, rather than having App::bind() calls littered somewhere in our code, we have them as Service Providers for our various Acme products.

The last step is to register these service providers so they are run as part of the bootstrap process.

Head to app/config/app.php. In the 'providers' array, we'll register our Service Providers.

'providers' => array(
    'Illuminate\Foundation\Providers\ArtisanServiceProvider',

    …and so on…

    'Acme\Product\Anvil\AnvilServiceProvider',
    'Acme\Product\Tnt\TntServiceProvider',
)

Now, our bindings will be created for us on bootstrap, and we can go our merry way without cluttering up a config or route file with bindings.

Alternatively!

If I didn't want to create a ServiceProvider class for each product in this example, I could instead create one ProductServiceProvider and do all the binding there.

You may find this way makes more sense to you:

// app/lib/Acme/Product/ProductServiceProvider.php

<?php namespace Acme\Product;

use Illuminate\Support\ServiceProvider;

class ProductServiceProvider extends ServiceProvider {

    /**
     * Register Acme Product bindings
     *
     * @return void
     */
    public function register()
    {
        $app = $this->app;

        // Handle Anvil binding
        $app->bind('Acme\Product\Anvil\AnvilInterface', 'Acme\Product\Anvil\AnvilHeavy');

        // Handle Tnt binding
        $app['Tnt'] = function() {
            return new TntHighyield;
        };
    }

}

What have we learned?

We learned that if you use IoC (and hopefully Dependency Injection) in Laravel, you'll likely be using container bindings.

  1. Judicious use of IoC will mean many App::bind() calls
  2. Organize your container bindings into ServiceProvider classes
  3. You can use many Service Providers, but also consider using one ServiceProvider (perhaps per namespace) to limit the number of classes and (potentially) increase maintainability.
comments powered by Disqus