Definiciones de servicios

Configuración es donde colocamos las definiciones de los servicios personalizados. Esto se hace en la sección services.

Por ejemplo, así es como creamos un servicio llamado database, que será una instancia de la clase PDO:

services:
	database: PDO('sqlite::memory:')

El nombre de los servicios se utiliza para permitirnos referenciarlos. Si un servicio no es referenciado, no hay necesidad de nombrarlo. Así que simplemente usamos una viñeta en lugar de un nombre:

services:
	- PDO('sqlite::memory:')  #  servicio anónimo

Una entrada de una línea puede dividirse en varias líneas para permitir añadir claves adicionales, como setup. El alias de la clave create: es factory:.

services:
	database:
		create: PDO('sqlite::memory:')
		setup: ...

A continuación recuperamos el servicio del contenedor DI usando el método getService() por nombre, o mejor aún, el método getByType() por tipo:

$database = $container->getService('database');
$database = $container->getByType(PDO::class);

Creación de un servicio

La mayoría de las veces, creamos un servicio simplemente creando una instancia de una clase:

services:
	database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret)

Lo que generará un método de fábrica en DI container:

public function createServiceDatabase(): PDO
{
	return new PDO('mysql:host=127.0.0.1;dbname=test', 'root', 'secret');
}

Alternativamente, se puede usar una clave arguments para pasar arguments:

services:
	database:
		create: PDO
		arguments: ['mysql:host=127.0.0.1;dbname=test', root, secret]

Un método estático también puede crear un servicio:

services:
	database: My\Database::create(root, secret)

Corresponde al código PHP:

public function createServiceDatabase(): PDO
{
	return My\Database::create('root', 'secret');
}

Se supone que un método estático My\Database::create() tiene un valor de retorno definido que el contenedor DI necesita conocer. Si no lo tiene, escribimos el tipo en la configuración:

services:
	database:
		create: My\Database::create(root, secret)
		type: PDO

Nette DI te da facilidades de expresión extremadamente potentes para escribir casi cualquier cosa. Por ejemplo, para refer a otro servicio y llamar a su método. Para simplificar, se utiliza :: en lugar de ->.

services:
	routerFactory: App\Router\Factory
	router: @routerFactory::create()

Corresponde al código PHP:

public function createServiceRouterFactory(): App\Router\Factory
{
	return new App\Router\Factory;
}

public function createServiceRouter(): Router
{
	return $this->getService('routerFactory')->create();
}

Las llamadas a métodos se pueden encadenar como en PHP:

services:
	foo: FooFactory::build()::get()

Corresponde a código PHP:

public function createServiceFoo()
{
	return FooFactory::build()->get();
}

Argumentos

Los parámetros con nombre también pueden usarse para pasar argumentos:

services:
	database: PDO(
		'mysql:host=127.0.0.1;dbname=test'  # positional
		username: root                      # named
		password: secret                    # named
	)

El uso de comas es opcional cuando se dividen los argumentos en varias líneas.

Por supuesto, también podemos utilizar otros serviciosparámetros como argumentos:

services:
	- Foo(@anotherService, %appDir%)

Corresponde a código PHP:

public function createService01(): Foo
{
	return new Foo($this->getService('anotherService'), '...');
}

Si el primer argumento es autowired y quieres especificar el segundo, omite el primero con el caracter _, por ejemplo Foo(_, %appDir%). O mejor aún, pase sólo el segundo argumento como un parámetro con nombre, por ejemplo Foo(path: %appDir%).

Nette DI y el formato NEON le ofrecen facilidades expresivas extremadamente potentes para escribir casi cualquier cosa. Así, un argumento puede ser un objeto recién creado, puede llamar a métodos estáticos, métodos de otros servicios, o incluso funciones globales utilizando una notación especial:

services:
	analyser: My\Analyser(
		FilesystemIterator(%appDir%)         # crear objeto
		DateTime::createFromFormat('Y-m-d')  # llamar método estático
		@anotherService                      # pasar otro servicio
		@http.request::getRemoteAddress()    # llamar a otro método de servicio
		::getenv(NetteMode)                  # llamar a una función global
	)

Corresponde al código PHP:

public function createServiceAnalyser(): My\Analyser
{
	return new My\Analyser(
		new FilesystemIterator('...'),
		DateTime::createFromFormat('Y-m-d'),
		$this->getService('anotherService'),
		$this->getService('http.request')->getRemoteAddress(),
		getenv('NetteMode')
	);
}

Funciones especiales

También puede utilizar funciones especiales en los argumentos para convertir o negar valores:

  • not(%arg%) negation
  • bool(%arg%) lossless cast to bool
  • int(%arg%) lossless cast to int
  • float(%arg%) lossless cast to float
  • string(%arg%) lossless cast to string
services:
	- Foo(
		id: int(::getenv('ProjectId'))
		productionMode: not(%debugMode%)
	)

La reescritura sin pérdidas difiere de la reescritura PHP normal, por ejemplo usando (int), en que lanza una excepción para valores no numéricos.

Se pueden pasar múltiples servicios como argumentos. La función typed() crea un array de todos los servicios de un tipo particular (clase o interfaz). La función omitirá los servicios que tengan desactivado el autowiring, y se pueden especificar múltiples tipos separados por una coma.

services:
	- BarsDependent( typed(Bar) )

También puede pasar un array de servicios automáticamente usando autowiring.

La función tagged() crea una matriz de todos los servicios con una determinada tag. Se pueden especificar múltiples etiquetas separadas por una coma.

services:
	- LoggersDependent( tagged(logger) )

Referencia a servicios

Los servicios individuales se referencian utilizando el carácter @ y el nombre, así por ejemplo @database:

services:
	- create: Foo(@database)
	  setup:
			- setCacheStorage(@cache.storage)

Corresponde a código PHP:

public function createService01(): Foo
{
	$service = new Foo($this->getService('database'));
	$service->setCacheStorage($this->getService('cache.storage'));
	return $service;
}

Incluso los servicios anónimos pueden ser referenciados usando una llamada de retorno, simplemente especificando su tipo (clase o interfaz) en lugar de su nombre. Sin embargo, esto no suele ser necesario debido a autowiring.

services:
	- create: Foo(@Nette\Database\Connection)  # or @\PDO
	  setup:
			- setCacheStorage(@cache.storage)

Configuración

En la sección setup listamos los métodos a llamar al crear el servicio:

services:
	database:
		create: PDO(%dsn%, %user%, %password%)
		setup:
			- setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION)

Corresponde al código PHP:

public function createServiceDatabase(): PDO
{
	$service = new PDO('...', '...', '...');
	$service->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
	return $service;
}

También se pueden establecer propiedades. También se puede añadir un elemento a un array, y debe escribirse entre comillas para no entrar en conflicto con la sintaxis de NEON:

services:
	foo:
		create: Foo
		setup:
			- $value = 123
			- '$onClick[]' = [@bar, clickHandler]

Corresponde a código PHP:

public function createServiceFoo(): Foo
{
	$service = new Foo;
	$service->value = 123;
	$service->onClick[] = [$this->getService('bar'), 'clickHandler'];
	return $service;
}

Sin embargo, los métodos estáticos o de otros servicios también pueden ser llamados en la configuración. Les pasamos el servicio real como @self:

services:
	foo:
		create: Foo
		setup:
			- My\Helpers::initializeFoo(@self)
			- @anotherService::setFoo(@self)

Corresponde al código PHP:

public function createServiceFoo(): Foo
{
	$service = new Foo;
	My\Helpers::initializeFoo($service);
	$this->getService('anotherService')->setFoo($service);
	return $service;
}

Autowiring (autocableado)

La clave de autocableado se puede utilizar para excluir un servicio del autocableado o para influir en su comportamiento. Ver chapter on autowiring para más información.

services:
	foo:
		create: Foo
		autowired: false     # foo is removed from autowiring

Etiquetas

Se puede añadir información de usuario a servicios individuales en forma de etiquetas:

services:
	foo:
		create: Foo
		tags:
			- cached

Las etiquetas también pueden tener un valor:

services:
	foo:
		create: Foo
		tags:
			logger: monolog.logger.event

Con la función tagged() se puede pasar como argumento un array de servicios con determinadas etiquetas. También se pueden especificar varias etiquetas separadas por una coma.

services:
	- LoggersDependent( tagged(logger) )

Los nombres de los servicios pueden obtenerse del contenedor DI utilizando el método findByTag():

$names = $container->findByTag('logger');
// $names es una matriz que contiene el nombre del servicio y el valor de la etiqueta
// i.e. ['foo' => 'monolog.logger.event', ...]

Modo de inyección

El indicador inject: true se utiliza para activar el paso de dependencias a través de variables públicas con la anotación inject y los métodos inject*().

services:
	articles:
		create: App\Model\Articles
		inject: true

Por defecto, inject sólo está activado para los presentadores.

Modificación de servicios

Hay una serie de servicios en el contenedor DI que han sido añadidos por built-in o su extensión. Las definiciones de estos servicios se pueden modificar en la configuración. Por ejemplo, para el servicio application.application, que por defecto es un objeto Nette\Application\Application, podemos cambiar la clase:

services:
	application.application:
		create: MyApplication
		alteration: true

La bandera alteration es informativa e indica que estamos modificando un servicio existente.

También podemos añadir una configuración:

services:
	application.application:
		create: MyApplication
		alteration: true
		setup:
			- '$onStartup[]' = [@resource, init]

Al reescribir un servicio, es posible que queramos eliminar los argumentos, elementos de configuración o etiquetas originales, que es para lo que sirve reset:

services:
	application.application:
		create: MyApplication
		alteration: true
		reset:
			- arguments
			- setup
			- tags

Un servicio añadido por extensión también se puede eliminar del contenedor:

services:
	cache.journal: false