Nette Documentation Preview

syntax
Contenedor DI de Nette
**********************

.[perex]
Nette DI es una de las bibliotecas más interesantes de Nette. Sabe generar y actualizar automáticamente contenedores DI compilados que son extremadamente rápidos y muy fáciles de configurar.

La forma de los servicios que el contenedor DI debe crear se define normalmente con archivos de configuración en [formato NEON|neon:format]. El contenedor que creamos a mano en el [capítulo anterior|container] se escribiría así:

```neon
parameters:
	db:
		dsn: 'mysql:'
		user: root
		password: '***'

services:
	- Nette\Database\Connection(%db.dsn%, %db.user%, %db.password%)
	- ArticleFactory
	- EditController
```

La sintaxis es muy concisa.

Todas las dependencias declaradas en los constructores de las clases `ArticleFactory` y `EditController` las descubre y las pasa automáticamente Nette DI gracias al llamado [autowiring|autowiring], así que no hace falta indicar nada en el archivo de configuración. Por tanto, aunque cambien los parámetros, no tiene que cambiar nada en la configuración. Durante el desarrollo, Nette regenera el contenedor automáticamente. Puede concentrarse puramente en desarrollar la aplicación.

Si queremos pasar las dependencias mediante setters, usamos para ello la sección [setup |services#Setup].

Nette DI genera directamente el código PHP del contenedor. El resultado es, por tanto, un archivo `.php` que puede abrir y examinar. Eso le permite ver exactamente cómo funciona el contenedor. También puede depurarlo en su IDE y recorrer su ejecución paso a paso. Y, sobre todo: el código PHP generado es extremadamente rápido.

Nette DI también puede generar el código de una [factory|factory] a partir de una interfaz dada. Por eso, en lugar de la clase `ArticleFactory`, en la aplicación solo tenemos que crear una interfaz:

```php
interface ArticleFactory
{
	function create(): Article;
}
```

Encontrará el ejemplo completo [en GitHub|https://github.com/nette-examples/di-example-doc].


Uso independiente
-----------------

Integrar la biblioteca Nette DI en una aplicación es muy fácil. Primero la instalamos con Composer (porque descargar archivos zip está muy pasado de moda):

```shell
composer require nette/di
```

El siguiente código usa el [Compiler |api:Nette\DI\Compiler] para crear una instancia del contenedor DI según la configuración guardada en el archivo `config.neon`:

```php
$loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp');
$class = $loader->load(function ($compiler) {
	$compiler->loadConfig(__DIR__ . '/config.neon');
});
$container = new $class;
```

El contenedor se genera una sola vez, su código se escribe en la caché (el directorio `__DIR__ . '/temp'`) y en las peticiones siguientes solo se carga desde ahí.

Por sí solo, el `Compiler` habilita en la configuración únicamente las secciones `services` y `parameters`. Para usar las demás (como `search`, `decorator`, `di` o `inject`) hay que registrar antes sus extensiones. Y para poder registrar extensiones desde la sección `extensions` de la configuración, añada la `ExtensionsExtension`:

```php
$compiler->addExtension('search', new Nette\DI\Extensions\SearchExtension($tempDir));
$compiler->addExtension('extensions', new Nette\DI\Extensions\ExtensionsExtension);
```

El [Configurator |application:bootstrapping] que se usa en las aplicaciones Nette completas las registra todas automáticamente.

Si guarda varios contenedores distintos en el mismo directorio de caché, distíngalos con una clave pasada como segundo argumento a `load()`; pasa a formar parte del nombre de la clase generada:

```php
$class = $loader->load(
	fn($compiler) => $compiler->loadConfig(__DIR__ . '/config.neon'),
	'my-key',
);
```

Para crear y obtener los servicios se usan los métodos `getService()` o `getByType()`. Así creamos el objeto `EditController`:

```php
$controller = $container->getByType(EditController::class);
$controller->someMethod();
```

Durante el desarrollo conviene activar el modo de refresco automático, en el que el contenedor se regenera solo si se modifica alguna clase o algún archivo de configuración. Basta con pasar `true` como segundo argumento en el constructor de [ContainerLoader |api:Nette\DI\ContainerLoader].

```php
$loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp', true);
```


Trabajar con el contenedor
--------------------------

Además de `getService()` y `getByType()`, el objeto del contenedor ofrece otros métodos útiles:

- `getByType(string $type, bool $throw = true): ?object` devuelve el servicio del tipo dado. Si pasa `false` como segundo argumento, devuelve `null` en lugar de lanzar una excepción cuando no existe tal servicio.
- `hasService(string $name): bool` e `isCreated(string $name): bool` le dicen si un servicio está definido y si ya se ha creado su instancia.
- `getParameters(): array` devuelve todos los parámetros del contenedor, `getParameter($key)` devuelve uno solo.
- `createInstance(string $class, array $args = []): object` crea una nueva instancia de la clase dada y le pasa las dependencias del constructor mediante autowiring.
- `callMethod(callable $function, array $args = []): mixed` llama al callable dado y le pasa sus argumentos mediante autowiring.
- `callInjects(object $service): void` llama a todos los métodos `inject*()` del objeto dado y les pasa las dependencias.

El constructor del contenedor acepta además un array de parámetros que complementan los definidos en la configuración:

```php
$container = new $class(['host' => 'localhost']);
```


Uso con Nette Framework
-----------------------

Como hemos mostrado, el uso de Nette DI no se limita a las aplicaciones construidas con Nette Framework; puede integrarlo en cualquier sitio con solo tres líneas de código. Ahora bien, si desarrolla aplicaciones con Nette Framework, de la configuración y la creación del contenedor se encarga [Bootstrap |application:bootstrapping#Configuración del contenedor DI].

Contenedor DI de Nette

Nette DI es una de las bibliotecas más interesantes de Nette. Sabe generar y actualizar automáticamente contenedores DI compilados que son extremadamente rápidos y muy fáciles de configurar.

La forma de los servicios que el contenedor DI debe crear se define normalmente con archivos de configuración en formato NEON. El contenedor que creamos a mano en el capítulo anterior se escribiría así:

parameters:
	db:
		dsn: 'mysql:'
		user: root
		password: '***'

services:
	- Nette\Database\Connection(%db.dsn%, %db.user%, %db.password%)
	- ArticleFactory
	- EditController

La sintaxis es muy concisa.

Todas las dependencias declaradas en los constructores de las clases ArticleFactory y EditController las descubre y las pasa automáticamente Nette DI gracias al llamado autowiring, así que no hace falta indicar nada en el archivo de configuración. Por tanto, aunque cambien los parámetros, no tiene que cambiar nada en la configuración. Durante el desarrollo, Nette regenera el contenedor automáticamente. Puede concentrarse puramente en desarrollar la aplicación.

Si queremos pasar las dependencias mediante setters, usamos para ello la sección setup.

Nette DI genera directamente el código PHP del contenedor. El resultado es, por tanto, un archivo .php que puede abrir y examinar. Eso le permite ver exactamente cómo funciona el contenedor. También puede depurarlo en su IDE y recorrer su ejecución paso a paso. Y, sobre todo: el código PHP generado es extremadamente rápido.

Nette DI también puede generar el código de una factory a partir de una interfaz dada. Por eso, en lugar de la clase ArticleFactory, en la aplicación solo tenemos que crear una interfaz:

interface ArticleFactory
{
	function create(): Article;
}

Encontrará el ejemplo completo en GitHub.

Uso independiente

Integrar la biblioteca Nette DI en una aplicación es muy fácil. Primero la instalamos con Composer (porque descargar archivos zip está muy pasado de moda):

composer require nette/di

El siguiente código usa el Compiler para crear una instancia del contenedor DI según la configuración guardada en el archivo config.neon:

$loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp');
$class = $loader->load(function ($compiler) {
	$compiler->loadConfig(__DIR__ . '/config.neon');
});
$container = new $class;

El contenedor se genera una sola vez, su código se escribe en la caché (el directorio __DIR__ . '/temp') y en las peticiones siguientes solo se carga desde ahí.

Por sí solo, el Compiler habilita en la configuración únicamente las secciones services y parameters. Para usar las demás (como search, decorator, di o inject) hay que registrar antes sus extensiones. Y para poder registrar extensiones desde la sección extensions de la configuración, añada la ExtensionsExtension:

$compiler->addExtension('search', new Nette\DI\Extensions\SearchExtension($tempDir));
$compiler->addExtension('extensions', new Nette\DI\Extensions\ExtensionsExtension);

El Configurator que se usa en las aplicaciones Nette completas las registra todas automáticamente.

Si guarda varios contenedores distintos en el mismo directorio de caché, distíngalos con una clave pasada como segundo argumento a load(); pasa a formar parte del nombre de la clase generada:

$class = $loader->load(
	fn($compiler) => $compiler->loadConfig(__DIR__ . '/config.neon'),
	'my-key',
);

Para crear y obtener los servicios se usan los métodos getService() o getByType(). Así creamos el objeto EditController:

$controller = $container->getByType(EditController::class);
$controller->someMethod();

Durante el desarrollo conviene activar el modo de refresco automático, en el que el contenedor se regenera solo si se modifica alguna clase o algún archivo de configuración. Basta con pasar true como segundo argumento en el constructor de ContainerLoader.

$loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp', true);

Trabajar con el contenedor

Además de getService() y getByType(), el objeto del contenedor ofrece otros métodos útiles:

  • getByType(string $type, bool $throw = true): ?object devuelve el servicio del tipo dado. Si pasa false como segundo argumento, devuelve null en lugar de lanzar una excepción cuando no existe tal servicio.
  • hasService(string $name): bool e isCreated(string $name): bool le dicen si un servicio está definido y si ya se ha creado su instancia.
  • getParameters(): array devuelve todos los parámetros del contenedor, getParameter($key) devuelve uno solo.
  • createInstance(string $class, array $args = []): object crea una nueva instancia de la clase dada y le pasa las dependencias del constructor mediante autowiring.
  • callMethod(callable $function, array $args = []): mixed llama al callable dado y le pasa sus argumentos mediante autowiring.
  • callInjects(object $service): void llama a todos los métodos inject*() del objeto dado y les pasa las dependencias.

El constructor del contenedor acepta además un array de parámetros que complementan los definidos en la configuración:

$container = new $class(['host' => 'localhost']);

Uso con Nette Framework

Como hemos mostrado, el uso de Nette DI no se limita a las aplicaciones construidas con Nette Framework; puede integrarlo en cualquier sitio con solo tres líneas de código. Ahora bien, si desarrolla aplicaciones con Nette Framework, de la configuración y la creación del contenedor se encarga Bootstrap.