Praktický návod
Jak správně používat Dependency Injection v Symfony
Třída nemá potají vyrábět vše, co potřebuje. Nech jí závislosti předat a bude se lépe testovat i měnit.
Nejdřív stručně
O co jde?
Dependency Injection znamená, že si třída nevytváří například logger nebo klienta databáze sama. Dostane je zvenku, nejčastěji v konstruktoru.
Service je objekt, který dělá jednu užitečnou práci. Container v Symfony služby vytvoří, propojí a podle typu je umí automaticky předat tam, kde jsou potřeba.
Připrav si
Co budeš potřebovat
Stačí běžící Symfony projekt a jedna malá služba, kterou chceš použít z kontroleru nebo jiného objektu.
- Symfony projekt se standardní konfigurací services.yaml.
- Jednu konkrétní odpovědnost. Třeba sestavení čísla objednávky nebo odeslání notifikace.
- Rozhraní tam, kde chceš skrýt konkrétního dodavatele, například e-mailovou nebo platební službu.
- Základní test nebo místo, odkud službu zavoláš. Díky němu hned zjistíš, zda je propojená správně.
Kroky 1 až 3
Předávej závislosti jednoduše
Začni konstruktorovou injekcí. Je čitelná, povinné závislosti jsou na první pohled vidět a dobře se testuje.
1. Vytvoř malou službu
- Do src/Service dej třídu s jedním jasným úkolem. Název popisuj podle práce, ne podle technologie.
- Nedávej do ní HttpFoundation Request ani celý container jen proto, že se to zrovna hodí. Potřebná data předej metodě.
- Pokud služba potřebuje jinou službu, přidej ji jako typovaný argument konstruktoru.
mkdir -p src/Service Oficiální Symfony dokumentace k service containeru 2. Nech Symfony použít autowiring
- Ve výchozím projektu Symfony obvykle automaticky najde třídy v src/ a vytvoří z nich služby.
- V konstruktoru napiš konkrétní typ, například LoggerInterface. Container podle typu najde odpovídající objekt a předá ho.
- Když existují dvě možné implementace stejného rozhraní, vyber jednu explicitně v services.yaml. Hádání by později bolelo.
php bin/console debug:autowiring LoggerInterface Oficiální Symfony dokumentace k autowiringu 3. Vstup služby používej přes rozhraní
- Tam, kde se může dodavatel změnit, závis na vlastním rozhraní. Například PaymentGateway místo konkrétního SDK klienta.
- Konkrétní implementaci vyber v konfiguraci. Aplikační kód pak neví, zda je to ostrá služba, testovací náhrada nebo lokální simulace.
- Služba, která má deset závislostí, je signál ke zmenšení odpovědnosti. Neřeš to přidáním jedenácté.
php bin/console debug:container --tag=app.service Oficiální Symfony dokumentace ke konfiguraci služeb Krok 4
Ověř propojení dřív, než přidáš další kód
Container umí sám říct, co zná a kam se závislost ztratila.
-
Najdi svou službu v containeru
Nahraď App\Service\OrderNumberGenerator názvem své třídy. Vypíše se definice i její argumenty.
php bin/console debug:container App\Service\OrderNumberGenerator -
Zkontroluj typ závislosti
Příkaz ukáže, které služby Symfony umí předat pro dané rozhraní nebo třídu.
php bin/console debug:autowiring -
Spusť svůj malý test
V testu předdej jednoduchou náhradu závislosti a ověř výsledek. Právě tady se ukáže výhoda konstruktorové injekce.
php bin/phpunit
Když to zlobí
Nejčastější chyby
Cannot autowire service
Container nezná typ argumentu nebo neví, kterou implementaci zvolit. Zkontroluj namespace, typ v konstruktoru a případně přidej explicitní vazbu rozhraní na implementaci do services.yaml.
php bin/console debug:autowiring Do služby předáváš celý container
Je to pohodlné jen první den. Vyber konkrétní závislosti a dej je do konstruktoru. Kód pak prozradí, co opravdu potřebuje, a test nebude stavět polovinu aplikace.
Kontroler obsahuje všechnu logiku
Kontroler má přijmout požadavek a vrátit odpověď. Pravidla pro objednávku, výpočet nebo integraci přesuň do malé služby, kterou kontroler jen zavolá.
Změna jedné služby rozbije deset testů
Služba má pravděpodobně příliš mnoho odpovědností nebo závisí na konkrétních implementacích. Rozděl ji podle use-caseů a na hranách používej rozhraní.
Hotovo
Závislosti už nejsou schované.
Dependency Injection ti teď pomáhá udržet třídy malé a čitelné. Stejný princip používej při každé nové službě.