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ścibool()
,int()
,float()
,string()
bezstratne rzutowanie na dany typtyped()
stworzy tablicę wszystkich usług określonego typutagged()
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