Nette skládá závislosti tříd za vás pomocí dependency injection a autowiringu
Dependency injection zní odborně, ale je to jen předání parametrů: co třída potřebuje, dostane zvenčí místo aby si to sháněla sama. Text ukazuje konstruktor injection, DI kontejner, autowiring v souboru NEON i generované továrny Nette – a proč se pak kód líp testuje.

Každý objekt v aplikaci něco potřebuje: úložiště článků potřebuje databázi, odesílač pošty připojení k SMTP, kontroler potřebuje obojí. Otázka je, odkud si to má vzít. Špatná odpověď zní, že si to objekt obstará sám – zavolá globální funkci, sáhne na singleton nebo si spojení vyrobí uvnitř metody. Tím vznikne skrytá vazba, kterou z hlavičky třídy nikdo nepozná.
class ArticleRepository
{
public function save(Article $article): void
{
$db = Database::getInstance(); // skryta vazba na globalni stav
$db->query('INSERT INTO articles ...', $article);
}
}
Kdo tuhle třídu čte, netuší, do jaké databáze se ukládá ani jak ji vyměnit. Dependency injection je na to jednoduchá odpověď: co třída potřebuje, to jí předáte zvenčí jako parametr. Úvodní kapitola dokumentace Nette to shrnuje do jednoho pravidla – ať je vám to podáno. Žádná magie, jen předání argumentů.
Hned na začátku je dobré nesplést si dva pojmy. Dependency injection je návrhový vzor, tedy způsob, jak psát kód. DI kontejner je nástroj, který to předávání za vás obstará. Jedno jde použít bez druhého: injektovat závislosti můžete i bez kontejneru, ručním předáním v konstruktoru.
Závislost je jen parametr
Nejběžnější způsob předání je přes konstruktor. Hodí se pro závislosti, bez kterých objekt nemá smysl – když je nedostane, nedá se vůbec sestavit. Od PHP 8.0 se dá zkrátit takzvanou promocí parametrů, kdy se vlastnost deklaruje rovnou v hlavičce konstruktoru, a od PHP 8.1 ji jde označit jako readonly, aby se po sestavení už neměnila.
class ArticleRepository
{
public function __construct(
private readonly Nette\Database\Explorer $db,
) {
}
public function save(Article $article): void
{
$this->db->table('articles')->insert([
'title' => $article->title,
]);
}
}
Teď je z hlavičky vidět všechno: třída potřebuje databázi a dostane ji zvenčí. Kdo ji použije, ji taky sestaví, a při tom rozhodne, kterou databázi dostane. Vedle konstruktoru zná Nette i předání přes setter (metoda set*()) pro nepovinné závislosti a zápis přímo do veřejné vlastnosti; ten se ale nedoporučuje, protože vlastnost musí být veřejná a stává se součástí rozhraní třídy.
Od ručního skládání ke kontejneru
Předávat parametry je hezké, dokud jich je málo. Jenže úložiště potřebuje databázi, ta potřebuje spojení, kontroler potřebuje úložiště – a než sestavíte jeden objekt, musíte ručně poskládat celý řetěz pod ním:
$controller = new EditController(
new ArticleFactory(
new Nette\Database\Connection($dsn, $user, $password),
),
);
Přesně tuhle práci dělá DI kontejner. Je to objekt, který umí vytvořit a nastavit ostatní objekty – v terminologii Nette se jim říká služby. Poprosíte ho o výsledný objekt a on si sám dohledá, co k jeho sestavení potřebuje. Aby přitom nevznikala pokaždé nová databáze, drží už vytvořené služby stranou a podruhé vrátí tutéž. Dokumentace to ukazuje na kontejneru napsaném ručně; skutečný kontejner v Nette (balíček nette/di, řada 3) si ale kód generuje a aktualizuje sám, takže ho nikdo nepíše ani neudržuje.
Autowiring hledá závislost podle typu
Služby se popisují v konfiguraci ve formátu NEON, což je čitelný textový formát podobný YAMLu. Bez další pomoci byste u každé služby museli vypsat všechny její závislosti:
services:
articles: App\Model\ArticleRepository(@database, @cache.storage)
To je práce, kterou umí odvodit stroj. Slouží k tomu autowiring: podívá se na typy parametrů konstruktoru a dosadí odpovídající službu sám. Zápis se pak scvrkne na jméno třídy:
services:
- App\Model\ArticleRepository
- App\Model\ArticleFactory
Podstatné je, čím se autowiring řídí: výhradně typy, nikdy jmény služeb. Ví proto i o tom, že třída splňuje rozhraní, která implementuje, a třídy, ze kterých dědí. Jméno služby je jen pomocný štítek a jeho přejmenování nic nerozbije.
Má to jednu podmínku: každý typ smí být v kontejneru právě jednou. Kdyby byly dvě služby téhož typu, autowiring by nevěděl, kterou podat, a vyhodí výjimku. Řeší se to buď vyřazením jedné služby z autowiringu (autowired: false), nebo upřednostněním jedné z nich:
services:
mainDb: PDO(%dsn%, %user%, %password%)
tempDb:
create: PDO('sqlite::memory:')
autowired: false # tempDb je z autowiringu vyrazena
articles: App\Model\ArticleRepository # do konstruktoru tedy dostane mainDb
Továrny, které Nette dopíše samo
Kontejner drží službu jako jeden sdílený kus. Někdy ale potřebujete vyrábět nové objekty za běhu – třeba nový článek pokaždé, když ho někdo napíše. K tomu slouží továrna: objekt, který jiné objekty vytváří a dodává jim jejich závislosti. V Nette ji nemusíte psát. Stačí rozhraní s jedinou metodou create() a deklarovaným návratovým typem a implementaci dopíše Nette DI za vás:
interface ArticleFactory
{
function create(): Article;
}
services:
- ArticleFactory
V kódu si pak vyžádáte objekt podle rozhraní a zavoláte create(). Když metoda přijme parametr, předá ho továrna do konstruktoru – podmínkou je, že se parametr jmenuje stejně jako parametr konstruktoru, podle čehož si je Nette spáruje.
class EditController
{
public function __construct(
private ArticleFactory $articleFactory,
) {
}
public function add(): void
{
$article = $this->articleFactory->create();
}
}
Proč se to testuje líp
Výhoda, kterou injektování přináší zadarmo, se ukáže až u testů. Když třída dostává databázi zvenčí, můžete jí v testu místo ostré databáze podat náhradu – třeba SQLite v paměti nebo prázdnou atrapu – a sestavit ji přímo, bez kontejneru:
$repository = new ArticleRepository($fakeDb);
$repository->save($article);
// zadna ostra databaze, zadny globalni stav k prepsani
U té první, skryté verze to nešlo: Database::getInstance() uvnitř metody se v testu těžko obchází, protože sáhne na globální stav, který test neřídí. Úvodní kapitola dokumentace na to míří stejnou otázkou – do jaké databáze vlastně skrytý objekt ukládá, do ostré, nebo testovací, a jak se to změní. S injektováním je odpověď v hlavičce třídy a test si dosadí, co potřebuje.
Kontejner tedy není to hlavní. Hlavní je pravidlo, že si objekt závislost nebere sám, ale dostane ji podanou. Kontejner to jen zautomatizuje ve chvíli, kdy objektů přibude natolik, že by je ruční skládání pohřbilo.
Zdroje
- Nette: What is Dependency Injection? – pravidlo „ať je vám to podáno“ a rozdíl mezi vzorem a kontejnerem.
- Nette: Passing Dependencies – konstruktor, setter a property injection včetně promoce parametrů a
readonly. - Nette: Autowiring – řízení podle typů, podmínka jedné služby na typ a
autowired: false. - Nette: Generated Factories – generování továrny z rozhraní s jednou metodou
create().