Nefunkční dependency injection

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

Zdravím, bohužel zápasím s dependency injection Mám třídu Identity, která dědí od Nette\Security\Identity a chci, aby měla připojení k databázi.

<?php
class Identity extends Security\Identity {

    private $username;
    private $email;
    private $passwordHash;
    /** @inject @var Nette\Database\Context */
    public $databaseHandler;
    private $databaseRow;

    private function init(){
        \Tracy\Debugger::fireLog($this->databaseHandler);
        $this->databaseRow = $this->databaseHandler->table(Model\UserHandler::TABLE_NAME)- >where(Model\UserHandler::COLUMN_NAME, $username)->fetch();
        \Tracy\Debugger::fireLog($this->databaseRow);
    }
    //zbytek kódu
?>

A v config.neon:

services
App\Model\UserHandler
App\Model\MailHandler
App\RouterFactory

router: @App\RouterFactory::createRouter
identity:
class: App\Model\PDO\Identity
inject: yes

a nefunguje to. Když se zavolá init, tak proměnná $databaseHandler není inicializovaná.

Tak jsem zkusil

<?php
    private $username;
    private $email;
    private $passwordHash;
    public $databaseHandler;
    private $databaseRow;

    public function injectContext(Nette\Database\Context $databaseHandler)
    {
        $this->databaseHandler = $databaseHandler;
    }
?>

a stále nic.

Zkusil jsem i předání konstruktorem, ale tam jsem si nedokázal poradit s tím, jak to je, když některé proměnné jsou autowirované a některé ne.

Ocením každou radu, děkuji.

Editoval Azathoth (28. 7. 2014 22:29)

David Kudera
Člen | 455
+
0
-

inject anotace je myšlená pouze pro presentery, v poslední době se to tu několikrát řešilo, tak kdyžtak mrkni ;-)

A pošli kdyžtak ten kód, který jsi použil pro předání přes konstruktor

Edit: jo a kdyžtak identity objekt se standardně vytváří ručně, takže tam se to samo nebude autowirovat. Takže i databáze se musí předat ručně v authenticate metodě

Editoval David Kudera (28. 7. 2014 22:04)

Azathoth
Člen | 495
+
0
-

Tak jsem ještě zkusil nastavení setterem:

zde je config.neon:

services
App\Model\UserHandler
App\Model\MailHandler

#- App\Model\PDO\Identity(%id%, %roles%, %email%, %passwordHash%)
 – App\RouterFactory
router: @App\RouterFactory::createRouter
identity:
class: App\Model\PDO\Identity
#inject: yes
setup:
 – setContext

a část třídy Identity

<?php
    private $username;
    private $email;
    private $passwordHash;
    public $databaseHandler;
    private $databaseRow;

    public function setContext(Nette\Database\Context $databaseHandler)
    {
        $this->databaseHandler = $databaseHandler;
    }
?>

přes kontruktor to vypadalo následovně:

<?php
function __construct(Nette\Database\Context $databaseHandler, $id = NULL, $roles = NULL, $email = NULL, $passwordHash = NULL) {
        parent::__construct($id, $roles);
        $this->databaseHandler = $databaseHandler;
        $this->username = $id;
        $this->email = $email;
        $this->passwordHash = $passwordHash;
        $this->databaseRow = $this->database->table(Model\UserHandler::TABLE_NAME)->where(Model\UserHandler::COLUMN_NAME, $username)->fetch();
    }
?>

a v config.neon bylo:

services
App\Model\UserHandler
App\Model\MailHandler
App\Model\PDO\Identity(%id%, %roles%, %email%, %passwordHash%)
App\RouterFactory

router: @App\RouterFactory::createRouter

Editoval Azathoth (28. 7. 2014 22:06)

David Kudera
Člen | 455
+
0
-

Správněji je konstruktor. Jinak předáváš tu službu databáze? Jestli vycházíš z authenticate metody ze sandboxu, tak to musíš udělat sám. Nechat si databázi injektnout do authenticátoru a v něm to předat při instancování třídy Identity

Azathoth
Člen | 495
+
0
-

Vycházím přesně ze sandboxu. Nejspíš to udělám tak, jak píšeš, že to předám při instancování, ale nepřijde mi to jako nejčistší řešení.
Jsem poněkud zmaten, protože injectnutí do autenticátoru mi funguje bez problémů, ale injectnutí sem vůbec nefunguje.
Ale není nějaký elegantní způsob, jak to udělat tak, abych tam tu databázi nemusel předávat, ale aby se to tam nastavilo samo? Myslel jsem si, že DI je právě o tom, že se nebudu muset starat o předávání objektu z jedné třídy do druhé, že to udělá DI samo, když se to správně nastaví.

David Kudera
Člen | 455
+
+1
-

Ano di je přesně o tom, ale funguje to u služeb. Nemůže to fungovat nijak kouzelně napříč celým php i u tříd, které si člověk vytváří sám přes new. Když koukneš do temp souborů configurátoru, tak uvidíš, že nette vlastně dělá to, že ty služby si takhle ručně předá samo za tebe.

Takže možnost je např. zaregistrovat si identity jako generovanou továrničku, jejíž továrnička bude v create metodě očekávat $id, $roles, $email a $passwordHash a zbytek (databáze) se předá pomocí autowiringu

services:

	-
		parameters: [id = null, roles = null, email = null, passwordHash = null]
		class: App\MyIdentity(%id%, %roles%, %email%, %passwordHash%)
		implements: App\IMyIdentityFactory

factory:

namespace App;

interface IMyIdentifyFactory
{


	/**
	 * @param int $id
	 * @param array $roles
	 * @param string $email
	 * @param string $passwordHash
	 * @return \App\MyIdentity
	 */
	public function create($id = null, $roles = null, $email = null, $passwordHash = null);

}

identity:

namespace App;

class MyIdentity
{

	public function __construct($id = null, $roles = null, $email = null, $passwordHash = null, Context $database)
	{
		// do something
	}

}

a použití

class Authenticator implements IAuthenticator
{

	private $identityFactory;

	public function __construct(IMyIdentityFactory $identityFactory)
	{
		$this->identityFactory = $identityFactory;
	}

	public function authenticate(array $credentials)
	{
		// ...

		return $this->identityFactory->create($id, $roles, $email, $passwordHash);
	}

}

nechce se mi dopisovat anotace, v PhpStormu se to píše mnohem líp než takhle z hlavy ;-)

Editoval David Kudera (28. 7. 2014 22:46)

Azathoth
Člen | 495
+
0
-

Děkuji moc, už to chápu a rozhodně vyzkouším.

Tak mám další problém: jak to udělat, když mi to hlásí: You cannot serialize or unserialize PDO instances.

Je nějaká rada, jak si poradit s injektovanými objekty ve třídě, která se posílá do session a jak se to dělá? Připojení k databázi se asi jen tak serializovat nedá.

Editoval Azathoth (28. 7. 2014 22:59)

David Kudera
Člen | 455
+
0
-

jo vlastně.. tohle mi nedošlo no.. taková snaha a nakonec aby to padlo na tomhle.. no.. nestačilo by nakonec kdyby tu databázi měla třída user a identity zůstalo čisté?

David Matějka
Moderator | 6445
+
0
-

Muzes se inspirovat tady

Azathoth
Člen | 495
+
0
-

Právě mne to napadlo. Jdu to předělat a databázi nacpat do usera.

Každopádně děkuji za rady a mám jednu poslední otázku: proč u UserManagera v sandboxu funguje DI automaticky, když je zaregistrovaný jako služba a u toho mého Identity ne? Nejde to Identity zaregistrovat jako službu tak, aby to fungovalo tak čistě jako u UserManagera v sandboxu? Nebo jak poznám, kdy to jde udělat pouze pomocí konstruktoru a zapsání do config.neon a kdy si musím psát továrnu sám?

David Matějka
Moderator | 6445
+
0
-

@Azathoth o spravu identity se stara trida implementujici Nette\Security\IUserStorage. defaultni implementaci je Nette\Http\UserStorage a to identitu serializuje a uklada do session.

David Kudera
Člen | 455
+
+1
-

To je docela snadný.

Pokud chceš, aby fungovalo DI s autowire, je nutný tu třídu do DI zaregistrovat, o zbytek se postará nette.

Na tobě je ale rozhodnutí, jestli přes továrničku nebo jako klasickou službu.

Továrničky hlavně využiješ, když budeš chtít mít klasický komponenty v DI, pro ně je to přímo ideální. No a pro všechny ostatní případy, kdy se nějaká třída vytváří znovu podle specifických argumentů.. Určitě existuje lepší vysvětlení…

Takže např. komponenta pro editaci uživatele očekává uživatele, kterého bude editovat = vytvořím komponentu speciálně pro daného uživatele
Nebo tady s identity. Vytvářím ji vždy novou pro každého uživatele zvlášť podle id, roles, email a passwordHash.

Ale např. třídu UserManager chci mít jako obyčejnou službu, nechci pokaždé vytvářet novou instanci. Tak jako určitě nechci vždy vytvářet novou instanci připojení k db apod.

Takže přímo k otázce. Pro funkčnost DI musíš registrovat úplně vždy v config.neon a továrnu psát jen v případech, kdy se to hodí

Azathoth
Člen | 495
+
0
-

matej21: takže navrhuješ, aby si připojení k databázi uchovával spíše nějaký můj potomek UserStorage než Identity?

Editoval Azathoth (29. 7. 2014 0:14)

David Matějka
Moderator | 6445
+
+1
-

@Azathoth ano, viz odkaz, co jsem posilal. v getIdentity metode si identitu pak naloadujes jak chces. Muzes taky kouknout, jak to majkl hackuje tady

Azathoth
Člen | 495
+
0
-

David: Aha. Takže když mám nějakou třídu která se vytvoří pouze jednou a stará se o něco, tak je lepší to mít jako službu (např. usermanager) a když mám nějakou datovou třídu, která se vytváří pokaždé s jinými parametry, tak je lepší vytvořit si vlastní továrnu? Už to začínám chápat, děkuji moc.