6

I have trouble understanding the benefit of the IOC container in the scope of dependency injection.
Considering this basic example:

App::bind('Car', function()
{
    return new Car;
});

Route::get('/', function()
{
    dd(App::make('Car'));  //  resolve it
}); 

I don't see the benefit of using IOC container over creating a new instance in the constructor.
Aside from testing benefits, I read that the reason is loose coupling.
However, as the 'car' binding just returns an instance of a new car, I don't see in which sense this example would be more loosely coupled.
For me, the two appear to be doing exactly the same thing.

html_programmer
  • 18,126
  • 18
  • 85
  • 158

2 Answers2

5

You're right, in contrived examples its usually a bit difficult to see exactly what benefit you're getting. Consider a closer-to-real-world example controller:

class TestController
{
    public function __construct(CarRepositoryInterface $car)
    {
        $this->_repository = $car;
    }

    public function route($id)
    {
        return View::make('my.view')->with('car', $this->_repository->find($id));
    }
}

Very simple, a repository is being injected into the controller's constructor which is then being used in the route to load a specific car by id. The details here of the repository aren't all that important, and presumably there's a service provider that's binding CarRepositoryInterface to a concrete CarRepository implementation:

class RepositoryServiceProvider extends ServiceProvider
{
    public function register()
    {
        $this->app->bind('CarRepositoryInterface', function($app) {
            return new EloquentCarRepository(new Car());
        });
    }
}

So there it is, everytime the controller gets constructed, an EloquentCarRepository gets created and injected into the constructor for the controller to use.

But wait, what happens if you want to switch from using Eloquent to say, Doctrine? Since we're leveraging dependency injection here you don't have to change a single line of code in your controller (or any other controller that may be using your current implementation). All you have to do is define your other implementation of CarRepositoryInterface, say, DoctrineCarRepository, and change one line of code in your service provider:

return new DoctrineCarRepository();

Everything else that depends on CarRepositoryInterface now magically works. And all of this is thanks to the IoC.

You can also add more complex logic to your service provider:

public function register()
{
    $this->app->bind('CarRepositoryInterface', function($app) {
        if($app->environment() == 'production') {
            return new EloquentCarRepository(new Car());
        } else {
            return new DoctrineCarRepository(new Car());
        }
    });
}

Here the EloquentCarRepository will be used only in the production environment, whereas on any other environment the DoctrineCarRepository will be used. (This example is only to show how you can gain a lot more control over what type of object gets constructed at runtime, not that I'm advocating actually doing this..)

Addendum

As I stated in my comment, this is the type of usage where you're not quite sure what type of object you're going to need until runtime. There is another usage: Depedency Management.

Suppose you have an object that depends on another object:

class MyObject
{
    public function __construct(AnotherObject $obj)
    {
        $this->obj = $obj;
    }
}

Suppose also that AnotherObject depends on yet another object:

class AnotherObject
{
    public function __construct(YetAnotherObject $obj)
    {
        $this->obj = $obj;
    }
}

This can quickly spiral out of control, and you can wind up with long chains of dependencies that need to be satisfied before you can actually create the object. With the IoC, you can just pluck an instance out of the container:

$myObject = app()->make('MyObject');

As long as the IoC can construct all of the dependencies, you don't have to do something like this:

$yetAnotherObj = new YetAnotherObject();
$anotherObj = new AnotherObject($yetAnotherObj);
$myObject = new MyObject($anotherObj);
Jeff Lambert
  • 24,395
  • 4
  • 69
  • 96
  • I think that I see. If I understand well, the IOC container allows to define a general abstraction (the key definition), for which the concrete class is bound at runtime. Would that be about right? – html_programmer Aug 11 '14 at 20:57
  • 1
    Correct. It is best used in situations where you're not sure what specific type of object you need until runtime. – Jeff Lambert Aug 11 '14 at 20:59
  • @Kim I added an additional use above, just for clarification. – Jeff Lambert Aug 11 '14 at 21:05
  • Yeah, that's definitely useful! Small question about this. Is it custom to perform the explicit binding in this case? I've just read about Automatic Resolution. Is it necessary to define the binding if you just return a class instance (eg. your very first example)? – html_programmer Aug 11 '14 at 21:14
  • 1
    @Kim you don't need a service provider in order to take advantage of this. The service provider is really only necessary if you need to make a decision on what implementation to inject or if there are complex dependencies that need to be constructed first. All you have to do in all other cases is define your class and then inject that class into your controller. – Jeff Lambert Aug 11 '14 at 21:26
3

That example you post, doesn't represent a real use case of an IoC Container...

An IoC Container its more usefull in this example:

When you have a BillingNotifierInterface which is implemented by a EmailBillingNotifier

App::bind('BillingNotifierInterface', function()
{
  return new EmailBillingNotifier;
});

And is using by a BillerInterface which is implemented by a StripeBiller, like this:

App::bind('BillerInterface', function()
{
  return new StripeBiller(App::make('BillingNotifierInterface'));
})

But suddenly your team want to change from EmailBillingNotifier to SMSBillingNotifier you just change 1 line and your app keep working smooth as a cat...

App::bind('BillingNotifierInterface', function()
{
  return new SMSBillingNotifier;
});

That is a REAL IoC CONTAINER application...

NBPalomino
  • 461
  • 1
  • 7
  • 15
  • 1
    +1 Good example. You can even let the application look at a user's preferences to determine whether they want email or sms notifications, and the IoC container can automatically do the hardwork for you. – Jeff Lambert Aug 11 '14 at 20:53
  • 1
    +1 Thanks for this simple example. That makes it even easier to understand. – html_programmer Aug 11 '14 at 20:59