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!