7

I am trying to understand dependency injection in PHP and I see there are two ways to do this in Laravel.

So let us say I have a class Foo like so:

class Foo{

}

Now I have a class called Bar which is dependent on Foo so I could do something like:

class Bar{
    protected $foo;
    public function __construct()
    {
        $this->foo = new Foo();
    }
}

But in Laravel, I have come across terms like typehinting and reflection which allow me to do this:

class Bar{
    protected $foo;
    public function __construct(Foo $foo)
    {
        $this->foo = $foo;
    }
}

What I am trying to understand is the difference between these two. Are they totally identical? And is there a specific reason I should prefer on over the other?

PS: I am newbie and I am not sure if I am using the jargon in the question correctly.

Rohan
  • 13,308
  • 21
  • 81
  • 154
  • 9
    First example is very hard to test, because you can't mock Foo inside Bar; second is called Dependency Injection (DI), and does allow you to mock Foo when you're testing Bar – Mark Baker Sep 29 '15 at 06:46
  • 3
    possible duplicate of [What is dependency injection?](http://stackoverflow.com/questions/130794/what-is-dependency-injection) – Gal Sisso Sep 29 '15 at 06:49
  • 5
    The second also eliminates the necessity for Foo actually being a Foo instance, but allows it to be either a Foo, or an instance of any class that extends Foo (the type hint enforces that).... loose coupling, because you're no longer constrained to an actual Foo – Mark Baker Sep 29 '15 at 06:52
  • 2
    @MarkBaker: So you mean to say this could help if I code an interface and it has multiple implementations, the controller does not need to know which implementation I am going to use, I can just typehint the interface. Am I understanding this right? – Rohan Sep 29 '15 at 07:00
  • You're understanding it correctly: you don't need a typehint for DI; but hinting to an interface for loose coupling is the value of the typehint.... the DI Is valuable in its own right for making the class easier to test, but doesn't require the typehint; but it's also a necessary step for loose coupling through a typehint – Mark Baker Sep 29 '15 at 07:05
  • Thank you for clearing this out. I think I am finally starting to understand how SOLID and these things work together. Cheers. :) – Rohan Sep 29 '15 at 07:08
  • There are real benefits to a SOLID approach, and this is a good stepping stone to that understanding – Mark Baker Sep 29 '15 at 07:08
  • An alternative approach would be to call a Factory inside the Bar Constructor `$this->foo = FooFactory::getFoo();`, that would pick what Foo to instantiate and return to Bar – Mark Baker Sep 29 '15 at 07:12
  • I will look into factories to understand this better. Thank you for your help. :) – Rohan Sep 29 '15 at 07:21
  • Could you close the question or kindly ask @MarkBaker to answer ? :) – sitilge Sep 29 '15 at 12:10
  • I am guessing he must have been sent a notification from your comment? If he doesn't answer it in a day or two, I'll take the information and answer it myself. – Rohan Sep 29 '15 at 12:14

1 Answers1

1

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.

deceze
  • 510,633
  • 85
  • 743
  • 889