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 valorii
  • bool(), int(), float(), string() conversie de tip fără pierderi la tipul specificat
  • typed() creează un array al tuturor serviciilor de tipul specificat
  • tagged() 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
versiune: 3.x