Automatické vložení závislosti, návrh aplikace

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

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

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

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);
?>
Etch
Člen | 403
+
0
-

batko napsal(a):

<?php
$x = new \Basket($this->user, $this->database);
?>

Musíš to tahat z contextu a nebo si to tam vstříknout přes inject

$x = $this->context->basket

při přímém volání $x = new \Basket budeš muset vždy závislosti předat ručně.

Editoval Etch (9. 6. 2013 11:03)

batko
Člen | 219
+
0
-

Aha :-) Takže pokud to vytáhnu z context tak se mi ty závislosti předají sami? Jdu to zkusit. Toto je dost zásadní informace.

batko
Člen | 219
+
0
-
<?php
 $x = $this->context->basket;
        echo $x->test();


?>

Tak tohle bohužel nefunguje. Voláno v presenteru.

Michal Vyšinský
Člen | 608
+
0
-

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

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

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

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

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

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

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

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

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)

Šaman
Člen | 2666
+
0
-

Ahá.. díky, konečně jsem pochopil DIC továrničky.
Jenom upozorňuji, že ve vývojové verzi 2.1 už je sekce factories nedoporučovaná a sjednocená s services.

David Matějka
Moderator | 6445
+
0
-

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

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

kdyz pouzijes ty DIC tovarnicky, tak pouzi jen tu definici v sekci factories, tu definici v sekci services tedy smaz

batko
Člen | 219
+
0
-
<?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
+
0
-

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

pouzivas vyvojovou verzi nette, nebo stabilni? na DIC factories je vyzadovana dev verze

batko
Člen | 219
+
0
-

jo tak tim to asi bylo :-) nasadil jsem to tam, ale hazi mi to dasi chybu ale to je změnou metod v Nette\Database\Connection::table() is deprecated; use SelectionFactory::table() instead.

David Matějka
Moderator | 6445
+
0
-

jo tahle zmena se mi taky moc nelibi, mas 2 moznosti:

  1. rychla moznost – stahni (checkoutni pokud pouzivas git) verzi pred timto commitem (mel by fungovat commit 32471c9306) – nebo pouzij ke stazeni balinku nette tento link
  2. 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
+
0
-

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

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

No tak to vypadá že to funguje. Chtěl bych ti moc poděkovat. není to se mnou lehký :-) a pradvěpodobně zítra přijde další dotaz :-) Ted to jdu otestovat jak se to chová a co to umí :-)

batko
Člen | 219
+
0
-

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

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;
    }

}
batko
Člen | 219
+
0
-

:-) ja uz jdu radji spat, diky moc vsem, jste borci :-)

Etch
Člen | 403
+
0
-

Š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. ;)