Nette DI Container
Nette DI is one of Nette's most interesting libraries. It can generate and automatically update compiled DI containers that are extremely fast and remarkably easy to configure.
The form of services that the DI container should create is usually defined using configuration files in NEON format. The container we manually created in the previous chapter would be written like this:
parameters:
db:
dsn: 'mysql:'
user: root
password: '***'
services:
- Nette\Database\Connection(%db.dsn%, %db.user%, %db.password%)
- ArticleFactory
- EditController
The syntax is very concise.
All dependencies declared in the constructors of the ArticleFactory and EditController classes are
discovered and passed automatically by Nette DI thanks to so-called autowiring, so there's no need to specify anything in the
configuration file. Thus, even if parameters change, you don't need to change anything in the configuration. During development,
Nette automatically regenerates the container. You can focus purely on application development.
If we want to pass dependencies using setters, we use the setup section for this.
Nette DI generates PHP code for the container directly. The result is thus a .php file that you can open and
examine. This allows you to see exactly how the container functions. You can also debug it in your IDE and step through its
execution. And most importantly: the generated PHP code is extremely fast.
Nette DI can also generate factory code based on a provided
interface. Therefore, instead of the ArticleFactory class, we only need to create an interface in the
application:
interface ArticleFactory
{
function create(): Article;
}
You can find the full example on GitHub.
Standalone Use
Integrating the Nette DI library into an application is very easy. First, we install it using Composer (because downloading zip files is so outdated):
composer require nette/di
The following code uses the Compiler to create an instance
of the DI container according to the configuration stored in the config.neon file:
$loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp');
$class = $loader->load(function ($compiler) {
$compiler->loadConfig(__DIR__ . '/config.neon');
});
$container = new $class;
The container is generated only once, its code is written to the cache (the __DIR__ . '/temp' directory), and on
subsequent requests, it is only loaded from there.
On its own, the Compiler enables only the services and parameters sections in the
configuration. To use others – such as search, decorator, di, or
inject – register their extensions first. And to allow registering extensions from the extensions
section of the config, add the ExtensionsExtension:
$compiler->addExtension('search', new Nette\DI\Extensions\SearchExtension($tempDir));
$compiler->addExtension('extensions', new Nette\DI\Extensions\ExtensionsExtension);
The Configurator used in full Nette applications registers all of these automatically.
If you keep several different containers in the same cache directory, distinguish them with a key passed as the second argument
to load(); it becomes part of the generated class name:
$class = $loader->load(
fn($compiler) => $compiler->loadConfig(__DIR__ . '/config.neon'),
'my-key',
);
The getService() or getByType() methods are used to create and retrieve services. This is how we
create the EditController object:
$controller = $container->getByType(EditController::class);
$controller->someMethod();
During development, it's useful to activate auto-refresh mode, where the container automatically regenerates if any class or
configuration file is modified. Just provide true as the second argument in the ContainerLoader constructor.
$loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp', true);
Working with the Container
Besides getService() and getByType(), the container object offers several other useful methods:
getByType(string $type, bool $throw = true): ?objectreturns the service of the given type. If you passfalseas the second argument, it returnsnullinstead of throwing an exception when no such service exists.hasService(string $name): boolandisCreated(string $name): booltell you whether a service is defined and whether it has already been instantiated.getParameters(): arrayreturns all container parameters,getParameter($key)returns a single one.createInstance(string $class, array $args = []): objectcreates a new instance of the given class and passes its constructor dependencies via autowiring.callMethod(callable $function, array $args = []): mixedcalls the given callable and passes its arguments via autowiring.callInjects(object $service): voidcalls allinject*()methods on the given object and passes dependencies to them.
The container constructor also accepts an array of parameters that supplement those defined in the configuration:
$container = new $class(['host' => 'localhost']);
Using with Nette Framework
As we have shown, the use of Nette DI is not restricted to applications built with Nette Framework; you can integrate it anywhere with just three lines of code. However, if you are developing applications using the Nette Framework, the configuration and creation of the container are handled by Bootstrap.