Lazy objekty v PHP 8.4 a jejich využití v Nette DI
- David Grudl
- Nette Core | 8258
PHP 8.4 přináší nativní podporu lazy objektů. Lazy objekt je speciální typ objektu, který odkládá svoji skutečnou inicializaci až do chvíle, kdy je potřeba – konkrétně když poprvé přistoupíme k některé jeho vlastnosti. Do té doby existuje jen jako „prázdná“ instance, která nezabírá zbytečné prostředky ani nevykonává potenciálně náročné operace z konstruktoru.
A právě tato vlastnost je skvělá pro DI kontejner v Nette. Ten běžně při startu aplikace vytváří celý strom závislostí – tedy všechny služby a jejich závislosti. To ale může být zbytečně náročné, protože ne všechny služby jsou v každém requestu skutečně potřeba.
V nové testovací verzi nette/di 3.2-dev lze zapnout lazy vytváření služeb pomocí jednoduché konfigurace:
di:
lazy: true
Co to znamená v praxi? Když si necháte z kontejneru vytáhnout nějakou službu, třeba:
$database = $container->getByType(Database::class);
Dostanete lazy objekt, který vypadá jako normální instance
Database
, nepoznáte rozdíl, ale ve skutečnosti ještě nemá
vytvořené připojení k databázi. K tomu dojde až v momentě, kdy
s databází začnete skutečně pracovat:
$database->query('SELECT ...'); // zde teprve dojde k vytvoření připojení
Pokud v daném requestu databázi vůbec nepoužijete, ušetříte prostředky za její inicializaci. To může být významná optimalizace, zejména u větších aplikací s komplexním stromem závislostí.
Samozřejmě to má i své důsledky – případné chyby při vytváření služeb (třeba špatné přihlašovací údaje k databázi) se projeví až při prvním použití, ne hned při startu aplikace. To může být v některých případech výhoda, v jiných nevýhoda, podle konkrétního use-case.
Bude zajímavé sledovat, jaký dopad bude mít tato novinka na výkon aplikací v produkčním prostředí. Potenciál pro optimalizaci je zde značný.
Při implementaci jsem nejprve zvažoval, že by se lazy zapínalo pro jednotlivé služby. Pak mi došlo, že opravdu bomba to bude, když se to naopak zapne pro úplně všechny služby a následně umožní selektivě vypínat. Na interní PHP třídy to vliv nemá, ty nelze lazyloadovat.
- Marek Bartoš
- Nette Blogger | 1298
Super funkce, těším se na smazání všech accesorů
Pak mi došlo, že opravdu bomba to bude, když se to naopak zapne pro úplně všechny služby a následně umožní selektivě vypínat.
A jak se to dá vypnout? V konfiguraci a v extension. Některé služby (např. obousměrných bridge pro Tracy a Monolog) nejspíš budu muset inicializovat ihned, aby se správně propojily.
Editoval Marek Bartoš (25. 11. 2024 23:33)
- David Grudl
- Nette Core | 8258
Uvědomil jsem si jeden zajímavý side efekt. Díky lazy loadingu jde vytvářet cyklické závislosti mezi službami, aniž by to způsobovalo problémy.
Představte si klasický případ: Služba A potřebuje službu B a služba B zároveň potřebuje službu A. V běžném DI containeru by to vedlo k vyhození výjimky „Circular reference detected“ při inicializaci. DI container smyčku detekuje, aby se PHP nezacyklilo, ale výsledkem je, že takové závislosti prostě nemůžete vytvořit.
S lazy objekty je to ale jinak. Služba A dostane při vytvoření lazy proxy služby B, která se inicializuje až v momentě, kdy A skutečně přistoupí k metodám nebo vlastnostem B. V té době už služba A existuje a může být předána službě B jako závislost.
V praxi to znamená, že můžete najednou klidně vytvořit container, který takové závislosti má a vůbec si toho nevšimnout. Je to dobře nebo špatně? Nechávám na vás. Ale může nastat problém, pokud takovou aplikaci nahrajete na server, kde je PHP < 8.4 a lazy objekty nejsou podporovány – cyklické závislosti, které dosud fungovaly, vyhodí výjimku.
- MajklNajt
- Člen | 513
David Grudl napsal(a):
Uvědomil jsem si jeden zajímavý side efekt. Díky lazy loadingu jde vytvářet cyklické závislosti mezi službami, aniž by to způsobovalo problémy.
Představte si klasický případ: Služba A potřebuje službu B a služba B zároveň potřebuje službu A. V běžném DI containeru by to vedlo k vyhození výjimky „Circular reference detected“ při inicializaci. DI container smyčku detekuje, aby se PHP nezacyklilo, ale výsledkem je, že takové závislosti prostě nemůžete vytvořit.
S lazy objekty je to ale jinak. Služba A dostane při vytvoření lazy proxy služby B, která se inicializuje až v momentě, kdy A skutečně přistoupí k metodám nebo vlastnostem B. V té době už služba A existuje a může být předána službě B jako závislost.
V praxi to znamená, že můžete najednou klidně vytvořit container, který takové závislosti má a vůbec si toho nevšimnout. Je to dobře nebo špatně? Nechávám na vás. Ale může nastat problém, pokud takovou aplikaci nahrajete na server, kde je PHP < 8.4 a lazy objekty nejsou podporovány – cyklické závislosti, které dosud fungovaly, vyhodí výjimku.
Podľa mňa tento side efekt nie je nejako zásadný, pretože
- ak vyvíjam aplikáciu pre PHP 8.4 a nahrám to na 8.3, a priori by som mal
dostať chybu od
composeru
Composer detected issues in your platform: Your Composer dependencies require a PHP version ">= 8.4.0".
- osobne si myslím, že cyklická závislosť v správne navrhnutej architektúre nemá čo robiť
Editoval MajklNajt (17. 3. 8:20)
- David Grudl
- Nette Core | 8258
Mluvím o situaci, kdy vyvyjis aplikaci pro PHP < 8.4 a máš na vývojářském počítači PHP 8.4