Definizione dei servizi
La configurazione è il luogo in cui insegniamo al container DI come costruire i singoli servizi e come collegarli ad altre dipendenze. Nette fornisce un modo molto chiaro ed elegante per raggiungere questo obiettivo.
La sezione services
nel file di configurazione in formato NEON è il luogo in cui definiamo i nostri servizi
personalizzati e le loro configurazioni. Vediamo un semplice esempio di definizione di un servizio chiamato database
,
che rappresenta un'istanza della classe PDO
:
services:
database: PDO('sqlite::memory:')
La configurazione specificata darà luogo al seguente metodo factory nel container DI:
public function createServiceDatabase(): PDO
{
return new PDO('sqlite::memory:');
}
I nomi dei servizi ci consentono di fare riferimento ad essi in altre parti del file di configurazione, nel formato
@nomeServizio
. Se non è necessario nominare il servizio, possiamo semplicemente usare solo un trattino:
services:
- PDO('sqlite::memory:')
Per ottenere un servizio dal container DI, possiamo utilizzare il metodo getService()
con il nome del servizio
come parametro, o il metodo getByType()
con il tipo del servizio:
$database = $container->getService('database');
$database = $container->getByType(PDO::class);
Creazione del servizio
Di solito creiamo un servizio semplicemente creando un'istanza di una certa classe. Ad esempio:
services:
database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret)
Se abbiamo bisogno di estendere la configurazione con ulteriori chiavi, la definizione può essere suddivisa su più righe:
services:
database:
create: PDO('sqlite::memory:')
setup: ...
La chiave create
ha un alias factory
, entrambe le varianti sono comuni nella pratica. Tuttavia,
raccomandiamo di usare create
.
Gli argomenti del costruttore o del metodo di creazione possono essere alternativamente scritti nella chiave
arguments
:
services:
database:
create: PDO
arguments: ['mysql:host=127.0.0.1;dbname=test', root, secret]
I servizi non devono essere creati solo tramite la semplice creazione di un'istanza di classe, possono anche essere il risultato della chiamata di metodi statici o metodi di altri servizi:
services:
database: DatabaseFactory::create()
router: @routerFactory::create()
Notate che per semplicità, invece di ->
si usa ::
, vedi 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 ha bisogno di conoscere il tipo del servizio creato. Se creiamo un servizio tramite un metodo che non ha un tipo di ritorno specificato, dobbiamo indicare esplicitamente questo tipo nella configurazione:
services:
database:
create: DatabaseFactory::create()
type: PDO
Argomenti
Passiamo gli argomenti al costruttore e ai metodi in modo molto simile a come avviene in PHP stesso:
services:
database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret)
Per una migliore leggibilità, possiamo suddividere gli argomenti su righe separate. In tal caso, l'uso delle virgole è opzionale:
services:
database: PDO(
'mysql:host=127.0.0.1;dbname=test'
root
secret
)
Puoi anche nominare gli argomenti e non dovrai preoccuparti del loro ordine:
services:
database: PDO(
username: root
password: secret
dsn: 'mysql:host=127.0.0.1;dbname=test'
)
Se vuoi omettere alcuni argomenti e usare il loro valore predefinito o inserire un servizio tramite autowiring, usa un trattino basso:
services:
foo: Foo(_, %appDir%)
Come argomenti si possono passare servizi, usare parametri e molto altro, vedi Espressioni.
Setup
Nella sezione setup
definiamo i metodi che devono essere chiamati durante la creazione del servizio.
services:
database:
create: PDO(%dsn%, %user%, %password%)
setup:
- setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION)
Questo in PHP sarebbe simile a:
public function createServiceDatabase(): PDO
{
$service = new PDO('...', '...', '...');
$service->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
return $service;
}
Oltre alla chiamata di metodi, è possibile anche passare valori alle proprietà. È supportata anche l'aggiunta di un elemento a un array, che deve essere scritto tra virgolette per non entrare in conflitto con la sintassi NEON:
services:
foo:
create: Foo
setup:
- $value = 123
- '$onClick[]' = [@bar, clickHandler]
Che nel codice PHP sarebbe simile a:
public function createServiceFoo(): Foo
{
$service = new Foo;
$service->value = 123;
$service->onClick[] = [$this->getService('bar'), 'clickHandler'];
return $service;
}
Nel setup è tuttavia possibile chiamare anche metodi statici o metodi di altri servizi. Se hai bisogno di passare come
argomento il servizio corrente, indicalo come @self
:
services:
foo:
create: Foo
setup:
- My\Helpers::initializeFoo(@self)
- @anotherService::setFoo(@self)
Notate che per semplicità, invece di ->
si usa ::
, vedi Espressioni. Verrà generato un tale metodo factory:
public function createServiceFoo(): Foo
{
$service = new Foo;
My\Helpers::initializeFoo($service);
$this->getService('anotherService')->setFoo($service);
return $service;
}
Espressioni
Nette DI ci offre mezzi espressivi eccezionalmente ricchi, con i quali possiamo scrivere quasi qualsiasi cosa. Nei file di configurazione possiamo quindi utilizzare parametri:
# parametro
%wwwDir%
# valore del parametro sotto la chiave
%mailer.user%
# parametro all'interno di una stringa
'%wwwDir%/images'
Inoltre creare oggetti, chiamare metodi e funzioni:
# creazione di un oggetto
DateTime()
# chiamata di un metodo statico
Collator::create(%locale%)
# chiamata di una funzione PHP
::getenv(DB_USER)
Fare riferimento ai servizi tramite il loro nome o tipo:
# servizio per nome
@database
# servizio per tipo
@Nette\Database\Connection
Utilizzare la sintassi first-class callable:
# creazione di un callback, analogo a [@user, logout]
@user::logout(...)
Utilizzare le costanti:
# costante di classe
FilesystemIterator::SKIP_DOTS
# costante globale ottenuta tramite la funzione PHP constant()
::constant(PHP_VERSION)
Le chiamate ai metodi possono essere concatenate come in PHP. Solo per semplicità, invece di ->
si usa
::
:
DateTime()::format('Y-m-d')
# PHP: (new DateTime())->format('Y-m-d')
@http.request::getUrl()::getHost()
# PHP: $this->getService('http.request')->getUrl()->getHost()
Queste espressioni possono essere utilizzate ovunque, durante la creazione dei servizi, negli argomenti, nella sezione setup o 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 è possibile utilizzare queste funzioni speciali:
not()
negazione del valorebool()
,int()
,float()
,string()
conversione senza perdita al tipo specificatotyped()
crea un array di tutti i servizi del tipo specificatotagged()
crea un array di tutti i servizi con il tag specificato
services:
- Foo(
id: int(::getenv('ProjectId'))
productionMode: not(%debugMode%)
)
Rispetto al classico type casting in PHP, come ad esempio (int)
, la conversione senza perdita genera un'eccezione
per valori non numerici.
La funzione typed()
crea un array di tutti i servizi del tipo specificato (classe o interfaccia). Omette
i servizi che hanno l'autowiring disabilitato. È possibile specificare anche più tipi separati da virgola.
services:
- BarsDependent( typed(Bar) )
È possibile passare l'array di servizi di un certo tipo come argomento anche automaticamente tramite autowiring.
La funzione tagged()
crea quindi un array di tutti i servizi con un determinato tag. Anche qui è possibile
specificare più tag separati da virgola.
services:
- LoggersDependent( tagged(logger) )
Autowiring
La chiave autowired
consente di influenzare il comportamento dell'autowiring per un servizio specifico. Per
i dettagli, vedi capitolo sull'autowiring.
services:
foo:
create: Foo
autowired: false # il servizio foo è escluso dall'autowiring
Servizi Lazy
Il lazy loading è una tecnica che posticipa la creazione di un servizio fino al momento in cui è effettivamente necessario. Nella configurazione globale è possibile abilitare la creazione lazy per tutti i servizi contemporaneamente. Per i singoli servizi è poi possibile sovrascrivere questo comportamento:
services:
foo:
create: Foo
lazy: false
Quando un servizio è definito come lazy, al momento della sua richiesta dal container DI, otteniamo uno speciale oggetto placeholder. Questo sembra e si comporta come il servizio reale, ma l'inizializzazione effettiva (chiamata del costruttore e del setup) avviene solo alla prima chiamata di uno qualsiasi dei suoi metodi o proprietà.
Il lazy loading può essere utilizzato solo per classi utente, non per classi PHP interne. Richiede PHP 8.4 o versioni successive.
Tag
I tag servono per aggiungere informazioni supplementari ai servizi. A un servizio è possibile aggiungere 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 con determinati tag, puoi usare la funzione tagged()
:
services:
- LoggersDependent( tagged(logger) )
Nel container DI è possibile ottenere i nomi di tutti i servizi con un determinato tag tramite il metodo
findByTag()
:
$names = $container->findByTag('logger');
// $names è un array contenente il nome del servizio e il valore del tag
// es. ['foo' => 'monolog.logger.event', ...]
Modalità Inject
Tramite il flag inject: true
si attiva il passaggio delle dipendenze tramite variabili pubbliche con l'annotazione
inject e i metodi inject*().
services:
articles:
create: App\Model\Articles
inject: true
Nell'impostazione predefinita, inject
è attivato solo per i presenter.
Modifica dei servizi
Il container DI contiene molti servizi che sono stati aggiunti tramite estensioni integrate o estensioni utente. È possibile modificare le definizioni di questi servizi direttamente nella
configurazione. Ad esempio, è possibile modificare la classe del servizio application.application
, che è standard
Nette\Application\Application
, in un'altra:
services:
application.application:
create: MyApplication
alteration: true
Il flag alteration
è informativo e indica che stiamo solo modificando un servizio esistente.
Possiamo anche completare il setup:
services:
application.application:
create: MyApplication
alteration: true
setup:
- '$onStartup[]' = [@resource, init]
Durante la sovrascrittura di un servizio, potremmo voler rimuovere gli argomenti originali, le voci di setup o i tag, a tale
scopo serve reset
:
services:
application.application:
create: MyApplication
alteration: true
reset:
- arguments
- setup
- tags
Se si desidera rimuovere un servizio aggiunto da un'estensione, è possibile farlo in questo modo:
services:
cache.journal: false