Nefunkční dependency injection
- Azathoth
- Člen | 495
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
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
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
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
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
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
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
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é?
- Azathoth
- Člen | 495
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
@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
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
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.