Definiowanie usług

Konfiguracja jest miejscem, w którym uczymy kontener DI, jak ma budować poszczególne usługi i jak je łączyć z innymi zależnościami. Nette dostarcza bardzo przejrzysty i elegancki sposób, jak tego dokonać.

Sekcja services w pliku konfiguracyjnym formatu NEON jest miejscem, gdzie definiujemy własne usługi i ich konfiguracje. Spójrzmy na prosty przykład definicji usługi o nazwie database, która reprezentuje instancję klasy PDO:

services:
	database: PDO('sqlite::memory:')

Podana konfiguracja zaowocuje następującą metodą fabrykującą w kontenerze DI:

public function createServiceDatabase(): PDO
{
	return new PDO('sqlite::memory:');
}

Nazwy usług pozwalają nam odwoływać się do nich w innych częściach pliku konfiguracyjnego, w formacie @nazwaUslugi. Jeśli nie ma potrzeby nazywania usługi, możemy po prostu użyć tylko myślnika:

services:
	- PDO('sqlite::memory:')

Aby uzyskać usługę z kontenera DI, możemy wykorzystać metodę getService() z nazwą usługi jako parametrem, lub metodę getByType() z typem usługi:

$database = $container->getService('database');
$database = $container->getByType(PDO::class);

Tworzenie usługi

Zazwyczaj tworzymy usługę po prostu tworząc instancję określonej klasy. Na przykład:

services:
	database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret)

Jeśli potrzebujemy rozszerzyć konfigurację o dodatkowe klucze, można definicję rozpisać na więcej linii:

services:
	database:
		create: PDO('sqlite::memory:')
		setup: ...

Klucz create ma alias factory, obie warianty są w praktyce powszechne. Niemniej jednak zalecamy używanie create.

Argumenty konstruktora lub metody tworzącej mogą być alternatywnie zapisane w kluczu arguments:

services:
	database:
		create: PDO
		arguments: ['mysql:host=127.0.0.1;dbname=test', root, secret]

Usługi nie muszą być tworzone tylko przez proste utworzenie instancji klasy, mogą być również wynikiem wywołania metod statycznych lub metod innych usług:

services:
	database: DatabaseFactory::create()
	router: @routerFactory::create()

Zauważ, że dla uproszczenia zamiast -> używa się ::, zobacz wyrażenia. Wygenerują się te metody fabrykujące:

public function createServiceDatabase(): PDO
{
	return DatabaseFactory::create();
}

public function createServiceRouter(): RouteList
{
	return $this->getService('routerFactory')->create();
}

Kontener DI potrzebuje znać typ utworzonej usługi. Jeśli tworzymy usługę za pomocą metody, która nie ma określonego typu zwracanego, musimy ten typ jawnie podać w konfiguracji:

services:
	database:
		create: DatabaseFactory::create()
		type: PDO

Argumenty

Do konstruktora i metod przekazujemy argumenty w sposób bardzo podobny jak w samym PHP:

services:
	database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret)

Dla lepszej czytelności możemy argumenty rozpisać na osobne linie. W takim przypadku używanie przecinków jest opcjonalne:

services:
	database: PDO(
		'mysql:host=127.0.0.1;dbname=test'
		root
		secret
	)

Argumenty możesz również nazwać i nie musisz się wtedy martwić o ich kolejność:

services:
	database: PDO(
		username: root
		password: secret
		dsn: 'mysql:host=127.0.0.1;dbname=test'
	)

Jeśli chcesz niektóre argumenty pominąć i użyć ich wartości domyślnej lub podstawić usługę za pomocą autowiringu, użyj podkreślenia:

services:
	foo: Foo(_, %appDir%)

Jako argumenty można przekazywać usługi, używać parametrów i wiele więcej, zobacz wyrażenia.

Setup

W sekcji setup definiujemy metody, które mają być wywoływane podczas tworzenia usługi.

services:
	database:
		create: PDO(%dsn%, %user%, %password%)
		setup:
			- setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION)

To w PHP wyglądałoby tak:

public function createServiceDatabase(): PDO
{
	$service = new PDO('...', '...', '...');
	$service->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
	return $service;
}

Oprócz wywoływania metod można również przekazywać wartości do właściwości. Obsługiwane jest również dodanie elementu do tablicy, które należy zapisać w cudzysłowach, aby nie kolidowało ze składnią NEON:

services:
	foo:
		create: Foo
		setup:
			- $value = 123
			- '$onClick[]' = [@bar, clickHandler]

Co w kodzie PHP wyglądałoby następująco:

public function createServiceFoo(): Foo
{
	$service = new Foo;
	$service->value = 123;
	$service->onClick[] = [$this->getService('bar'), 'clickHandler'];
	return $service;
}

W setupie można jednak wywoływać również metody statyczne lub metody innych usług. Jeśli potrzebujesz przekazać jako argument aktualną usługę, podaj ją jako @self:

services:
	foo:
		create: Foo
		setup:
			- My\Helpers::initializeFoo(@self)
			- @anotherService::setFoo(@self)

Zauważ, że dla uproszczenia zamiast -> używa się ::, zobacz wyrażenia. Wygeneruje się taka metoda fabrykująca:

public function createServiceFoo(): Foo
{
	$service = new Foo;
	My\Helpers::initializeFoo($service);
	$this->getService('anotherService')->setFoo($service);
	return $service;
}

Wyrażenia

Nette DI daje nam niezwykle bogate środki wyrazu, za pomocą których możemy zapisać prawie wszystko. W plikach konfiguracyjnych możemy więc wykorzystywać parametry:

# parametr
%wwwDir%

# wartość parametru pod kluczem
%mailer.user%

# parametr wewnątrz stringa
'%wwwDir%/images'

Dalej tworzyć obiekty, wywoływać metody i funkcje:

# tworzenie obiektu
DateTime()

# wywołanie metody statycznej
Collator::create(%locale%)

# wywołanie funkcji PHP
::getenv(DB_USER)

Odwoływać się do usług albo ich nazwą, albo za pomocą typu:

# usługa według nazwy
@database

# usługa według typu
@Nette\Database\Connection

Używać składni first-class callable:

# tworzenie callbacku, odpowiednik [@user, logout]
@user::logout(...)

Używać stałych:

# stała klasy
FilesystemIterator::SKIP_DOTS

# stałą globalną uzyskamy funkcją PHP constant()
::constant(PHP_VERSION)

Wywołania metod można łączyć w łańcuchy tak samo jak w PHP. Tylko dla uproszczenia zamiast -> używa się :::

DateTime()::format('Y-m-d')
# PHP: (new DateTime())->format('Y-m-d')

@http.request::getUrl()::getHost()
# PHP: $this->getService('http.request')->getUrl()->getHost()

Te wyrażenia możesz używać wszędzie, przy tworzeniu usług, w argumentach, w sekcji setup lub parametrach:

parameters:
	ipAddress: @http.request::getRemoteAddress()

services:
	database:
		create: DatabaseFactory::create( @anotherService::getDsn() )
		setup:
			- initialize( ::getenv('DB_USER') )

Funkcje specjalne

W plikach konfiguracyjnych możesz używać tych specjalnych funkcji:

  • not() negacja wartości
  • bool(), int(), float(), string() bezstratne rzutowanie na dany typ
  • typed() stworzy tablicę wszystkich usług określonego typu
  • tagged() stworzenie tablicy wszystkich usług z danym tagiem
services:
	- Foo(
		id: int(::getenv('ProjectId'))
		productionMode: not(%debugMode%)
	)

W przeciwieństwie do klasycznego rzutowania w PHP, jak np. (int), bezstratne rzutowanie rzuci wyjątek dla wartości nieliczbowych.

Funkcja typed() tworzy tablicę wszystkich usług danego typu (klasa lub interfejs). Pomija usługi, które mają wyłączony autowiring. Można podać również więcej typów oddzielonych przecinkiem.

services:
	- BarsDependent( typed(Bar) )

Tablicę usług określonego typu możesz przekazywać jako argument również automatycznie za pomocą autowiringu.

Funkcja tagged() tworzy następnie tablicę wszystkich usług z określonym tagiem. Również tutaj możesz specyfikować więcej tagów oddzielonych przecinkiem.

services:
	- LoggersDependent( tagged(logger) )

Autowiring

Klucz autowired pozwala wpłynąć na zachowanie autowiringu dla konkretnej usługi. Szczegóły znajdziesz w rozdziale o autowiringu.

services:
	foo:
		create: Foo
		autowired: false     # usługa foo jest wyłączona z autowiringu

Usługi lazy

Lazy loading (leniwe ładowanie) to technika, która odkłada tworzenie usługi aż do momentu, gdy jest ona faktycznie potrzebna. W globalnej konfiguracji można włączyć leniwe tworzenie dla wszystkich usług naraz. Dla poszczególnych usług można następnie to zachowanie nadpisać:

services:
	foo:
		create: Foo
		lazy: false

Gdy usługa jest zdefiniowana jako lazy, przy jej żądaniu z kontenera DI otrzymujemy specjalny obiekt zastępczy. Wygląda on i zachowuje się tak samo jak rzeczywista usługa, ale rzeczywista inicjalizacja (wywołanie konstruktora i setupu) nastąpi dopiero przy pierwszym wywołaniu jakiejkolwiek jej metody lub właściwości.

Leniwe ładowanie można stosować tylko dla klas użytkownika, a nie dla wewnętrznych klas PHP. Wymaga PHP 8.4 lub nowszego.

Tagi

Tagi służą do dodawania dodatkowych informacji do usług. Usłudze możesz dodać jeden lub więcej tagów:

services:
	foo:
		create: Foo
		tags:
			- cached

Tagi mogą również przenosić wartości:

services:
	foo:
		create: Foo
		tags:
			logger: monolog.logger.event

Aby uzyskać wszystkie usługi z określonymi tagami, możesz użyć funkcji tagged():

services:
	- LoggersDependent( tagged(logger) )

W kontenerze DI możesz uzyskać nazwy wszystkich usług z określonym tagiem za pomocą metody findByTag():

$names = $container->findByTag('logger');
// $names to tablica zawierająca nazwę usługi i wartość tagu
// np. ['foo' => 'monolog.logger.event', ...]

Tryb Inject

Za pomocą flagi inject: true aktywuje się przekazywanie zależności przez publiczne właściwości z adnotacją inject i metody inject*().

services:
	articles:
		create: App\Model\Articles
		inject: true

Domyślnie inject jest aktywowany tylko dla prezenterów.

Modyfikacja usług

Kontener DI zawiera wiele usług, które zostały dodane za pośrednictwem wbudowanego lub użytkownika rozszerzenia. Możesz modyfikować definicje tych usług bezpośrednio w konfiguracji. Na przykład możesz zmienić klasę usługi application.application, która standardowo jest Nette\Application\Application, na inną:

services:
	application.application:
		create: MyApplication
		alteration: true

Flaga alteration jest informacyjna i mówi, że tylko modyfikujemy istniejącą usługę.

Możemy również uzupełnić setup:

services:
	application.application:
		create: MyApplication
		alteration: true
		setup:
			- '$onStartup[]' = [@resource, init]

Podczas nadpisywania usługi możemy chcieć usunąć oryginalne argumenty, pozycje setup lub tagi, do czego służy reset:

services:
	application.application:
		create: MyApplication
		alteration: true
		reset:
			- arguments
			- setup
			- tags

Jeśli chcesz usunąć usługę dodaną przez rozszerzenie, możesz to zrobić tak:

services:
	cache.journal: false
wersja: 3.x