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.
The benefits of inversion of control in Spring and Java are a developer can maintain the creation, configuration, provisioning and lifecycle of all container-managed objects separately from the code where they are referenced. As such, IoC eases the software developer's concern about these aforementioned activities.
inversion of control (IoC)
IoC means that one code calls another; DI goes beyond that and implements IoC by using composition. A DI or IoC container needs to instantiate objects (dependencies) and provide them to the application. To do so, it must deal with constructor injection, setter injection, and interface injection.
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);
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...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With