DI Frequently Asked Questions (FAQ)
- Is DI another name for IoC?
- What is a Service Locator?
- When is it better not to use DI?
- Does using DI have its drawbacks?
- How to rewrite a legacy application to DI?
- Why composition is preferred over inheritance?
- Can Nette DI Container be used outside of Nette?
- Why is the configuration in NEON files?
- Does parsing NEON files slow down the application?
- How do I access the parameters from the configuration file in my class?
- Does Nette support PSR-11 Container interface?
Is DI another name for IoC?
The Inversion of Control (IoC) is a principle focused on the way code is executed – whether your code initiates
external code or your code is integrated into external code, which then calls it. IoC is a broad concept that includes events, the so-called Hollywood Principle, and other aspects. Factories,
which are part of Rule
#3: Let the Factory Handle It, and represent inversion for the
new operator, are also components of this
The Dependency Injection (DI) is about how one object knows about another object, i.e., dependency. It is a design pattern that requires explicit passing of dependencies between objects.
Thus, DI can be said to be a specific form of IoC. However, not all forms of IoC are suitable in terms of code purity. For example, among anti-patterns, we include all techniques that work with global state or the so-called Service Locator.
What is a Service Locator?
A Service Locator is an alternative to Dependency Injection. It works by creating a central storage where all available services or dependencies are registered. When an object needs a dependency, it requests it from the Service Locator.
However, compared to Dependency Injection, it loses transparency: dependencies are not directly passed to objects and are therefore not easily identifiable, which requires examining the code to uncover and understand all connections. Testing is also more complicated, as we cannot simply pass mock objects to the tested objects, but have to go through the Service Locator. Furthermore, the Service Locator disrupts the design of the code, as individual objects must be aware of its existence, which differs from Dependency Injection, where objects have no knowledge of the DI container.
When is it better not to use DI?
There are no known difficulties associated with using the Dependency Injection design pattern. On the contrary, obtaining dependencies from globally accessible locations leads to a number of complications, as does using a Service Locator. Therefore, it is advisable to always use DI. This is not a dogmatic approach, but simply no better alternative has been found.
However, there are certain situations where we do not pass objects to each other and obtain them from the global space. For example, when debugging code and needing to dump a variable value at a specific point in the program, measure the duration of a certain part of the program, or log a message. In such cases, where it is about temporary actions that will be later removed from the code, it is legitimate to use a globally accessible dumper, stopwatch, or logger. These tools, after all, do not belong to the design of the code.
Does using DI have its drawbacks?
Does using Dependency Injection involve any disadvantages, such as increased code writing complexity or worse performance? What do we lose when we start writing code in accordance with DI?
DI has no impact on application performance or memory requirements. The performance of the DI Container may play a role, but in the case of Nette DI, the container is compiled into pure PHP, so its overhead during application runtime is essentially zero.
When writing code, it is necessary to create constructors that accept dependencies. In the past, this could be time-consuming, but thanks to modern IDEs and constructor property promotion, it is now a matter of a few seconds. Factories can be easily generated using Nette DI and a PhpStorm plugin with just a few clicks. On the other hand, there is no need to write singletons and static access points.
It can be concluded that a properly designed application using DI is neither shorter nor longer compared to an application using singletons. Parts of the code working with dependencies are simply extracted from individual classes and moved to new locations, i.e., the DI container and factories.
How to rewrite a legacy application to DI?
Migrating from a legacy application to Dependency Injection can be a challenging process, especially for large and complex applications. It is important to approach this process systematically.
- When moving to Dependency Injection, it is important that all team members understand the principles and practices being used.
- First, perform an analysis of the existing application to identify key components and their dependencies. Create a plan for which parts will be refactored and in what order.
- Implement a DI container or, better yet, use an existing library such as Nette DI.
- Gradually refactor each part of the application to use Dependency Injection. This may involve modifying constructors or methods to accept dependencies as parameters.
- Modify places in the code where dependency objects are created so that dependencies are instead injected by the container. This may include the use of factories.
Remember that moving to Dependency Injection is an investment in code quality and the long-term sustainability of the application. While it may be challenging to make these changes, the result should be cleaner, more modular, and easily testable code that is ready for future extensions and maintenance.
Why composition is preferred over inheritance?
It is preferable to use composition instead of inheritance because it serves to reuse code without having to worry about the consequences of changes. Thus, it provides a looser coupling where we don't have to worry that changing some code will cause the need to change other dependent code. A typical example is a situation referred to as constructor hell.
Can Nette DI Container be used outside of Nette?
Absolutely. The Nette DI Container is part of Nette, but is designed as a standalone library that can be used independently of other parts of the framework. Just install it using Composer, create a configuration file defining your services, and then use a few lines of PHP code to create the DI container. And you can immediately start taking advantage of Dependency Injection in your projects.
The Nette DI Container chapter describes what a specific use case looks like, including the code.
Why is the configuration in NEON files?
NEON is a simple and easily readable configuration language developed within Nette for setting up applications, services, and their dependencies. Compared to JSON or YAML, it offers much more intuitive and flexible options for this purpose. In NEON, you can naturally describe bindings that would not be possible to write in Symfony & YAML either at all or only through a complex description.
Does parsing NEON files slow down the application?
Although NEON files are parsed very quickly, this aspect doesn't really matter. The reason is that parsing files occurs only once during the first launch of the application. After that, the DI container code is generated, stored on the disk, and executed for each subsequent request without the need for further parsing.
This is how it works in a production environment. During development, NEON files are parsed every time their content changes, ensuring that the developer always has an up-to-date DI container. As mentioned earlier, the actual parsing is a matter of an instant.
How do I access the parameters from the configuration file in my class?
Keep in mind Rule #1: Let It Be Passed to You. If a class requires information from a configuration file, we don't need to figure out how to access that information; instead, we simply ask for it – for example, through the class constructor. And we perform the passing in the configuration file.
In this example,
%myParameter% is a placeholder for the value of the
myParameter parameter, which
will be passed to the
# config.neon parameters: myParameter: Some value services: - MyClass(%myParameter%)
If you want to pass multiple parameters or use autowiring, it is useful to wrap the parameters in an object.
Does Nette support PSR-11 Container interface?
Nette DI Container does not support PSR-11 directly. However, if you need interoperability between the Nette DI Container and libraries or frameworks that expect the PSR-11 Container Interface, you can create a simple adapter to serve as a bridge between the Nette DI Container and PSR-11.