The principle says that your low-level chunks should implement an all-sufficient and clear abstraction, and high-level code should work only with this abstraction and not low-level implementation.
When we split a big multitask class into small specialized classes, we face the issue of creating dependent objects and injecting them into each other.
And after splitting we will create or get all dependent items and build our service:
Dependency injection container is a factory that allows us to not care about building our objects. In Yii2 we can configure a container only once and use it for retrieving our service like this:
Or we ask the container to inject it as a dependency in the constructor of an other service:
In all cases the container will resolve all dependencies and inject dependent objects in each other.
In the recipe we create shopping cart with storage subsystem and inject the cart automatically into controller.
In this case we have the main ShoppingCart
class with a low-level dependency, defined by an abstraction interface:
And we have some an implementation of the abstraction:
Right now we can create an instance of the cart manually like this:
It allows us to create a lot of different implementations such as SessionStorage
, CookieStorage
, or DbStorage
. And we can reuse the framework-independent ShoppingCart
class with StorageInterface
in different projects and different frameworks. We must only implement the storage class with the interface's methods for needed framework.
But instead of manually creating an instance with all dependencies, we can use a dependency injection container.
By default the container parses the constructors of all classes and recursively creates all the required instances. For example, if we have four classes:
We can retrieve the instance of class A
in two ways:
And the container automatically creates instances of the B
, D
, C
, and A
classes and injects them into each other.
In our case we mark the cart instance as a singleton:
This means that the container will return a single instance for every repeated call instead of creating the cart again and again.
Besides, our ShoppingCart
has the StorageInterface
type in its own constructor and the container does know what class it must instantiate for this type. We must manually bind the class to the interface like this:
But our SessionStorage
class has non-standard constructor:
Therefore we use an anonymous function to manually creatie the instance:
And after all we can retrieve the cart object from the container manually in our own controllers, widgets, and other places:
But every controller and other object will be created via the createObject
method inside the framework. And we can use injection of cart via the controller constructor:
Use this injected cart object: