Jak vytvářet složité komponenty s mnoha závislostmi, aniž bychom si zaneřádili presentery, které je budou používat?
Článek je psán pro Nette 2.1.
Díky chytrým vlastnostem Dependency Injection kontejneru, kterým Nette disponuje, lze stejně jako u psaní klasických služeb nechat většinu práce na frameworku. Vezměme si jako příklad registrační formulář.
Tato komponenta bude muset umět vícenásobně komunikovat s modelovou vrstvou:
1) Zjistit, jestli zadaný email jěště není registrován.
2) Provést samotnou registraci.
3) Odeslat uživateli email s odkazem k potvrzení.
4) Pracovat s SDK Facebooku, abychom umožnili registraci přes tuto sociální síť.
Ukázková registrační komponenta
K řešení takového registračního procesu by šlo pochopitelně přistoupit různými způsoby, ale pro účely našeho příkladu předpokládejme, že každý z těchto bodů znamená jednu službu, kterou je třeba formulářové komponentě předat jako závislost.
class RegistrationControl extends Nette\Application\UI\Control
{
private $emailValidator, $registrator, $mailer, $facebook;
public function __construct(EmailValidator $emailValidator, Registrator $registrator, Nette\Mail\IMailer $mailer, Facebook $facebook)
{
$this->emailValidator = $emailValidator;
// etc.
}
}
Jak předat komponentě závislosti?
Pokud bychom psali klasickou službu, nebylo by co řešit, o předání všech závislostí by se neviditelně postaral DI kontejner. Jenže s komponentami obvykle zacházíme tak, že jejich novou instanci vytváříme přímo v presenteru v jejich tzv. továrničce (metodě createComponent...).
Logickou otázkou je, proč prostě nezaregistrujeme komponentu jako klasickou službu, a tu jen z továrničky nevracíme. Takový přístup je ale krajně nevhodný, když chceme komponentu například vytvářet vícenásobně pomocí Nette\Application\UI\Multiplier.
Zdlouhavou a těžkopádnou variantou je předat si všechny 4 závislosti do presenteru a v továrničce je předat komponentě jako argumenty konstruktoru. Budeme však v presenteru muset zavést 4 privátní proměnné, které mimo továrničku nenajdou žádné uplatnění. Kromě toho jen při pomýšlení na množství napsaného kódu se nám může dělat nevolno...
Architektonicky správným řešením, které neznečistí presenter zbytečnými proměnnými, je napsat pro komponentu její tovární službu. Tato třída bude umět pouze to, že vytvoří instanci komponenty. Mohla by vypadat takto:
class RegistrationControlFactory
{
private $emailValidator, $registrator, $mailer, $facebook;
public function __construct(EmailValidator $emailValidator, Registrator $registrator, Nette\Mail\IMailer $mailer, Facebook $facebook)
{
$this->emailValidator = $emailValidator;
$this->registrator = $registrator;
$this->mailer = $mailer;
$this->facebook = $facebook;
}
public function create()
{
return new RegistrationControl($this->emailValidator, $this->registrator, $this->mailer, $this->facebook);
}
}
Takovou tovární třídu pak stačí zaregistrovat jako službu v config.neon...
services:
- RegistrationControlFactory
...předat ji presenteru jako závislost...
private $registrationControlFactory;
public function injectRegistrationControlFactory(RegistrationControlFactory $factory)
{
$this->registrationControlFactory = $factory;
}
...a v továrničce zavolat metodu create(). Takovou metodu můžeme bezpečně volat i vícekrát při použití Nette\Application\UI\Multiplier.
protected function createComponentRegistration()
{
return $this->registrationControlFactory->create();
}
Jak si ušetřit psaní
Asi si teď klepete na čelo, jak jsme si to vlastně pomohli. Množství kódu zůstalo, jen se z presenteru přesunulo do zvláštní třídy. Framework má však skryté eso v rukávu. Konceptu tovární třídy totiž rozumí, a dokáže takovou službu dokonce napsat za nás!
Klíčem k využití této užitečné feature je interface. Pokud napíšeme interface s metodou create(), a dáme DI kontejneru vědět, že jej má chápat jako tovární třídu, o napsání implementace se postará za nás.
Interface pro naši registrační komponentu by mohl vypadat takto:
interface IRegistrationControlFactory
{
/** @return RegistrationControl */
function create();
}
Metoda create() je konvencí. Interface musí obsahovat pouze ji. Přípustnou alternativou je get().
Registraci továrny jako služby upravíme následovně:
services:
- IRegistrationControlFactory
Všimněte si anotace @return u deklarace metody create - podle té Nette pozná, jakou instanci má továrna vytvořit.
Dokonce není potřeba uvádět FQN (Fully qualified name), Nette v souboru s továrnou rozezná namespace i use klauzule.
V presenteru pak pouze upravíme typehint v injectRegistrationControlFactory metodě z RegistrationControlFactory na IRegistrationControlFactory, a je to. Kódu, kde se jen předávají závislosti, jsme se úspěšně zbavili - o to se postará autowiring. A zároveň můžeme vytvářet nové instance tam, kde jsou potřeba.
Jak vytvářet složité komponenty s mnoha závislostmi, aniž bychom si zaneřádili presentery, které je budou používat?
Článek je psán pro Nette 2.1.
Díky chytrým vlastnostem Dependency Injection kontejneru, kterým Nette disponuje, lze stejně jako u psaní klasických služeb nechat většinu práce na frameworku. Vezměme si jako příklad registrační formulář.
Tato komponenta bude muset umět vícenásobně komunikovat s modelovou vrstvou:
1) Zjistit, jestli zadaný email jěště není registrován. 2) Provést samotnou registraci. 3) Odeslat uživateli email s odkazem k potvrzení. 4) Pracovat s SDK Facebooku, abychom umožnili registraci přes tuto sociální síť.
Ukázková registrační komponenta
K řešení takového registračního procesu by šlo pochopitelně přistoupit různými způsoby, ale pro účely našeho příkladu předpokládejme, že každý z těchto bodů znamená jednu službu, kterou je třeba formulářové komponentě předat jako závislost.
Jak předat komponentě závislosti?
Pokud bychom psali klasickou službu, nebylo by co řešit, o předání všech závislostí by se neviditelně postaral DI kontejner. Jenže s komponentami obvykle zacházíme tak, že jejich novou instanci vytváříme přímo v presenteru v jejich tzv. továrničce (metodě
createComponent...
).Logickou otázkou je, proč prostě nezaregistrujeme komponentu jako klasickou službu, a tu jen z továrničky nevracíme. Takový přístup je ale krajně nevhodný, když chceme komponentu například vytvářet vícenásobně pomocí
Nette\Application\UI\Multiplier
.Zdlouhavou a těžkopádnou variantou je předat si všechny 4 závislosti do presenteru a v továrničce je předat komponentě jako argumenty konstruktoru. Budeme však v presenteru muset zavést 4 privátní proměnné, které mimo továrničku nenajdou žádné uplatnění. Kromě toho jen při pomýšlení na množství napsaného kódu se nám může dělat nevolno...
Architektonicky správným řešením, které neznečistí presenter zbytečnými proměnnými, je napsat pro komponentu její tovární službu. Tato třída bude umět pouze to, že vytvoří instanci komponenty. Mohla by vypadat takto:
Takovou tovární třídu pak stačí zaregistrovat jako službu v
config.neon
......předat ji presenteru jako závislost...
...a v továrničce zavolat metodu
create()
. Takovou metodu můžeme bezpečně volat i vícekrát při použitíNette\Application\UI\Multiplier
.Jak si ušetřit psaní
Asi si teď klepete na čelo, jak jsme si to vlastně pomohli. Množství kódu zůstalo, jen se z presenteru přesunulo do zvláštní třídy. Framework má však skryté eso v rukávu. Konceptu tovární třídy totiž rozumí, a dokáže takovou službu dokonce napsat za nás!
Klíčem k využití této užitečné feature je interface. Pokud napíšeme interface s metodou
create()
, a dáme DI kontejneru vědět, že jej má chápat jako tovární třídu, o napsání implementace se postará za nás.Interface pro naši registrační komponentu by mohl vypadat takto:
Metoda
create()
je konvencí. Interface musí obsahovat pouze ji. Přípustnou alternativou jeget()
.Registraci továrny jako služby upravíme následovně:
Všimněte si anotace
@return
u deklarace metodycreate
- podle té Nette pozná, jakou instanci má továrna vytvořit.Dokonce není potřeba uvádět FQN (Fully qualified name), Nette v souboru s továrnou rozezná
namespace
iuse
klauzule.V presenteru pak pouze upravíme typehint v
injectRegistrationControlFactory
metodě zRegistrationControlFactory
naIRegistrationControlFactory
, a je to. Kódu, kde se jen předávají závislosti, jsme se úspěšně zbavili - o to se postará autowiring. A zároveň můžeme vytvářet nové instance tam, kde jsou potřeba.{{author: Vojtěch Dobeš|3328}}