Vytvaranie instancii – najlepsi sposob?

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

Dobry den,
majme priklad: model „User“, ktory je zaregistrovany ako service. Ako service je z dovodu, ze sa do neho musi injectovat iny service (napr. databaza)

class BaseModel extends \Nette\Object{
	public function __construct(Container $c, Database $db){...}
}

class UserModel extends BaseModel{ ... }

a potrebujem vyriesit nasledujucu situaciu … UserModel sa musi pouzivat na udrziavanie stavovych informacii aktualne prihlaseneho pouzivatela – to je ok, cez AutoWiring a nie je problem k nemu pristupovat cez getService('user');

Nasledne potrebujem instanciovat tento model pre ineho usera (napr na vykonanie update). Nemozem pouzit $this->getService('user'), nakolko tam je instanciovany aktualny pouzivatel. A chcem sa vyhnut new User($this->container, $this-getService('database')).

Je dobrym riesenim pouzit $this->context->createInstance('App\Model\UserModel'); alebo radsej new User(...); ?

Dufam, ze sa neplanuje vyradit $this->context z Nette\Application\UI\Presenter, pripadne $context->createInstance, tak ako sa vyradilo FileTemplate (a musel som fixovat napr. Eciovni na faktury)

Ako to riesite vy?

Dakujem.

Editoval tomasz_svk (16. 5. 2015 21:39)

F.Vesely
Člen | 369
+
+1
-

Podivej se do dokumentace na Ziskavani zavislosti na injectovani sluzeb do presenteru. Rozhodne nepouzivej context, je to neciste, primo prasacke.

tomasz_svk
Člen | 5
+
-1
-

F.Vesely napsal(a):

Podivej se do dokumentace na Ziskavani zavislosti na injectovani sluzeb do presenteru. Rozhodne nepouzivej context, je to neciste, primo prasacke.

Ano, ale to nie je odpoved na moju otazku, pre mna je primarne instanciovanie tz:

user1 = $this->context->createInstance('App\Model\UserModel');
user2 = $this->context->createInstance('App\Model\UserModel');

vs.

user1 = new App\Model\UserModel(...);
user2 = new App\Model\UserModel(...);

pride mi to zbytocne injectovat, nakolko k pouzitiu user1 a user2 vobec nemusi prist.

F.Vesely
Člen | 369
+
0
-

A co presne ten tvuj model dela? UserModel by mel umet pracovat se vsemi uzivateli, ne jen s jednim. Pokud chces editovat uzivatele, tak by sis ho mel pres UserModel vytahnout z databaze, upravit hodnoty a pres UserModel zase ulozit zpet.

Azathoth
Člen | 495
+
0
-

Model\User je zaregistrovaný jako služba. Choví se jako singleton. A stačí jako singleton. Na co potřebuješ vytvářet novou instanci služby?

tomasz_svk
Člen | 5
+
0
-

UserModel je iba priklad, pretoze ma nenapadla jednoducha situacia, na ktorej to vysvetlit. Berte to tak, ze:

  • nema to nic spolocne s „userom“
  • robi to cokolvek, udrzuje napriklad stavove premenne

Ale teraz ma napadol mozno lepsi priklad:

Mail wrapper:
class MailWrapper extends \Nette\Mail\Message{ function __construct(Template $template, Mailer $mailer){...}; function send(); ... }


/** @var MailWrapper @inject */
public $mail;
...

foreach($users as $user){
	$this->mail->addTo($user->getMail());

	//Var. do tpl, vola sa n-krat s inym 'name', napriklad podla uctu
    $this->mail->setVariable('name',random());

    $this->mail->send();
}

Tu nastava ten problem, ze \Nette\Mail\Message::addTo appenduje prijemcov. Aby bolo mozne poslat dalsiemu userovi mail, je potreba prave povodneho prijemcu vymazat. Pocitajme, ze se setVariable sa vola v ramci jedneho mailu vela krat a vzdy sa nastavuje ina premenna (nemusi sa 2× ta ista, a v opakovanom cykle by bola neziaduca)

Riesenia su v podstate 4:

  • implementovat clean() a v kazdom cykle zavolat
  • instanciovat nanovo s parametrami
  • instanciovat servicu cez context
  • naklonovat prazdnu servicu na zaciatku cyklu a pracovat s tym a nasledne ju uvolnit (rad by som sa vyhol)

Editoval tomasz_svk (17. 5. 2015 2:28)

jiri.pudil
Nette Blogger | 1034
+
+7
-

Čisté řešení je v podstatě jen jedno: napsat si na to MailFactory, která ti vrátí vždy novou instanci, a injectovat si tu továrnu.

tomasz_svk
Člen | 5
+
-2
-

jiri.pudil napsal(a):

Čisté řešení je v podstatě jen jedno: napsat si na to MailFactory, která ti vrátí vždy novou instanci, a injectovat si tu továrnu.

Ano, nakoniec som to vyriesil bez nejakeho robustneho prepisovania kodu:

public function __construct(...){
	$this->constructorParams = func_get_args();
}

public function newInstance(){
	$rf = $this->getReflection();
	return $rf->newInstanceArgs($this->constructorParams);
}

Editoval tomasz_svk (17. 5. 2015 13:19)

jiri.pudil
Nette Blogger | 1034
+
+5
-

Ano, nakoniec som to vyriesil bez nejakeho robustneho prepisovania kodu:

Tak se nad nějakým robustním přepsáním kódu zamysli, protože tohle je prasárna, a navíc relativně pomalá (protože reflexe v runtime) :)

Azathoth
Člen | 495
+
0
-

Evidentně řešíš vytváření entit. Na entity vždycky továrnu, entitu nelze injectovat (je to pitomost) a normálně si v tovární třídě vytvoř normálním způsobem novou entitu. Prostě zavolej konstruktor.

newPOPE
Člen | 648
+
0
-

@tomasz_svk mam pocit, ze velmi spekulujes.

Tu je nieco co potrebujes factory method pattern. Nastuduj a pouzi :)

A s tym mailom by to mozno slo urobit nejak takto:

foreach($users as $user) {
  $mail = clone $this->mail;
  $mail->addTo($user->getMail());
  $mail->send(...);
}
h4kuna
Backer | 740
+
-1
-

newPOPE napsal(a):

foreach($users as $user) {
  $mail = clone $this->mail;
  $mail->addTo($user->getMail());
  $mail->send(...);
}

Typuji že toto si bude pamatovat předchozí odesílatele. Tj v prvním cyklu to pošleš na xx@yy.tld, v druhém xx@yy.tld, zz@yy.tld atd…

bazo
Člen | 620
+
0
-

h4kuna napsal(a):

newPOPE napsal(a):

> foreach($users as $user) {
>   $mail = clone $this->mail;
>   $mail->addTo($user->getMail());
>   $mail->send(...);
> }

Typuji že toto si bude pamatovat předchozí odesílatele. Tj v prvním cyklu to pošleš na xx@yy.tld, v druhém xx@yy.tld, zz@yy.tld atd…

ved preto tam ma ten clone…

Jan Suchánek
Člen | 404
+
+1
-

@bazo: Furt nechápu jakej je rozdíl místo clone mít radši factory, která ten email vyrobí, případně i dostane další závislosti? Proč zrovna clone, k čemu?

// inject služby, případně předání závislosti

foreach($users as $user) {
  $mail = $this->emailFactory->create();
  $email = $user->getEmail();
  $mail->addTo($email);
  $mail->send(...);
}

nebo jen ?

foreach($users as $user) {
  $email = $user->getEmail();
  $this->emailService->send($email);
}

Editoval jenicek (18. 5. 2015 22:06)

newPOPE
Člen | 648
+
+1
-

@jenicek okej, clone je tam preto aby sa neappendovali prijimatelia. A na druhej strane je tam preto lebo to uz je setupnuty mail (sablona, premenne a pod). A ked premyslam nad premennymi tak pokial je to personalizovana vec tak budes v tom maili potrebovat meno napr takze by som to napisal nejak takto:

foreach($users as $user) {
  $this->emailService->send($user); // ma entitu usera tu si tam spracuje a posle email.
}
Jan Suchánek
Člen | 404
+
0
-

@newPOPE: ideal, aspoň to je přehledný a dělá to jednu věc.

tomasz_svk
Člen | 5
+
0
-

@newPOPE clone nakoniec nebolo dobre riesenie, kvoli referenciam na objekty (shallow copy). Nastastie islo iba o 2 pripady, ktore sa pouzivaju iba obcas … takze reflection postacilo :)