Automatické vložení závislosti, návrh aplikace
- batko
- Člen | 219
Ahoj,
Dotaz 1.
mám následující třídu. Nejde o žádnou konkrténí je to jen příklad.
Zajímá mě, zda jde nějak udělt to, že pokud vytvořím objekt new Basket
tak abych nemusel ručně předávat jako argumenty objekt databáze a objekt
uživatele.
Již se mi to snažil dříve někdo vysvětlit a i jsem to tu hledal a pořád nic.
<?php
class Basket extends Nette\Object {
/** @var Nette\Database\Connection */
public $database;
/** @var Nette\Security\User */
public $user;
function __construct(Nette\Security\User $user, Nette\Database\Connection $database) {
}
?>
Dotaz 2.
Pravděpodobně to souvisí s dotazem 1.
Začal jsem psát aplikaci dle PÍŠEME PRVNÍ APLIKACI a vše šlo hladce. Presenery, komponenty, repository atd.
Pak jsem si napsal první vlastní třídu a nějak mi to přestalo zapadat. Pokud jsem vytvořil objekt v presenteru, tak vše bylo v pořádku. Pokud v komponenetě, tak jsem již musel předat databázi komponentě a pak do objektu.
Naopak, někde se používají repository jako v PÍŠEME PRVNÍ APLIKACI, ale k nim se z komponenty a vlatní třídy nedostanu.
Mohl by mi toto někdo stručně popsat?
Děkuji MOC
- David Matějka
- Moderator | 6445
psal jsem ti to tady, https://forum.nette.org/…eni-databaze#… kdyztak napis co presne nechapes.
princip je jednoduchej: Basket registrujes jako sluzbu (sekce services v neonu) a zavislost na Basket si das do presenteru pres inject* metodu. pro komponenty je to slozitejsi a postup je v mem prispevku, na ktery vyse odkazuji
- batko
- Člen | 219
OK nyní mám
services:
basket: Basket
a třídu Basket
<?php
class Basket extends Nette\Object {
/** @var Nette\Database\Connection */
public $database;
/** @var Nette\Security\User */
public $user;
function __construct(Nette\Security\User $user, Nette\Database\Connection $database) {
?>
a pokud vytvořím objekt tak tam stejn musím jako parametry předat user a db nebo se pletu?
<?php
$x = new \Basket($this->user, $this->database);
?>
- Michal Vyšinský
- Člen | 608
Nastavuješ ty properties v tom konstruktoru? Mělo by to být takto:
function __construct(Nette\Security\User $user, Nette\Database\Connection $database) {
$this->user = $user;
$this->database = $database;
}
DI kontejner ti „jen“ předá ty služby do konstruktoru, nastavit do těch properties si to musíš v konstruktoru už sám.
- Etch
- Člen | 403
@batko:
Do konfigu si dej:
services:
basket:
class: \TestBasket
Někam do libs
si ulož následující třídu:
<?php
class TestBasket extends Nette\Object{
private $user;
private $connection;
public function __construct(Nette\Security\User $user, Nette\Database\Connection $connection) {
$this->user = $user;
$this->connection = $connection;
}
public function test(){
echo 'Basket ok!! <br>';
}
public function testConnection(){
if($this->connection instanceof Nette\Database\Connection){
echo 'Database ok!! <br>';
}else{
echo 'Database error!! <br>';
}
}
public function testUser(){
if($this->user instanceof Nette\Security\User){
echo 'User ok!! <br>';
}else{
echo 'User error!! <br>';
}
}
}
?>
a do presenteru si dej následující:
public function renderDefault(){
$basket = $this->context->basket;
$basket->test();
$basket->testConnection();
$basket->testUser();
exit;
}
- ViPEr*CZ*
- Člen | 817
batko napsal(a):
a pokud vytvořím objekt tak tam stejn musím jako parametry předat user a db nebo se pletu?<?php $x = new \Basket($this->user, $this->database); ?>
Obecně ano, jak by se tam taky jinak ty závyslosti dostali, než že je tam
vy sám předáte.
V Nette ovšem využijte config a založte si službu (jak tu už je ostatně
napsáno) a Nette se postará samo o předání závyslostí přes tzv.
autowiring: https://doc.nette.org/cs/configuring#…
V Presenteru si pak službu injectujte: https://pla.nette.org/…ect-autowire
- batko
- Člen | 219
@Etch: díky moc, tohle funguje a už to asi konečně chápu.
Ale ješte mi neni jasná jedna věc a ta je podle mě dost zásadní.
Mám presenter ve kterém využiju uvedenou třídu Basket. Vše ok. Ale v presenteru vytvořím komponentu například ShowBasket, která mě bude zobrazovat obsah košíku. Jak přistupovat k této třídě v komponentě, abych nemusel do konstruktoru vkládat služby a abych mohl jednoduše přistupovat k repository které mam napsané.
<?php
public function createComponentShowBasket() {
return new \Todo\ShowBasket();
}
?>
@Matej21 se mi to tu snazl vysvetlit https://forum.nette.org/…eni-databaze#… ale bohužel jsem to nepochopil…
Děkuji
- Etch
- Člen | 403
batko napsal(a):
<?php public function createComponentShowBasket() { return new \Todo\ShowBasket(); } ?>
Buď to nechápu a nebo mi to přijde jako naprosto stejný problém, který si měl s třídou „Basket“. Componentu si nastav v configu a pak jí tahej z conteineru a o závislosti se postará auto-wiring.
- Šaman
- Člen | 2666
@Etch:
Jak myslíš vytvořit v configu a pak ji tahat? Já to tak sice dělám, ale
je to nedoporučovaný způsob, protože v presenteru přistupuješ přímo ke
kontejneru. Já si tahám komponenty pomocí supertovárničky
createComponent($name)
, takže mi to hodně zjednodušuje
přidávání komponent a to jedno volání kontextu holt musím zkousnout.
@batko:
Když se ti třída (ať už jako služba, nebo továrnou) vytváří
v kontejneru, tak funguje automatické předávání závislostí (auro
wiring). Pokud vytváříš komponentu v presenteru, tak musíš všechny
závislosti předat ručně. A do toho presenteru je dostaneš pomocí
inject*()
metod. Takže když si v presenteru budeš chtít
vytvořit komponentu, která potřebuje databázi, tak si tu databázi nejprve
necháš injectnout do presenteru (automaticky) touto metodou:
<?php
/** @var Nette\Database\Connection $database */
protected $db;
/**
* Injector databáze.
* Vpodstatě setter, který se ale nastaví automaticky při vytvoření presenteru.
*/
public function injectDb(Nette\Database\Connection $database $db)
{
$this->db = $db
}
?>
Usera máš v presenteru automaticky vždy. Tvoje továrnička pak bude vypadat takto:
<?php
public function createComponentShowBasket()
{
return new \Todo\ShowBasket($this->user, $this->db);
}
?>
- David Matějka
- Moderator | 6445
zavislosti pri tvorbe komponenty predavat rucne nemusis, k tomu ucelu slouzi
tovarnicka. nejdriv ukazu zpusob bez nette magie (bez dic tovarnicek):
mas tu tridu ShowBasket, ktera vyzaduje uzivatele a databazi. bez tovarnicky by
sis opravdu musel do presenteru injectnout User a Connection a tyto sluzby
predat pri vytvareni ShowBasket v createComponent medode (jak to pise
@Šaman)
situaci si muzem vylepsit, ze si vytvorim tridu ShowBasketFactory se zhruba nasledujicim kodem:
class ShowBasketFactory
{
/** @var Nette\Security\User */
protected $user;
/** @var Nette\Database\Connection */
protected $connection;
public function __construct(Nette\Security\User $user, Nette\Database\Connection $connection)
{
$this->user = $user;
$this->connection = $connection;
}
public function create()
{
return new ShowBasket($this->user, $this->connection);
}
}
v neonu tohle registruju jako beznou sluzbu
services:
showBasketFactory: ShowBasketFactory
a nette uz se postara o zavislosti na Connection a User
v presenteru se potom uz nemusis starat o to, co trida ShowBasket pozaduje za zavislosti. tebe jen zajima, ze potrebujes instanci tridy a vis, ze k tomu mas tovarnicku. tak si tovarnicku injectnes do presenteru
class FooPresenter extends BasePresenter
{
protected $showBasketFactory;
public function injectShowBasketFactory(ShowBasketFactory $showBasketFactory)
{
$this->showBasketFactory = $showBasketFactory;
}
public function createComponentShowBasket()
{
return $this->showBasketFactory->create();
}
}
nette vidi metodu inject* a podle typehintu zjisti, ze pozadujes sluzbu
ShowBasketFactory, proto ji injectne do presenteru (pri vytvareni teto sluzby
mimochodem zjisti, ze ta factory pozaduje User a Connection, proto tam tyto
sluzby pri vytvareni vlozi)
v createComponent metode akorat zavolas metodu create() na tovarnicce, ktera ti
vrati tu spravnou instanci
EDIT:
jeste k DIC tovarnickam. to je to, co jsem psal v tom mem odkazovanem
prispevku. DIC tovarnicky tento proces zjednodusuji, aby jsi nemusel neustale
psat tovarnicky (ShowBasketFactory) cele rucne. pri pouziti DIC tovarnicek ti
staci napsat rozhrani:
interface ShowBasketFactory
{
/**
* @return ShowBasket
*/
public function create();
}
a toto rozhrani neregistrovat nyni jako sluzbu, ale do sekce factories takto:
factories:
showBasketFactory:
implement: ShowBasketFactory
nette se nyni postara o vytvoreni tridy, ktera implementuje toto rozhrani (trida je v podstate podobna tride ShowBasketFactory, kterou jsem vytvoril vyse). diky tomu, ze je implementovano to rozhrani, muzes si pote v presenteru pozadat o zavislost uplne stejne, jako v pripade vlastni tridy
EDIT2:
a v cem je toto delsi reseni lepsi nez to, co pise
@Šaman? vem si, ze bys tuto komponentu chtel vytvaret
treba ve dvou presenterech. nyni by jsi musel injectovat vsechny zavislosti one
komponenty do vsech presenteru. a kdyby doslo ke zmene zavislosti teto
komponenty, musel by jsi to resit ve vsech presenterech, kde komponentu
vytvaris. timhle zpusobem, jak pisu, akorat upravis tridu ShowBasketFactory,
v pripade, ze pouzijes DIC factory a to rozhrani, nebudes muset upravovat nic.
proste pridas zavislost do konstruktoru ShowBasket a nette se postara
o zbytek.
EDIT3:
o DIC tovarnickach najdes jeste dalsi info zde https://doc.nette.org/…tion/factory
Editoval matej21 (9. 6. 2013 19:03)
- batko
- Člen | 219
Děkuji,
udělal jsem jak píšeš, ale hází to chybu
Nette\DI\ServiceCreationException
Service ‚factories‘: Unknown key ‚showBasketFactory‘ in definition of service.
jeste to x krat projdu, radeji
EDIT tohle jsem vyřešil, měl jsem posunutý text v configu
Editoval batko (9. 6. 2013 18:34)
- David Matějka
- Moderator | 6445
asi mas spatne odsazeni a factories mas jako sluzbu pod services, treba takhle ma vypadat config:
services:
sluzba1: Foo
sluzba2:
class: Bar
factories:
tovarnicka1:
implement: FooBar
EDIT: @batko ok, byl jsi rychlejsi :)
Editoval matej21 (9. 6. 2013 18:35)
- David Matějka
- Moderator | 6445
@Šaman: j melo by to tak byt, ale myslim, ze jeste to nejde, viz: https://github.com/…Compiler.php#L258 – klic implement je jen pokud je definice non-shared a podle tohohle https://github.com/…Compiler.php#L184 je shared v pripade, ze se definice nachazi v configu pod sekci services
- batko
- Člen | 219
services:
showBasketFactory: ShowBasketFactory
factories:
showBasketFactory:
implement: ShowBasketFactory
<?php
interface ShowBasketFactory
{
/**
* @return ShowBasket
*/
public function create();
}
class TestPresenter extends \BasePresenter {
protected $showBasketFactory;
public function injectShowBasketFactory(ShowBasketFactory $showBasketFactory) {
$this->showBasketFactory = $showBasketFactory;
}
public function createComponentShowBasket() {
return $this->showBasketFactory->create();
}
}
?>
ja uz fakt nevim…
It is not allowed to use services and factories with the same name: ‚showBasketFactory‘.
Editoval batko (9. 6. 2013 18:51)
- David Matějka
- Moderator | 6445
kdyz pouzijes ty DIC tovarnicky, tak pouzi jen tu definici v sekci factories, tu definici v sekci services tedy smaz
- batko
- Člen | 219
<?php
interface ShowBasketFactory {
/**
* @return ShowBasket
*/
public function create();
}
/**
* Description of TestPresenter
*
* @author Batko
*/
class TestPresenter extends \BasePresenter {
protected $showBasketFactory;
public function injectShowBasketFactory(ShowBasketFactory $showBasketFactory) {
$this->showBasketFactory = $showBasketFactory;
}
public function createComponentShowBasket() {
return $this->showBasketFactory->create();
}
}
class ShowBasket extends Nette\Application\UI\Control {
public function __construct() {
parent::__construct(); // vždy je potřeba volat rodičovský konstruktor
}
public function render() {
$this->template->setFile(__DIR__ . '/ProductList.latte');
$this->template->tasks = $this->selected;
$this->template->render();
}
}
?>
Chyba:
Service ‚showBasketFactory‘: Unknown key ‚implement‘ in definition of
service.
- batko
- Člen | 219
Udělal jsem i příklad https://doc.nette.org/…tion/factory
a hází to chybu
Service ‚mailingMessage‘: Unknown key ‚implement‘ in definition of service
- David Matějka
- Moderator | 6445
pouzivas vyvojovou verzi nette, nebo stabilni? na DIC factories je vyzadovana dev verze
- David Matějka
- Moderator | 6445
jo tahle zmena se mi taky moc nelibi, mas 2 moznosti:
- rychla moznost – stahni (checkoutni pokud pouzivas git) verzi pred timto commitem (mel by fungovat commit 32471c9306) – nebo pouzij ke stazeni balinku nette tento link
- sprava moznost – opravit si kde pouzivas Connection. pokud pouzivas vsechno spravne a mas repositar treba dle quickstartu https://doc.nette.org/cs/quickstart tak by melo stacit vymenit v konstruktoru zavislost Nette\Database\Connection za Nette\Database\SelectionFactory (a prejmenovat property connection na selectionFactory)
Editoval matej21 (9. 6. 2013 20:24)
- batko
- Člen | 219
tohle jsem vyresil hravě, ale…
Nette\DI\ServiceCreationException
No service of type FrontModule\ShowBasketFactory found. Make sure the type hint in FrontModule\TestPresenter::injectShowBasketFactory() is written correctly and service of this type is registered.
ja proste uz nevim kde muze byt chyba
- David Matějka
- Moderator | 6445
ted bude chyba v namespace
pokud nemas ShowBasketFactory v namespace, tak dej do injectu \ShowBasketFactory misto ShowBasketFactory. jinak to bude tu tridu hledat v soucasnem namespace (FrontModule) nebo muzes ShowBasketFactory dat do namespace spolecneho s presenterem (pak nezapomen toto zmenit v configu)
- batko
- Člen | 219
Uz je tu prvni dotaz :-)
V komponentě níže, bych rád vytvořil objekt vlastní třídy. A jako další bych rád přistupoval do repository.
Třidu i repository mám nastevené jako sevices.
<?php
class ShowBasket extends Nette\Application\UI\Control {
public function __construct() {
parent::__construct(); // vždy je potřeba volat rodičovský konstruktor
}
public function render() {
$this->template->setFile(__DIR__ . '/ShowBasket.latte');
//zda bych chtěl vytvořit objekt vlastní třídy
$this->template->render();
}
}
?>
Editoval batko (9. 6. 2013 20:58)
- jiri.pudil
- Nette Blogger | 1032
Jednoduše si o ty služby řekni v konstruktoru komponenty, Nette je automaticky předá. Příklad:
class ShowBasket extends Nette\Application\UI\Control
{
protected $repository;
public function __construct(FooRepository $repository) {
parent::__construct();
$this->repository = $repository;
}
}
- Etch
- Člen | 403
Šaman:
Ano vím, že je to nedoporučované, v podstatě jsem myslel přesně nějakou „supertovárničku“.
Já jsem se tam hlavně poněkud neobratně vyjádřil. Já ten výraz
„tahat“ používám jak pro $this->context->...
tak pro
inject*
atd. V podstatě jediné, co pro mě není „tahat“ je
explicitní volání new ...
, takže chápu, že použití tohoto
výrazu může být z mé strany občas trochu zavádějící. Pokusím se
příště radši více rozepsat. :D
Já jsem v tom předchozím postu chtěl pouze naznačit, že auto-wiring
prostě nikdy nemůže fungovat při explicitním volání
new \Todo\ShowBasket();
. To jestli už pak použije DIC továrnu,
$this->context->…, inject* už jsem neřešil. ;)