Definizione dei servizi
La configurazione è il luogo in cui diciamo al container DI come creare i singoli servizi e come collegarli alle loro dipendenze. Nette offre un modo molto chiaro ed elegante di farlo.
La sezione services del file di configurazione NEON è il luogo in cui definiamo i nostri servizi e le loro
configurazioni. Guardiamo un semplice esempio che definisce un servizio chiamato database, il quale rappresenta
un'istanza della classe PDO:
services:
database: PDO('sqlite::memory:')
La configurazione qui sopra produce il seguente metodo factory nel container DI:
public function createServiceDatabase(): PDO
{
return new PDO('sqlite::memory:');
}
I nomi dei servizi permettono di farvi riferimento in altre parti del file di configurazione, con il formato
@nomeServizio. Se non serve assegnare un nome al servizio, possiamo semplicemente usare un trattino
(-):
services:
- PDO('sqlite::memory:')
Per ottenere un servizio dal container DI possiamo usare il metodo getService(), con il nome del servizio come
parametro, oppure il metodo getByType(), con il tipo del servizio:
$database = $container->getService('database');
$database = $container->getByType(PDO::class);
Creazione dei servizi
Di norma creiamo un servizio semplicemente istanziando una determinata classe. Per esempio:
services:
database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret)
Se dobbiamo ampliare la configurazione con altre chiavi, la definizione si può spezzare su più righe:
services:
database:
create: PDO('sqlite::memory:')
setup: ...
La chiave create ha un alias, factory; entrambe le varianti sono di uso comune. Consigliamo però di
usare create.
Gli argomenti del costruttore o del metodo factory si possono indicare, in alternativa, con la chiave
arguments:
services:
database:
create: PDO
arguments: ['mysql:host=127.0.0.1;dbname=test', root, secret]
I servizi non devono per forza essere creati con la semplice istanziazione di una classe: possono essere anche il risultato della chiamata di metodi statici o di metodi di altri servizi:
services:
database: DatabaseFactory::create()
router: @routerFactory::create()
Notate che, per semplicità, si usa :: invece di ->, vedi Linguaggio delle espressioni. Verranno generati questi metodi factory:
public function createServiceDatabase(): PDO
{
return DatabaseFactory::create();
}
public function createServiceRouter(): RouteList
{
return $this->getService('routerFactory')->create();
}
Il container DI deve conoscere il tipo del servizio che sta creando. Se creiamo un servizio con un metodo privo di tipo di ritorno dichiarato, dobbiamo indicare esplicitamente questo tipo nella configurazione:
services:
database:
create: DatabaseFactory::create()
type: PDO
Argomenti
Passiamo gli argomenti ai costruttori e ai metodi in modo molto simile a come si fa in PHP:
services:
database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret)
Per una migliore leggibilità possiamo elencare gli argomenti su righe separate. In tal caso le virgole diventano facoltative:
services:
database: PDO(
'mysql:host=127.0.0.1;dbname=test'
root
secret
)
Potete anche dare un nome agli argomenti, eliminando la necessità di preoccuparvi del loro ordine:
services:
database: PDO(
username: root
password: secret
dsn: 'mysql:host=127.0.0.1;dbname=test'
)
Se volete omettere certi argomenti e usarne i valori predefiniti, oppure farvi iniettare un servizio tramite l'autowiring, usate un trattino basso (_):
services:
foo: Foo(_, %appDir%)
Gli argomenti possono comprendere servizi, parametri e molto altro, vedi Linguaggio delle espressioni.
Setup
Nella sezione setup definiamo i metodi da chiamare al momento della creazione del servizio.
services:
database:
create: PDO(%dsn%, %user%, %password%)
setup:
- setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION)
In PHP avrebbe questo aspetto:
public function createServiceDatabase(): PDO
{
$service = new PDO('...', '...', '...');
$service->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
return $service;
}
Oltre alle chiamate di metodo si possono anche assegnare valori alle proprietà. È supportata anche l'aggiunta di elementi agli array, il che richiede di racchiudere tra apici l'accesso all'array, per evitare conflitti con la sintassi NEON:
services:
foo:
create: Foo
setup:
- $value = 123
- '$onClick[]' = [@bar, clickHandler]
Che in codice PHP avrebbe questo aspetto:
public function createServiceFoo(): Foo
{
$service = new Foo;
$service->value = 123;
$service->onClick[] = [$this->getService('bar'), 'clickHandler'];
return $service;
}
Nel setup potete però chiamare anche metodi statici o metodi di altri servizi. Se dovete passare come argomento il servizio
corrente stesso, fatevi riferimento con @self:
services:
foo:
create: Foo
setup:
- My\Helpers::initializeFoo(@self)
- @anotherService::setFoo(@self)
Notate che, per semplicità, si usa :: invece di ->, vedi Linguaggio delle espressioni. Verrà generato questo metodo factory:
public function createServiceFoo(): Foo
{
$service = new Foo;
My\Helpers::initializeFoo($service);
$this->getService('anotherService')->setFoo($service);
return $service;
}
Linguaggio delle espressioni
Nette DI offre un linguaggio delle espressioni eccezionalmente ricco, con il quale possiamo definire quasi qualsiasi cosa. Nei file di configurazione possiamo quindi usare i parametri:
# parametro
%wwwDir%
# valore di un parametro sotto una chiave
%mailer.user%
# parametro dentro una stringa
'%wwwDir%/images'
Inoltre creare oggetti, chiamare metodi e funzioni:
# crea un oggetto
DateTime()
# chiama un metodo statico
Collator::create(%locale%)
# chiama una funzione PHP
::getenv(DB_USER)
Fare riferimento ai servizi per nome oppure per tipo:
# servizio per nome
@database
# servizio per tipo
@Nette\Database\Connection
Usare la first-class callable syntax:
# crea una callback, equivalente a [@user, logout]
@user::logout(...)
Usare le costanti:
# costante di classe
FilesystemIterator::SKIP_DOTS
# ottiene una costante globale con la funzione PHP constant()
::constant(\PHP_VERSION)
Accedere alle proprietà pubbliche e alle costanti di un servizio con @servizio::membro. Se il nome indichi una
proprietà o una costante lo decide la sua prima lettera: un'iniziale minuscola significa una proprietà pubblica, una maiuscola
significa una costante:
# proprietà pubblica di un servizio (inizia con una lettera minuscola)
@settings::apiUrl
# costante di classe di un servizio (inizia con una lettera maiuscola)
@settings::Version
Le chiamate di metodo si possono concatenare come in PHP. Per semplicità si usa :: invece di
->:
DateTime()::format('Y-m-d')
# PHP: (new DateTime())->format('Y-m-d')
@http.request::getUrl()::getHost()
# PHP: $this->getService('http.request')->getUrl()->getHost()
Potete usare queste espressioni ovunque: nella creazione dei servizi, negli argomenti, nella sezione setup oppure nei parametri:
parameters:
ipAddress: @http.request::getRemoteAddress()
services:
database:
create: DatabaseFactory::create( @anotherService::getDsn() )
setup:
- initialize( ::getenv('DB_USER') )
Funzioni speciali
Nei file di configurazione potete usare queste funzioni speciali:
not()nega un valorebool(),int(),float(),string()conversione senza perdita nel tipo indicatotyped()crea un array di tutti i servizi del tipo indicatotagged()crea un array di tutti i servizi con il tag indicato
services:
- Foo(
id: int(::getenv('ProjectId'))
productionMode: not(%debugMode%)
)
A differenza della conversione standard di PHP, come (int), la conversione senza perdita solleva un'eccezione per
i valori non numerici.
La funzione typed() crea un array di tutti i servizi del tipo indicato (classe o interfaccia). Esclude
i servizi che hanno l'autowiring disattivato. Si possono indicare anche più tipi, separati da virgole.
services:
- BarsDependent( typed(Bar) )
Un array di servizi di un certo tipo si può passare come argomento anche automaticamente, tramite l'autowiring.
La funzione tagged() crea invece un array di tutti i servizi con un determinato tag. Anche qui potete indicare
più tag separati da virgole.
services:
- LoggersDependent( tagged(logger) )
Autowiring
La chiave autowired vi permette di influire sul comportamento dell'autowiring per un determinato servizio. Per
i dettagli vedi il capitolo sull'autowiring.
services:
foo:
create: Foo
autowired: false # il servizio foo è escluso dall'autowiring
Servizi pigri
Il caricamento pigro è una tecnica che rimanda la creazione di un servizio finché non serve davvero. Nella configurazione globale potete attivare la creazione pigra per tutti i servizi in una volta. Per i singoli servizi potete poi sovrascrivere questo comportamento:
services:
foo:
create: Foo
lazy: false
Quando un servizio è definito come pigro, chiedendolo al container DI riceviamo un oggetto proxy speciale. Questo proxy sembra e si comporta esattamente come il servizio reale, ma l'inizializzazione vera e propria (la chiamata del costruttore e quelle del setup) avviene solo al primo accesso a uno qualsiasi dei suoi metodi o delle sue proprietà.
Tenete presente che, poiché il servizio viene creato più tardi, anche gli errori nella sua configurazione si manifestano più tardi. Per esempio, credenziali del database sbagliate non si riveleranno all'avvio dell'applicazione, ma solo alla prima query.
La creazione pigra attenua anche le dipendenze circolari, cioè la situazione in cui il servizio A richiede il servizio B e
contemporaneamente B richiede A. Senza di essa il container segnala l'errore Circular reference detected. Con un
proxy pigro il servizio A riceve solo un proxy del servizio B, che si inizializza quando viene davvero usato, in un momento in
cui A esiste già. Una dipendenza circolare, comunque, segnala una progettazione difettosa, ed è meglio liberarsene.
Il caricamento pigro richiede PHP 8.4 o successivo e funziona solo per i servizi creati istanziando
direttamente una classe (per esempio create: Foo), non per quelli creati da un metodo factory. Non si può usare
nemmeno per le classi che in ultima analisi estendono una classe interna di PHP. Quando il caricamento pigro non si può
applicare, il flag lazy: true viene ignorato in silenzio.
Tag
I tag servono ad aggiungere informazioni supplementari ai servizi. Potete assegnare a un servizio uno o più tag:
services:
foo:
create: Foo
tags:
- cached
I tag possono anche contenere valori:
services:
foo:
create: Foo
tags:
logger: monolog.logger.event
Per ottenere tutti i servizi associati a determinati tag potete usare la funzione tagged():
services:
- LoggersDependent( tagged(logger) )
All'interno del container DI potete ottenere i nomi di tutti i servizi con un determinato tag usando il metodo
findByTag():
$names = $container->findByTag('logger');
// $names è un array con i nomi dei servizi come chiavi e i valori dei tag come valori
// per esempio ['foo' => 'monolog.logger.event', ...]
Modalità inject
Usando il flag inject: true si attiva la dependency injection tramite le proprietà pubbliche con l'attributo Inject e tramite i metodi inject*().
services:
articles:
create: App\Model\Articles
inject: true
Per impostazione predefinita la modalità inject è attiva solo per i presenter.
Modifiche ai servizi
Il container DI contiene numerosi servizi aggiunti da estensioni integrate o dell'utente. Potete
modificare le definizioni di questi servizi esistenti direttamente nella configurazione. Per esempio potete cambiare la classe del
servizio application.application, che di norma è Nette\Application\Application, con un'altra:
services:
application.application:
create: MyApplication
alteration: true
Il flag alteration indica che stiamo solo modificando un servizio esistente. Funge anche da protezione: se il
servizio da modificare non esiste, la compilazione fallisce con un'eccezione.
Possiamo anche integrare il setup:
services:
application.application:
create: MyApplication
alteration: true
setup:
- '$onStartup[]' = [@resource, init]
Non dovete identificare un servizio con il suo nome interno: potete fare riferimento a esso per tipo. L'esempio precedente si può scrivere anche così:
services:
@Nette\Application\Application:
create: MyApplication
Modificando un servizio potremmo voler rimuovere gli argomenti, gli elementi del setup o i tag originali, con la chiave
reset:
services:
application.application:
create: MyApplication
alteration: true
reset:
arguments: true
setup: true
tags: true
Se volete rimuovere un servizio aggiunto da un'estensione, potete farlo così:
services:
cache.journal: false