[2011–05–05] Finalizace Dependency Injection
- Filip Procházka
- Moderator | 4668
Tak schválně, rád se poučím. Ukaž jak ActiveRecord využívá Dependency Injection.
- Yrwein
- Člen | 45
Je problém (technický) vytvářet entity uvnitř nějaké továrny (namísto rovnou operátoru new), která se dokáže postarat o injektnutí spojení k db? (A nyní neřeším, jestli taková knihovna nyní existuje, neb to sám nevím. .])
Edit: Aneb vzor ActiveRecord imho nepředpokládá, že musí přistupovat ke svým závislostem z nějakého statického globálního kontextu.
Editoval Yrwein (14. 6. 2011 19:29)
- Filip Procházka
- Moderator | 4668
Nojo, ale to už není ten pure statickej ActiveRecord :) Kam se vůbec
řadí Nette\Database
a NotORM
? K AR to má určitě
blízko, tojo…
- gofry
- Člen | 6
David Grudl napsal(a):
Chcete vidět Active Record s DI? http://goo.gl/yZDa9
Presne na toto som sa akurát pozeral a nie je mi úplne jasné, prečo sa do
konštruktoru predáva $users
miesto $database
. Nie je
úlohou práve modelu Authenticator, aby sa o všetko postaral sám? Tj. o to,
kde sú users uložení, ako ich vytiahnúť a ako ich autentikovať?
- Šaman
- Člen | 2659
gofry napsal(a):
Presne na toto som sa akurát pozeral a nie je mi úplne jasné, prečo sa do konštruktoru predáva
$users
miesto$database
. Nie je úlohou práve modelu Authenticator, aby sa o všetko postaral sám? Tj. o to, kde sú users uložení, ako ich vytiahnúť a ako ich autentikovať?
Určitě nemá. Authenticator má autentizovat, tedy rozhodnout jestli uživatel je skutečně ten, za kterého se vydává. A je mu šumák kde se ten uživatel vzal. O to se stará jiná část modelu.
A pokud budeš chtít automaticky testovat authenticator, předáš mu pole testovacích dat a víš co máš mít na výstupu. Pokud by si sám sahal do databáze, tak je špatně testovatelný. A navíc by se porušil princip jedna funkce na jednom místě (UsersModel i Authenticator by si implementovaly načítání usera.)
Editoval Šaman (16. 7. 2011 5:58)
- Jan Suchánek
- Člen | 404
Zajímavý článek od Vaška Purcharta o Dependency Injection.
Editoval jenicek (18. 7. 2011 17:06)
- gofry
- Člen | 6
Šaman napsal(a):
gofry napsal(a):
Presne na toto som sa akurát pozeral a nie je mi úplne jasné, prečo sa do konštruktoru predáva
$users
miesto$database
. Nie je úlohou práve modelu Authenticator, aby sa o všetko postaral sám? Tj. o to, kde sú users uložení, ako ich vytiahnúť a ako ich autentikovať?Určitě nemá. Authenticator má autentizovat, tedy rozhodnout jestli uživatel je skutečně ten, za kterého se vydává. A je mu šumák kde se ten uživatel vzal. O to se stará jiná část modelu.
A pokud budeš chtít automaticky testovat authenticator, předáš mu pole testovacích dat a víš co máš mít na výstupu. Pokud by si sám sahal do databáze, tak je špatně testovatelný. A navíc by se porušil princip jedna funkce na jednom místě (UsersModel i Authenticator by si implementovaly načítání usera.)
Mal by teda existovať model User s metodou getAllUsers() a jej výstup by sa predal authenticatoru? Pozeral som si totiž príklad CD Collection z dev verzie Nette a tam je len Model.php, ktorý v sebe obsahuje ako getAlbums() tak createAuthenticatorService(). Nejak sa mi nevidí, aby tieto metódy boli spolu v jednom modeli.
- Filip Procházka
- Moderator | 4668
Ne, měla by existovat služba, která ti načte uživatele podle jména (nebo emailu) a tu předáš authenticatoru. Authenticator ji potom zavolá. Ta mu vrátí nějaký výsledek a on porovná heslo a popř. další detaily.
Třeba takto:
use use Nette\Security\AuthenticationException;
class Authenticator extends Nette\Object implements Nette\Security\IAuthenticator
{
/** @var Users */
private $users;
/**
* @param Users $users
*/
public function __construct(Users $users)
{
$this->users = $users;
}
/**
* @param array $credentials
* @return Nette\Security\IIdentity
*/
public function authenticate(array $credentials)
{
$identity = $this->users->findByNameOrEmail($credentials[self::USERNAME]);
if (!$identity instanceof Identity) {
throw new AuthenticationException('User not found', self::IDENTITY_NOT_FOUND);
} elseif (!$identity->isPasswordValid($credentials[self::PASSWORD])) {
throw new AuthenticationException('Invalid password', self::INVALID_CREDENTIAL);
} elseif (!$identity->isApproved()) {
throw new AuthenticationException('Account is not approved', self::NOT_APPROVED);
}
return $identity;
}
}
Ty pak authenticator klasicky registruješ třeba v Configu, nebo Configuratoru. Třeba takto:
services:
users:
class: Users
arguments: [@connection]
authenticator:
class: Authenticator
agruments: [@users]
- gofry
- Člen | 6
Hm, takže tá služba vlastne nie je nič iné ako model, akurát miesto toho, aby som nejakému inému modelu alebo presenteru apod. predával výstupy z daného modelu, tak mu predám rovno celý model.
A keď niekto využíva veľa iných modelov, tak mu predám rovno celý kontajner modelov, nech sa nemusí o každý model starať samostatne. Alebo dám rovno každému celý kontajner všetkých modelov a on si už z nich vyberie čo potrebuje. Rozumiem tomu správne?
- Filip Procházka
- Moderator | 4668
gofry napsal(a):
Hm, takže tá služba vlastne nie je nič iné ako model, akurát miesto toho, aby som nejakému inému modelu alebo presenteru apod. predával výstupy z daného modelu, tak mu predám rovno celý model.
Modely můžou o sobě navzájem vědět a jeden druhý využívat.
A keď niekto využíva veľa iných modelov, tak mu predám rovno celý kontajner modelov, nech sa nemusí o každý model starať samostatne. Alebo dám rovno každému celý kontajner všetkých modelov a on si už z nich vyberie čo potrebuje. Rozumiem tomu správne?
Tohle je už není tak správně jak by se mohlo zdát. Tím že dáš každému modelu celý kontejner, tak mu v podstatě dáváš přístup i ke službám, které nepotřebuješ a trochu tím špaleš i po Dependency Injection.
Lepší je každému modelu předávat toho co nejméně. Jenom to co potřebuje.
Pokud toho model potřebuje opravdu hodně, tak je dobré mu ty služby předat v novém containeru. Jde vytvořit i v configu, ale nevypadá to pěkně. Takže třeba:
public static function createServiceUzivatele(Container $container)
{
$context = new Container();
$context->addService('authenticator', $container->authenticator);
$context->addService('authorizator', $container->authorizator);
// .. další služby nebo parametry
return new UzivateleModel($context);
}
Tím jednotlivé služby od sebe pěkně oddělíš.
Také je potřeba se zamyslet, pokud model potřebuje moc služeb. Znamená to, že toho dělá moc a měl by jsi ho rozdělit na více tříd/služeb. Přijatelné jsou cca 3 až 4 jiné služby. Cokoliv více už může (může !== musí) znamenat, že je špatně vymyšlený.
Editoval HosipLan (22. 7. 2011 12:36)
- Elijen
- Člen | 171
Patrik Votoček napsal(a):
Na to je Nette\DI\Container::TAG_TYPEHINT aneb „speciální“ tag.
Toto jsem ze zdrojáků Containeru vypozoroval, ale stále nemohu zjistit, jak tento tag nastavit pro staticky vytvořenou službu (metodou createServiceXYZ ve vlastním kontajneru dědícím z Nette\DI\Container).
- Patrik Votoček
- Člen | 2221
Odstranění DI\Container::getServiceNamesByTag()
je trvalé?
Nebo se objeví zpět? Mám poslat pull?
Btw: bude někde sepsáno pár slov k novinkám v kontejneru (autowire)?
- David Grudl
- Nette Core | 8218
Patrik Votoček napsal(a):
Odstranění
DI\Container::getServiceNamesByTag()
je trvalé? Nebo se objeví zpět? Mám poslat pull?
K čemu to používáš?
- Patrik Votoček
- Člen | 2221
Ke spoustě věcí.
V systému mám například něco čemu říkáme „globální komponenty“. Zaregistruju si komponentu do DIC s tagem „component“ a na základě toho si je presenter / control vytáhne.
Nebo na listenery k Doctrine. Továrnička na EntityManager řekne DIC že chce všechny služby s tagem „listener“ a nastaví je EventManageru.
Pak taky na console commandy. (Jednotlivé příkazy pro CLI).
Těch příkladů by se našlo více…
- Patrik Votoček
- Člen | 2221
paranoiq napsal(a):
tag není interface
tak nějak nevím co jsi tím chtěl říct…
- Filip Procházka
- Moderator | 4668
No, kdyby ty služby měly specifický interface, nemohl by to nějak řešit nový autowire? Nedaly by se nějak v „compile-time DIC“ vytáhnout všechny služby určitého typu? Commity jsem procházel, ale je tam moc generování phpka a celkově jsem z toho krapet zmatený :)
- Yrwein
- Člen | 45
Parafráze: Interface není tag. :)
Tzn. interface by neměl sloužit k označování – tagování může být „dynamické“ (pro každou aplikaci třebas budou jiné komponenty ozančené jako „globální“ nebo budou komponenty označeny jinak), kdežto interface je u třídy „natvrdo“. (Edit: Čímž neříkám, že někdy se nemůže vybírání jen dle interface hodit.)
Editoval Yrwein (25. 10. 2011 17:04)
- Patrik Votoček
- Člen | 2221
Yrwein napsal(a):
… tagování může být „dynamické“ …
to je jasné od toho máme Convention over configuration :-)
A ještě jedna poznámka. Problém je pokud mám v DIC uloženo více komponent (všechny implementují Nette\ComponentModel\IComponent), ale pouze 5 z nich josou ony „globální“ dalších 9, které tam jsou, už „globální“ nejsou. Myslím si, že to je ideální ukázka využití tagů.
- David Grudl
- Nette Core | 8218
Tagování a (do jisté míry) hledání podle třídy/interface bude zachováno, jen neexistoval způsob, jak to řešit pro statické kontejnery a kvůli tomu musím pozměnit API.
Hledání podle třídy bude plnohodnotné u všech kontejnerů strojově vygenerovaných (např. z config.neon), u ostatních buď omezeně, nebo vůbec. Důvod je ten, že zjistit u dosud neinstancované/nenačtené třídy její předky a rozhraní znamená nainklůdovat všechny příslušné soubory, což představuje výkonnostní brzdu. K nějakému kešování bych přistoupil až ve chvíli, kdy se generování kontejnerů neosvědčí.
K dokončení upravené implementace DI nechybí moc, tak vydržte. Výsledek bude výrazně pohodlnější a srozumitelnější i pro začátečníky.
- Patrik Votoček
- Člen | 2221
Já jsem to u statických kontejnerů řešil zavedením anotace
@tags
.
Tfuj se mě ulevilo že to bude zachováno… :-)
- Džízis Crust
- Člen | 10
Zdravím :)
Tak jsem si to tu celé pročetl, pomačkal odkazy a myslím, že mi to pomohlo o dost vyrůst co se DI týče :) Další věc co mě potěšila je, že mám čekat na pohodlnější a srozumitelnější výsledek. Tak bych se nedočkavě zeptal, kdy to cca přijde? Nebo už jsem to prospal? :Díky