Definirea serviciilor
Configurația este locul unde învățăm containerul DI cum să asambleze serviciile individuale și cum să le conecteze cu alte dependențe. Nette oferă o modalitate foarte clară și elegantă de a realiza acest lucru.
Secțiunea services
din fișierul de configurație în format NEON este locul unde definim serviciile proprii și
configurațiile lor. Să vedem un exemplu simplu de definire a unui serviciu numit database
, care reprezintă
o instanță a clasei PDO
:
services:
database: PDO('sqlite::memory:')
Configurația menționată va rezulta în următoarea metodă factory în containerul DI:
public function createServiceDatabase(): PDO
{
return new PDO('sqlite::memory:');
}
Numele serviciilor ne permit să ne referim la ele în alte părți ale fișierului de configurație, în formatul
@numeServiciu
. Dacă nu este necesar să numim serviciul, putem folosi pur și simplu doar o liniuță:
services:
- PDO('sqlite::memory:')
Pentru a obține un serviciu din containerul DI, putem utiliza metoda getService()
cu numele serviciului ca
parametru, sau metoda getByType()
cu tipul serviciului:
$database = $container->getService('database');
$database = $container->getByType(PDO::class);
Crearea serviciului
De cele mai multe ori, creăm un serviciu pur și simplu prin crearea unei instanțe a unei anumite clase. De exemplu:
services:
database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret)
Dacă avem nevoie să extindem configurația cu alte chei, definiția poate fi împărțită pe mai multe rânduri:
services:
database:
create: PDO('sqlite::memory:')
setup: ...
Cheia create
are aliasul factory
, ambele variante sunt comune în practică. Cu toate acestea,
recomandăm utilizarea create
.
Argumentele constructorului sau ale metodei de creare pot fi alternativ scrise în cheia arguments
:
services:
database:
create: PDO
arguments: ['mysql:host=127.0.0.1;dbname=test', root, secret]
Serviciile nu trebuie create doar prin simpla instanțiere a unei clase, ele pot fi, de asemenea, rezultatul apelării metodelor statice sau metodelor altor servicii:
services:
database: DatabaseFactory::create()
router: @routerFactory::create()
Observați că, pentru simplitate, în loc de ->
se folosește ::
, vezi Expresii. Se vor genera aceste metode factory:
public function createServiceDatabase(): PDO
{
return DatabaseFactory::create();
}
public function createServiceRouter(): RouteList
{
return $this->getService('routerFactory')->create();
}
Containerul DI trebuie să cunoască tipul serviciului creat. Dacă creăm un serviciu folosind o metodă care nu are specificat tipul returnat, trebuie să specificăm explicit acest tip în configurație:
services:
database:
create: DatabaseFactory::create()
type: PDO
Argumente
Transmitem argumente constructorului și metodelor într-un mod foarte similar cu cel din PHP însuși:
services:
database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret)
Pentru o mai bună lizibilitate, putem împărți argumentele pe rânduri separate. În acest caz, utilizarea virgulelor este opțională:
services:
database: PDO(
'mysql:host=127.0.0.1;dbname=test'
root
secret
)
Puteți, de asemenea, să numiți argumentele și nu trebuie să vă mai faceți griji cu privire la ordinea lor:
services:
database: PDO(
username: root
password: secret
dsn: 'mysql:host=127.0.0.1;dbname=test'
)
Dacă doriți să omiteți unele argumente și să folosiți valoarea lor implicită sau să injectați un serviciu folosind autowiring, utilizați underscore _
:
services:
foo: Foo(_, %appDir%)
Ca argumente se pot transmite servicii, se pot utiliza parametri și multe altele, vezi Expresii.
Setup
În secțiunea setup
definim metodele care trebuie apelate la crearea serviciului.
services:
database:
create: PDO(%dsn%, %user%, %password%)
setup:
- setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION)
Acest lucru ar arăta astfel în PHP:
public function createServiceDatabase(): PDO
{
$service = new PDO('...', '...', '...');
$service->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
return $service;
}
Pe lângă apelarea metodelor, se pot transmite și valori către proprietăți. Este suportată și adăugarea unui element într-un array, care trebuie scris între ghilimele pentru a nu intra în conflict cu sintaxa NEON:
services:
foo:
create: Foo
setup:
- $value = 123
- '$onClick[]' = [@bar, clickHandler]
Ceea ce în codul PHP ar arăta astfel:
public function createServiceFoo(): Foo
{
$service = new Foo;
$service->value = 123;
$service->onClick[] = [$this->getService('bar'), 'clickHandler'];
return $service;
}
În setup se pot apela însă și metode statice sau metode ale altor servicii. Dacă aveți nevoie să transmiteți serviciul
curent ca argument, specificați-l ca @self
:
services:
foo:
create: Foo
setup:
- My\Helpers::initializeFoo(@self)
- @anotherService::setFoo(@self)
Observați că, pentru simplitate, în loc de ->
se folosește ::
, vezi Expresii. Se va genera o astfel de metodă factory:
public function createServiceFoo(): Foo
{
$service = new Foo;
My\Helpers::initializeFoo($service);
$this->getService('anotherService')->setFoo($service);
return $service;
}
Expresii
Nette DI ne oferă expresii extrem de bogate, cu ajutorul cărora putem scrie aproape orice. În fișierele de configurație putem astfel utiliza parametri:
# parametru
%wwwDir%
# valoarea parametrului sub cheie
%mailer.user%
# parametru în interiorul șirului
'%wwwDir%/images'
Mai departe, putem crea obiecte, apela metode și funcții:
# crearea obiectului
DateTime()
# apelarea metodei statice
Collator::create(%locale%)
# apelarea funcției PHP
::getenv(DB_USER)
Ne putem referi la servicii fie după numele lor, fie după tip:
# serviciu după nume
@database
# serviciu după tip
@Nette\Database\Connection
Putem folosi sintaxa first-class callable:
# crearea callback-ului, echivalent cu [@user, logout]
@user::logout(...)
Putem folosi constante:
# constanta clasei
FilesystemIterator::SKIP_DOTS
# constanta globală o obținem cu funcția PHP constant()
::constant(PHP_VERSION)
Apelurile metodelor pot fi înlănțuite la fel ca în PHP. Doar pentru simplitate, în loc de ->
se folosește
::
:
DateTime()::format('Y-m-d')
# PHP: (new DateTime())->format('Y-m-d')
@http.request::getUrl()::getHost()
# PHP: $this->getService('http.request')->getUrl()->getHost()
Aceste expresii le puteți utiliza oriunde, la crearea serviciilor, în argumente, în secțiunea Setup sau în parametri:
parameters:
ipAddress: @http.request::getRemoteAddress()
services:
database:
create: DatabaseFactory::create( @anotherService::getDsn() )
setup:
- initialize( ::getenv('DB_USER') )
Funcții speciale
În fișierele de configurație puteți utiliza aceste funcții speciale:
not()
negația valoriibool()
,int()
,float()
,string()
conversie de tip fără pierderi la tipul specificattyped()
creează un array al tuturor serviciilor de tipul specificattagged()
creează un array al tuturor serviciilor cu tag-ul dat
services:
- Foo(
id: int(::getenv('ProjectId'))
productionMode: not(%debugMode%)
)
Spre deosebire de conversia de tip clasică în PHP, cum ar fi de ex. (int)
, conversia de tip fără pierderi va
arunca o excepție pentru valorile non-numerice.
Funcția typed()
creează un array al tuturor serviciilor de tipul dat (clasă sau interfață). Omite serviciile
care au autowiring-ul dezactivat. Se pot specifica și mai multe tipuri separate prin virgulă.
services:
- BarsDependent( typed(Bar) )
Puteți transmite array-ul de servicii de un anumit tip ca argument și automat folosind autowiring.
Funcția tagged()
creează apoi un array al tuturor serviciilor cu un anumit tag. Și aici puteți specifica mai
multe tag-uri separate prin virgulă.
services:
- LoggersDependent( tagged(logger) )
Autowiring
Cheia autowired
permite influențarea comportamentului autowiring-ului pentru un serviciu specific. Pentru
detalii, vezi capitolul despre autowiring.
services:
foo:
create: Foo
autowired: false # serviciul foo este exclus din autowiring
Servicii lazy
Încărcarea leneșă (Lazy loading) este o tehnică care amână crearea unui serviciu până în momentul în care este efectiv necesar. În configurația globală se poate permite crearea lazy pentru toate serviciile simultan. Pentru servicii individuale, puteți apoi suprascrie acest comportament:
services:
foo:
create: Foo
lazy: false
Când un serviciu este definit ca lazy, la solicitarea sa din containerul DI, primim un obiect substituent special. Acesta arată și se comportă la fel ca serviciul real, dar inițializarea reală (apelarea constructorului și a setup-ului) are loc abia la primul apel al oricărei metode sau proprietăți ale sale.
Încărcarea leneșă poate fi utilizată numai pentru clasele definite de utilizator, nu și pentru clasele interne PHP. Necesită PHP 8.4 sau o versiune mai recentă.
Tag-uri
Tag-urile servesc la adăugarea de informații suplimentare serviciilor. Puteți adăuga unul sau mai multe tag-uri unui serviciu:
services:
foo:
create: Foo
tags:
- cached
Tag-urile pot purta și valori:
services:
foo:
create: Foo
tags:
logger: monolog.logger.event
Pentru a obține toate serviciile cu anumite tag-uri, puteți utiliza funcția tagged()
:
services:
- LoggersDependent( tagged(logger) )
În containerul DI puteți obține numele tuturor serviciilor cu un anumit tag folosind metoda findByTag()
:
$names = $container->findByTag('logger');
// $names este un array care conține numele serviciului și valoarea tag-ului
// de ex. ['foo' => 'monolog.logger.event', ...]
Mod Inject
Folosind flag-ul inject: true
se activează transmiterea dependențelor prin proprietăți publice cu adnotarea inject și metodele inject*().
services:
articles:
create: App\Model\Articles
inject: true
În mod implicit, inject
este activat doar pentru presenteri.
Modificarea serviciilor
Containerul DI conține multe servicii care au fost adăugate prin extensii încorporate sau extensii
utilizator. Puteți modifica definițiile acestor servicii direct în configurație. De exemplu, puteți schimba clasa
serviciului application.application
, care este standard Nette\Application\Application
, cu alta:
services:
application.application:
create: MyApplication
alteration: true
Flag-ul alteration
este informativ și indică faptul că doar modificăm un serviciu existent.
Putem, de asemenea, completa setup-ul:
services:
application.application:
create: MyApplication
alteration: true
setup:
- '$onStartup[]' = [@resource, init]
La suprascrierea unui serviciu, putem dori să eliminăm argumentele originale, elementele setup sau tag-urile, pentru aceasta
folosim reset
:
services:
application.application:
create: MyApplication
alteration: true
reset:
- arguments
- setup
- tags
Dacă doriți să eliminați un serviciu adăugat de o extensie, o puteți face astfel:
services:
cache.journal: false