PHP Dependency InjectionDependency Injection (DI) is a fancy term for "passing things in". All it really means is passing the dependencies of an object via the constructor and / or setters instead of creating them upon object creation inside the object. Dependency Injection might also refer to Dependency Injection Containers which automate the construction and injection.
Objects will often depend on other objects. Instead of creating the dependency in the constructor, the dependency should be passed into the constructor as a parameter. This ensures there is not tight coupling between the objects, and enables changing the dependency upon class instantiation. This has a number of benefits, including making code easier to read by making the dependencies explicit, as well as making testing simpler since the dependencies can be switched out and mocked more easily.
In the following example, Component will depend on an instance of Logger, but it doesn't create one. It requires one to be passed as argument to the constructor instead.
Without dependency injection, the code would probably look similar to:
Using new to create new objects in the constructor indicates that dependency injection was not used (or was used incompletely), and that the code is tightly coupled. It is also a sign that the code is incompletely tested or may have brittle tests that make incorrect assumptions about program state.
In the above example, where we are using dependency injection instead, we could easily change to a different Logger if doing so became necessary. For example, we might use a Logger implementation that logs to a different location, or that uses a different logging format, or that logs to the database instead of to a file.
Dependencies can also be injected by setters.
This is especially interesting when the core functionality of the class does not rely on the dependency to work. Here, the only needed dependency is the DatabaseConnection so it's in the constructor. The Logger dependency is optional and thus does not need to be part of the constructor, making the class easier to use.
Note that when using setter injection, it's better to extend the functionality rather than replacing it. When setting a dependency, there's nothing confirming that the dependency won't change at some point, which could lead in unexpected results. For example, a FileLogger could be set at first, and then a MailLogger could be set. This breaks encapsulation and makes logs hard to find, because we're replacing the dependency.
To prevent this, we should add a dependency with setter injection, like so:
Like this, whenever we'll use the core functionality, it won't break even if there is no logger dependency added, and any logger added will be used even though another logger could've been added. We're extending functionality instead of replacing it.
Dependency Injection (DI) in the context of using a Dependency Injection Container (DIC) can be seen as a superset of constructor injection. A DIC will typically analyze a class constructor's typehints and resolve its needs, effectively injecting the dependencies needed for the instance execution.
The exact implementation goes well beyond the scope of this document but at its very heart, a DIC relies on using the signature of a class...
... to automatically instantiate it, relying most of the time on an autoloading system.
If you are using PHP in version at least 5.5 and want to get a name of a class in a way that's being shown above, the correct way is the second approach. That way you can quickly find usages of the class using modern IDEs, which will greatly help you with potential refactoring. You do not want to rely on regular strings.
In this case, the Documentation\Example knows it needs a Meaning, and a DIC would in turn instantiate a Meaning type. The concrete implementation need not depend on the consuming instance. Instead, we set rules in the container, prior to object creation, that instructs how specific types should be instantiated if need be.
This has a number of advantages, as a DIC can Share common instances Provide a factory to resolve a type signature Resolve an interface signature If we define rules about how specific type needs to be managed we can achieve fine control over which types are shared, instantiated, or created from a factory