panthro panthro - 17 days ago 7
PHP Question

How Laravel's container binding mechanisms differ?

I'm looking at Laravel's service container docs, specifically the binding section.

What are the differences and when should I use each type of binding? The documentation mentions:


  • Simple Binding

  • Singleton Binding

  • Instance Binding

  • Primitive binding

  • Interface binding


Answer

First, let's see what it actually is:

IoC container is a component that knows how instances are created. It also knows of all their underlying dependencies and how to resolve them.

Its knowledge about instance creation and dependency resolving might be guided by the programmer. That's why Laravel's container provides various binding APIs for me and you.

"Resolving out of the container" is a phrase you read/hear a lot. It means that you tell the container to make something for you based on the [optional] guidance you gave her previously.

Before you continue reading about bindings, I strongly recommend you to read this answer:
What is Laravel IoC Container in simple words?

Simple Binding

app()->bind(DatabaseWriter::class, function ($app) {
    return new DatabaseWriter(
        $app->make(DatabaseAdapterInterface)
    );
});

You say to the container that when you want to resolve an instance of DatabaseWriter class, follow this logic I just told you in the closure coz I know better. Every single time that you want to resolve the class, you must follow this and deliver me a new instance.

You use this type of bindings all the time. You're giving the container small recipes on how to make your things for you.

Singleton Binding

Same as simple bindings, with one obvious difference. You're telling the container that I want only one instance of this class across my whole application. The first time you're resolving the class, follow the logic in the closure I passed to you, but be sure that you just return that only instance every other time you want to resolve it. Deliver me the only instance you were allowed to make.

It's a singleton, right? Once a singleton binding is resolved, the same object instance will be returned on subsequent calls into the container.

Obviously, you use this type of binding when you want to utilize the Singleton pattern. It's rare these days.

Instance Binding

It's like doing a favor for the container. You don't tell her how to instantiate a certain class, you do it yourself and just give her the instance. She holds it for you and returns it on subsequent calls into the container.

It's especially handy when you're unit-testing. If you bind a mock instance to the container for some class, all the subsequent calls to app()->make() will return that mock for you. So you're practically injecting a class mock all over the app when the actual class is used.

class QuestionsControllerTest extends TestCase 
{  
    public function testQuestionListing()
    {
        $questionMock = Mockery::mock('Question')
           ->shouldReceive('latest')
           ->once()
           ->getMock();

        // You're telling the container that everytime anyone
        // wants a Question instance, give them this mock I just
        // gave you.
        $this->app->instance('Question', $this->questionMock);

        // whatever...
    }
}

Primitive Binding

Laravel's container provides a DSL for you to tell her how to resolve primitives as well. You say when BillingController class wants a $taxRate variable and it's not passed, give it 0.2. It's like setting default values from far far away!

app()->when('App\Http\Controllers\BillingController')
     ->needs('$taxRate')
     ->give(.2);

The use-case might be rare, but you might need them occasionally. This example might be a lil bit more sensual:

app()->when('App\Http\Controllers\CustomerController')
    ->needs('$customers')
    ->give(function() {
        return Customer::paying();
    });

Interface Binding

It's used when you want to bind interfaces to concrete implementations.

After reading a dozen articles on SOLID and how to be a better programmer, you decide to follow Dependency Inversion principle and instead of depending on concrete instances, you depend on abstractions.

After all, it's a good practice, in or out of Laravel.

class DatabaseWriter {

    protected $db;

    // Any concrete implementation of this interface will do
    // Now, that I depend on this DatabaseAdapterInterface contract,
    // I can work with MySQL, MongoDB and WhatevaDB! Awesome!
    public function __construct(DatabaseAdapterInterface $db)
    {
        $this->db = $db;
    }

    public function write()
    {
        $this->db->query('...');
    }

}

Without Laravel's container, you first need to create a concrete implementation of DatabaseAdapterInterface and pass it through DatabaseWriter's constructor to be able to instantiate it:

$dbWriter = new DatabaseWriter(new MongodbAdapter)

If MongodbAdapter has its own dependencies, you might end up here:

// Looks familiar, right? 
// These are those recipes you used to give to Laravel container 
// through simple binding. 
$dbWriter = new DatabaseWriter(new MongodbAdapter(new MongodbConnection))

But with Laravel's container in the party, you tell her that when anyone asks for a concrete implementation of DatabaseAdapterInterface, ask no more and give them a MongodbAdapter:

app()->bind(DatabaseAdapterInterface::class, MongodbAdapter::class)

Then you go on and resolve an instance of DatabaseWriter out of container, like a boss:

$dbWriter = app()->make(DatabaseWriter::class)

Much easier and cleaner, right? You remove all the obvious clutter and move it to somewhere else. Your AppServiceProvider maybe.

OK, let's see how she works in this scenario. First, she probes DatabaseWriter for possible dependencies (through reflection), sees that it needs a DatabaseAdapterInterface. Checks her notebook, recalls that you told her that MongodbAdapter is the concrete implementation of that interface. Makes one and hand it over to DatabaseWriter.

You use these type of bindings almost all the time, if you're adhering to dependency inversion principle.

OK, enough with the chatter, let's see how she really works:
https://github.com/laravel/framework/blob/5.3/src/Illuminate/Container/Container.php#L627