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 valorbool()
,int()
,float()
,string()
conversão sem perdas para o tipo especificadotyped()
cria um array de todos os serviços do tipo especificadotagged()
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