[2011–05–05] Finalizace Dependency Injection

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
Elijen
Člen | 171
+
0
-

HosipLan napsal(a):

Neříkám, že ActiveRecord je anti-pattern sám o sobě, nechce se mi to zjišťovat, ale navzájem se vylučuje s Dependency Injection.

A tomu se také snažím oponovat. Nevidím totiž důvod, proč by ActiveRecord nemohl DI používat. V čem se tedy vylučuje s DI?

Ondřej Mirtes
Člen | 1536
+
0
-
Article::findAll();

Jak tam chceš něco vstříknout?

Filip Procházka
Moderator | 4668
+
0
-

Tak schválně, rád se poučím. Ukaž jak ActiveRecord využívá Dependency Injection.

Yrwein
Člen | 45
+
0
-

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)

bojovyletoun
Člen | 667
+
0
-

Article::$connection=…; :)

David Grudl
Nette Core | 8136
+
0
-

Chcete vidět Active Record s DI? http://goo.gl/yZDa9

Filip Procházka
Moderator | 4668
+
0
-

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…

JakubJarabica
Gold Partner | 184
+
0
-

No ty woe, Davidek ma novy avatar.

gofry
Člen | 6
+
0
-

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 | 2635
+
0
-

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
+
0
-

Zajímavý článek od Vaška Purcharta o Dependency Injection.

Editoval jenicek (18. 7. 2011 17:06)

gofry
Člen | 6
+
0
-

Š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
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

Zdravím,

Jestliže mám ve vlastním Containeru definované služby staticky, je možné nějak určit její typ? Zejména kvůli metodě Containeru getServiceByType. Bez typeHintu totiž metoda službu nalezne pouze v případě, že již byla vytvořena její instance.

Jan Endel
Člen | 1016
+
0
-

v Netbeans určitě v jiných IDE si nejsem jist funguje:

/**
 * @property-read MyService myService
 */
class MyContainer extends \Nette\DI\Container
{
	public function createServiceMyService()
	{
		return new MyService();
	}
}
Elijen
Člen | 171
+
0
-

pilec napsal(a):

v Netbeans určitě v jiných IDE si nejsem jist funguje:

Měl jsem na mysli typeHint pro Nette Container nikoliv pro IDE.

Patrik Votoček
Člen | 2221
+
0
-

Na to je Nette\DI\Container::TAG_TYPEHINT aneb „speciální“ tag.

Elijen
Člen | 171
+
0
-

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
+
0
-

Tahle funkcionalita bohužel ve standardním Nette není

Patrik Votoček
Člen | 2221
+
0
-

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 | 8136
+
0
-

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
+
0
-

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…

paranoiq
Člen | 392
+
0
-

tag není interface

Patrik Votoček
Člen | 2221
+
0
-

paranoiq napsal(a):

tag není interface

tak nějak nevím co jsi tím chtěl říct…

Filip Procházka
Moderator | 4668
+
0
-

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
+
0
-

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
+
0
-

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 | 8136
+
0
-

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
+
0
-

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
+
0
-

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