It mostly comes down to coupling of code.
class Foo {
public function __construct() {
new Bar;
}
}
This couples a very specific Bar
to this specific Foo
. There's no way to alter which Bar
gets instantiated without rewriting this code. This also means Foo
needs to know about Bar
's dependencies. Maybe today Bar
can be instantiated with just new Bar
. But maybe tomorrow you're refactoring Bar
and must now instantiate it with new Bar($database)
. Now you also need to rewrite Foo
to accommodate that.
That's where dependency injection comes in (the above is not dependency injection, you're not injecting anything):
class Foo {
public function __construct(Bar $bar) { }
}
This Foo
merely declares that it needs an object with the characteristics of Bar
upon instantiation. But Foo
does not need to know anything about how Bar
came about to be, what its dependencies are or what exactly it does. The only thing it expects of Bar
is a defined public
interface, anything else about it is irrelevant. In fact, to gain even more flexibility, you may want to use an interface
instead of a concrete class dependency here.
Dependency injection allows you to divorce concrete details of classes from other code. It allows you to have one central place where classes are instantiated, which is a place where you need to know and consider concrete details about the classes you're instantiating. This can be a dependency injection container for example. You don't want to spread class instantiation logic all over the place, because as mentioned above, that logic may change, and then you need to rewrite code all over the place.
require_once 'Foo.php';
require_once 'Bar.php';
$foo = new Foo(new Bar);
The above code is where it's being decided which Bar
gets injected into Foo
. It's also the place which needs to worry about Bar
's dependencies. Note that dependency loading and instantiation is the only thing this code does. It's trivial to change only this piece of code as necessary, without needing to touch Foo
or Bar
, which may be full of complex business logic.
Dependency injected code also allows you to take your app apart and put it together flexibly. For example for testing purposes. Or simply to reuse different components flexibly in different contexts.
Also see How Not To Kill Your Testability Using Statics.