DI – best practise
Souhrn z přednášky Filipa Procházky, kde z praktického hlediska popisoval Depency Injection v Nette 2.1. Video z přednáškou na konci této stránky.
Typy injekce
- konstruktor – ve většině případů preferovaný způsob
- setter
- property
- magie – nikdy!
Modely
- vždy se snaž použít constructor injection
- setter injection použij pouze v případě, když jsi v úzkých
- property ani magii nikdy nepoužívej, není k tomu důvod
Presentery
- z historických a ryze praktických důvodů (
BasePresenter
→ModuleBasePresenter
→ …) není dobré používat u presenterů (i když je to nejčistší řešení) constructor injection (constructor hell) - z toho důvodu existují
inject*
metody, jsou volány automaticky (pouze na presenterech) - protože je ale psaní
inject*
metod hrozná otrava, vznikla anotace@inject
(opět je automaticky zpracována a opět pouze na presenterech)/** @var My\Manager @inject */ public $manager;
Je nutné mít na paměti, že se stále jedná jen o public property, takže její hodnotu může kdokoli změnit a však z pragmatického hlediska je to v případě presenterů nejlepší možné řešení.
Presenter v DI kontejneru
Nově lze presentery registrovat v DIC jako služby, PresenterFactory
se nejprve pokusí získat presenter z DI, až poté vytváří vlastní instanci.
services:
productListPresenter:
class: ShopModule\ProductsPresenter()
setup:
- $tempPath(%tempPath%)
#nebo
- ShopModule\ProductsPresenter()
Komponenty
- vždy se snaž použít constructor injection, není důvod používat cokoli jiného
- ani setter injection, ani property injection není potřeba
Jak dostat komponentu do presenteru?
Problém, který musíme vyřešit je to, jak dostat komponentu do presenteru:
protected function createComponentForm()
{
return new OrderForm($entityManager???);
}
Potřebuji do formuláře dostat určitou závislost – jak to udělám?
Tip: komponenty v Nette popisoval na jedné Posobotě Jan Tvrdík ve své přednášce Jak na komponenty v Nette
Komponenta má být znovupoužitelná část, kterou můžu použít vícekrát, nedává proto smysl registrovat komponenty do DIC jako služby. Nejlepším možným řešením je použít továrničky. Vytvořme si továrničku OrderFormFactory
:
class OrderFormFactory {
private $em;
function __construct(EntityManager $em) { ... }
/** @return OrderForm */
public function create() {
return new OrderForm($this->em);
}
}
Tuto třídu si zaregistrujeme do DIC jako službu a presenteru si pak vyžádám OrderFormFactory
, kterou použiju v presenteru v továrničce komponenty (metodě createComponentForm
). Ještě jednodušším řešením je využít generované továrničky.
Generované továrničky
Napíšu si interface:
interface IOrderFormFactory {
/** @return OrderForm */
function create();
}
Tím se zbavíme povinnosti psát ručně kód továrniček jako byla např. OrderFormFactory
, DIC se postará o vytvoření odpovídající továrničky automaticky.
Poznámka: metoda v interface se musí jmenovat create
.
V konfiguraci poté už jen stačí říct Nette, že chci službu, která implementuje toto rozhraní.
services:
orderFormFactory:
implement: IOrderFormFactory
#nebo
- {implement: IOrderFormFactory}
Nette nám vygeneruje továrničku, která vytvoří instance třídy OrderForm
. OrderForm
může bezproblémů používat autowire, lze mu předávat parametry apod. Generovaná továrnička se postará, aby OrderForm dostal vše co potřebuje.
Poté stačí už jen vyžádat si v presenteru službu implementující IOrderFormFactory
a použít jí pro vytvoření komponenty:
/** @var IOrderFormFactory @inject */
public $orderFormFactory;
protected function createComponentOrderForm()
{
$form = $this->orderFormFactory->create();
$form->onSuccess[] = ...;
return $form;
}
Tip: pokud chcete komponentu použít na více místech, je dobré nedávat výsledná přesměrování přímo do formuláře (komponenty), ale přidat je k formuláři až v dané továrničce (metodě createComponent*
).
Poznámka: ne vždy lze generované továrničky použít, někdy je lepší napsat továrničku ručně.
Rozšíření
V Nette existuje koncept rozšíření.
class OrmExtension extends CompilerExtension {
# vytvori sluzby (zaregistruje je v DIC)
public function loadConfiguration() { }
# upravuje své a jiné služby
public function beforeCompile() { }
# hackuje vytvořený DI container
public function afterCompile() { }
}
Jak vypadá dobré rozšíření:
- musí používat Composer
- má jednoduchou registraci
- řídí se pravidlem Convention over Configuration
- validuje parametry, které dostanete
Video
Zdroje
Další užitečné informace:
- Jak předávat závislosti do presenterů, komponent a jiných služeb? – David Grudl
- Tvorba komponent s využitím autowiringu – Vojtěch Dobeš
- Předání parametrů do komponenty při použití Interface
- Ako predat v extension parameter pre factory create()?
- Předání parametru továrničce
- Formulář jako vlastní komponenta
Nalezli jste nějakou chybu, či nepřesnost? Dejte mi o ní vědět!