Definindo Serviços

A configuração é o local onde ensinamos ao contêiner de DI como construir serviços individuais e como conectá-los a outras dependências. O Nette fornece uma maneira muito clara e elegante de conseguir isso.

A seção services no arquivo de configuração no formato NEON é onde definimos nossos próprios serviços e suas configurações. Vejamos um exemplo simples de definição de um serviço chamado database, que representa uma instância da classe PDO:

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

A configuração fornecida resultará no seguinte método de fábrica no Contêiner de DI:

public function createServiceDatabase(): PDO
{
	return new PDO('sqlite::memory:');
}

Os nomes dos serviços nos permitem referenciá-los em outras partes do arquivo de configuração, no formato @nomeDoServico. Se não for necessário nomear o serviço, podemos simplesmente usar um marcador:

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

Para obter um serviço do contêiner de DI, podemos usar o método getService() com o nome do serviço como parâmetro, ou o método getByType() com o tipo do serviço:

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

Criação do serviço

Geralmente, criamos um serviço simplesmente criando uma instância de uma determinada classe. Por exemplo:

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

Se precisarmos estender a configuração com outras chaves, a definição pode ser dividida em várias linhas:

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

A chave create tem um alias factory, ambas as variantes são comuns na prática. No entanto, recomendamos usar create.

Os argumentos do construtor ou do método de criação podem ser escritos alternativamente na chave arguments:

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

Os serviços não precisam ser criados apenas pela simples criação de uma instância de classe, eles também podem ser o resultado da chamada de métodos estáticos ou métodos de outros serviços:

services:
	database: DatabaseFactory::create()
	router: @routerFactory::create()

Observe que, para simplificar, :: é usado em vez de ->, veja expressões. Os seguintes métodos de fábrica serão gerados:

public function createServiceDatabase(): PDO
{
	return DatabaseFactory::create();
}

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

O contêiner de DI precisa saber o tipo do serviço criado. Se criarmos um serviço usando um método que não tem um tipo de retorno especificado, devemos especificar explicitamente esse tipo na configuração:

services:
	database:
		create: DatabaseFactory::create()
		type: PDO

Argumentos

Passamos argumentos para o construtor e métodos de maneira muito semelhante ao próprio PHP:

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

Para melhor legibilidade, podemos dividir os argumentos em linhas separadas. Nesse caso, o uso de vírgulas é opcional:

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

Você também pode nomear os argumentos e não precisa se preocupar com a ordem deles:

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

Se você quiser omitir alguns argumentos e usar seu valor padrão ou injetar um serviço usando autowiring, use um sublinhado:

services:
	foo: Foo(_, %appDir%)

Como argumentos, é possível passar serviços, usar parâmetros e muito mais, veja expressões.

Setup

Na seção setup, definimos os métodos que devem ser chamados ao criar o serviço.

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

Isso seria assim em PHP:

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

Além de chamar métodos, também é possível passar valores para propriedades. A adição de um elemento a um array também é suportada, o que precisa ser escrito entre aspas para não colidir com a sintaxe NEON:

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

O que seria assim no código PHP:

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

No setup, no entanto, também é possível chamar métodos estáticos ou métodos de outros serviços. Se você precisar passar o serviço atual como argumento, indique-o como @self:

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

Observe que, para simplificar, :: é usado em vez de ->, veja expressões. O seguinte método de fábrica será gerado:

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

Expressões

Nette DI nos dá recursos de expressão extraordinariamente ricos, com os quais podemos escrever quase qualquer coisa. Nos arquivos de configuração, podemos usar parâmetros:

# parâmetro
%wwwDir%

# valor do parâmetro sob a chave
%mailer.user%

# parâmetro dentro de uma string
'%wwwDir%/images'

Além disso, criar objetos, chamar métodos e funções:

# criação de objeto
DateTime()

# chamada de método estático
Collator::create(%locale%)

# chamada de função PHP
::getenv(DB_USER)

Referenciar serviços pelo nome ou pelo tipo:

# serviço por nome
@database

# serviço por tipo
@Nette\Database\Connection

Usar a sintaxe first-class callable:

# criação de callback, análogo a [@user, logout]
@user::logout(...)

Usar constantes:

# constante de classe
FilesystemIterator::SKIP_DOTS

# constante global obtida pela função PHP constant()
::constant(PHP_VERSION)

As chamadas de método podem ser encadeadas como em PHP. Apenas para simplificar, :: é usado em vez de ->:

DateTime()::format('Y-m-d')
# PHP: (new DateTime())->format('Y-m-d')

@http.request::getUrl()::getHost()
# PHP: $this->getService('http.request')->getUrl()->getHost()

Você pode usar essas expressões em qualquer lugar, ao criar serviços, em argumentos, na seção Setup ou em parâmetros:

parameters:
	ipAddress: @http.request::getRemoteAddress()

services:
	database:
		create: DatabaseFactory::create( @anotherService::getDsn() )
		setup:
			- initialize( ::getenv('DB_USER') )

Funções especiais

Nos arquivos de configuração, você pode usar estas funções especiais:

  • not() negação do valor
  • bool(), int(), float(), string() conversão sem perdas para o tipo especificado
  • typed() cria um array de todos os serviços do tipo especificado
  • tagged() cria um array de todos os serviços com a tag especificada
services:
	- Foo(
		id: int(::getenv('ProjectId'))
		productionMode: not(%debugMode%)
	)

Em comparação com a conversão de tipo clássica em PHP, como (int), a conversão sem perdas lançará uma exceção para valores não numéricos.

A função typed() cria um array de todos os serviços de um determinado tipo (classe ou interface). Ela omite serviços que têm o autowiring desativado. É possível especificar vários tipos separados por vírgula.

services:
	- BarsDependent( typed(Bar) )

Você também pode passar um array de serviços de um determinado tipo como argumento automaticamente usando autowiring.

A função tagged() então cria um array de todos os serviços com uma determinada tag. Aqui também você pode especificar várias tags separadas por vírgula.

services:
	- LoggersDependent( tagged(logger) )

Autowiring

A chave autowired permite influenciar o comportamento do autowiring para um serviço específico. Para detalhes, veja o capítulo sobre autowiring.

services:
	foo:
		create: Foo
		autowired: false     # o serviço foo é excluído do autowiring

Serviços Lazy

Lazy loading é uma técnica que adia a criação de um serviço até o momento em que ele é realmente necessário. Na configuração global, é possível habilitar a criação lazy para todos os serviços de uma vez. Para serviços individuais, você pode então substituir esse comportamento:

services:
	foo:
		create: Foo
		lazy: false

Quando um serviço é definido como lazy, ao solicitá-lo do contêiner de DI, recebemos um objeto substituto especial. Ele parece e se comporta da mesma forma que o serviço real, mas a inicialização real (chamada do construtor e setup) ocorre apenas na primeira chamada de qualquer um de seus métodos ou propriedades.

O lazy loading pode ser usado apenas para classes de usuário, não para classes internas do PHP. Requer PHP 8.4 ou mais recente.

Tags

As tags servem para adicionar informações complementares aos serviços. Você pode adicionar uma ou mais tags a um serviço:

services:
	foo:
		create: Foo
		tags:
			- cached

As tags também podem carregar valores:

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

Para obter todos os serviços com certas tags, você pode usar a função tagged():

services:
	- LoggersDependent( tagged(logger) )

No contêiner de DI, você pode obter os nomes de todos os serviços com uma determinada tag usando o método findByTag():

$names = $container->findByTag('logger');
// $names é um array contendo o nome do serviço e o valor da tag
// por exemplo, ['foo' => 'monolog.logger.event', ...]

Modo Inject

Usando o sinalizador inject: true, a passagem de dependências é ativada através de propriedades públicas com a anotação inject e métodos inject*().

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

Por padrão, inject é ativado apenas para presenters.

Modificação de serviços

O contêiner de DI contém muitos serviços que foram adicionados através de extensões embutidas ou de usuário. Você pode modificar as definições desses serviços diretamente na configuração. Por exemplo, você pode alterar a classe do serviço application.application, que por padrão é Nette\Application\Application, para outra:

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

O sinalizador alteration é informativo e indica que estamos apenas modificando um serviço existente.

Também podemos complementar o setup:

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

Ao sobrescrever um serviço, podemos querer remover os argumentos originais, itens de setup ou tags, para o qual usamos reset:

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

Se você quiser remover um serviço adicionado por uma extensão, pode fazer assim:

services:
	cache.journal: false
versão: 3.x