Geneované továrničky, proč?
- kleinpetr
- Člen | 480
Ahoj,
už nějaký pátek píšu v nette, a většina věcí už je mi jasných, jediná věc která mi vůbec není jasná jsou továrničky na komponenty nebo celkově interfaces..
Zrovna píšu komponentu a přemýšlím nad předáním závislostí. Už asi 20× jsem četl různé best practises atp. Ale furt nechápu jako k čemu nebo v čem je to dorbý..
Dejme tomu že mám komponentu a potřebuju jí předat db connect tak jednoduše
class SomeControl extends UI\Control
{
private $database;
public function __construct(Nette\Database\Connection $database)
{
parent::__construct();
$this->database = $database;
}
}
A teď dejme tomu ikdyž se to asi nedělá si tu komponentu přidám jako službu do configu a injectnu v presenteru. To je celý. K čemu mi teda jsou nějaký továrničky ?
Díky za vysvětlení.
Editoval kleinpetr (24. 4. 2015 13:01)
- David Matějka
- Moderator | 6445
kratce: komponenty nejsou sluzby
prakticky:
- pomoci tovarny snadno predas parametr do konstruktoru
- komponenta muze byt pripojena pouze jednou, to znamena, ze ji nemuzes pouzit 2× behem jednoho pozadavku – a to i kdyby treba doslo k internimu forwardu (treba na 404, kde tu komponentu chces take pouzit). Tim, ze pouzijes tovarnu, se vzdy vytvori nova instance komponenty.
- akadlec
- Člen | 1326
Aby sis tu komponentu mohl vygenerovat tolikrát kolikrát potřebuješ. Např. komponenta co ti dělá přihlašovací form. Chceš mít na stránce dva, jeden do modálního okna na otevření tlačítkem a druhou přímo ve stránce. Takže si pomoci továrničky udělat tu komponentu 2× ale pokaždé s jinými parametry. Nemusíš pak psát new JmenoComponenty(zavislosti,…) ale jen $factory->create()
- David Matějka
- Moderator | 6445
Ja ten iface pisu vzdy do samostatneho souboru, ale prakticky je to jedno, hlavne aby to nasel nejaky loader (a kdyz to bude v app, tak to robot loader najde). Ale prijde mi prehlednejsi to davat do samotneho souboru, uz jen dle letmeho pohledu do adresarovy struktury vidim, ze to ma napsanou tovarnu
- kleinpetr
- Člen | 480
Ok tak já si s tím jdu nějak pohrát :) mě to totiž zmátlo, protože celkově jsem ifaces nikdy nepsal a pořádně nevim k čemu jsou, jako četl jsem, že jen deklarujou public fce daný třídy a dá se podle něho zjistit instanceof() nějakýho objektu, ale jinak v nich nevidím nic tak zázračnýho, a tomhle případě použití je to takový matoucí ;) každopádně díky za pomoc :)
- David Matějka
- Moderator | 6445
interface obecne jsou takove predpisy, jak ta trida ma vypadat, respektive co ma umet. Takze kdyz nekde vyzadujes interface, je ti jedno, jak je ta trida implementovana – jestli treba zapisuje na disk nebo do databaze, tebe zajima pouze to, aby se chovala, jak rika ten interface.
V pripade generovanych tovarnicek vytvoris interface a nette dle toho vygeneruje implementaci. Jak takova implementace vypada, muzes najit ve vygenerovanem DI kontejneru na konci souboru. (temp/cache/Nette.Configurator/…php)
- kleinpetr
- Člen | 480
Jo takhle, takže se to spíše používá u větších projektů na kterých pracuje více lidí. Aby věděli jaký fce můžou používat atd.. A tedy když nad tím tak přemýšlím a vytvořím si nějakou třídu s interfacem.
interface SomeClassInterFace
{
public function hello();
}
class SomeClass implements SomeClassInterFace
{
public function hello($world){
echo 'Hello '.$world;
}
}
Takže v tomhle případě když smažu v tý tříde SomeClass fci hello() tak hodí chybu.
A druhá věc, já si tedy nyní mohu vytvořit objekt pouze z toho IF ?
$some = new SomeClassInterFace();
$some->hello('world');
??
- Michal Vyšinský
- Člen | 608
Možná by chtělo si něco přečíst o interfacech: https://php.net/…terfaces.php
- David Matějka
- Moderator | 6445
Prave ten objekt nevytvarej – ty nekde vyzadujes SomeClassInterFace (treba v konstruktoru, nebo jako parametr metody) a o jeho vytvoreni (tzn vyber, ktera implementace se pouzije, jak se nakonfiguruje atd.) se postara nekdo jiny.
Hezkym prikladem ze sveta nette je cache storage. Vsude, kde chces pracovat s cache, vyzadujes interface IStorage a je ti jedno, jestli dostanes FileStorage, RedisStorage nebo WhateverStorage. A o to predani spravneho storage se postara DI kontejner tim, ze konkretni storage zaregistrujes v config.neon
- Šaman
- Člen | 2640
Nebo Mailer. Takhle vypadá můj testovací mailer. Ale protože používám rozhraní, mohu si ho v configu vytvořit (v tomto případě jako službu) a každá komponenta, nebo presenter, který si po kontejneru vyžádá instanci IMailer, tak dostane můj BarDumpMailer. A na ostrén serveru se místo toho nakonfiguruje třeba Nettí SmtpMailer. Tolik k používání rozhraní. Teoreticky by stejnou službu udělala i nějaká BaseClass, od které by všichni dědili, ale tam pak vzniká často obtěžující závislost na té BaseClass. Např. v tomhle případě není potřeba.
Služby i komponnety se dnes v configu zapisují stejně a rozdíl je jen
v tom, jak si je vyžádáš. Přímý inject ti vrátí vždy stejnou instanci
služby. Třeba tem můj mailer bude jen jeden pro celou aplikaci.
Továrnička ti zajistí vytvoření nové instance. To se používá třeba pro
komponenty (tam si zpravidla každá instance komponenty žije vlastním
životem).
- kleinpetr
- Člen | 480
Tak už chápu podstatu těch továrniček :). a snažím se je tedy zprovoznit nějak takhle:
namespace App\Components;
new MyControl extends UI\Control
{
.
.
}
namespace App\Factories;
use App\Components;
interface IMyControlFactory
{
/** @return Components\MyControl */
function create(); //tady už phpStorm řve
}
Ta komponenta teda musí obsahovat tu fci create() ?
- David Matějka
- Moderator | 6445
Ne, ta komponenta nema implementovat ten interface a nema obsahovat metodu create. To jsou dve veci – komponenta a tovarna na ni (ktera je zde definovana pouze interfacem a implementaci vytvori nette)
(btw, zmenil jsem question na diskuzi, at je to prehlednejsi)
- kleinpetr
- Člen | 480
@akadlec ta abstrakce mi nedošla :) díky.
@DavidMatějka ok tak už se asi pomalu blížím do cíle ještě se mu
sice něco nelíbí
Class App\Factories\IMyControlFactory used in service 'myControl' not found or is not instantiable
,
ale to už bude asi někde moje chyba :) Ještě mě tak napadá, že mě
stačí když si tu továrnu injectnu na basePresenteru a můžu si z ní
kdykoliv vytvořit komponentu kdekoliv v aplikaci :) což je super!
Editoval kleinpetr (24. 4. 2015 15:03)
- kleinpetr
- Člen | 480
normálně v services
#Factories
myControl: App\Factories\IMyControlFactory
když to udělám jen přes pomlčku
- App\Factories\IMyControlFactory
tak vyhodí
Class App\Factories\IMyControlFactory used in service '26_App_Factories_IMyControlFactory' not found or is not instantiable
Editoval kleinpetr (24. 4. 2015 15:32)
- romiix.org
- Člen | 343
To vyzerá na zlý namespace. Je 100% dobrý? Prípadne ti RobotLoader nenájde súbor. Premazal si cache?
- Michal Vyšinský
- Člen | 608
Ne ten namespace není správně. Podle toho cos sem dal (https://forum.nette.org/…arnicky-proc#…)
to má být App\Components\IMyControlFactory
EDIT: sorry blbě jsem se podíval – pravděpodobně je potřeba smazat cache, nebo jestli nepoužíváš RobotLoader ale třeba composer tak dumnout autoload
Editoval Michal Vyšinský (24. 4. 2015 15:44)
- David Matějka
- Moderator | 6445
@MichalVyšinský u ty factory ma uveden
namespace App\Factories
@kleinpetr jeste jsem se ptal, kde to mas ulozeny
- David Matějka
- Moderator | 6445
Ale predpokladam, ze robot loader mas zapnuty ?
App/factories/IMyControlFactory.php
to je predpokladam jen typo
a je to ve slozce app
stejne jako zbytek aplikace?
- David Matějka
- Moderator | 6445
Jak si psal „tady už phpStorm řve“, co tam phpstorm rve? ukaz jak presne ted vypada ten soubor s tovarnou?
- kleinpetr
- Člen | 480
<?php
/**
* Created by PhpStorm.
* User: kleinpetr
* Date: 4/24/15
* Time: 2:34 odp.
*/
namespace App\Factories;
use App\Components;
abstract class IMyControlFactory
{
/** @return Components\MyControl */
abstract function create();
}
řval kvůli tomu, že nebyl abstract
Editoval kleinpetr (24. 4. 2015 16:35)
- Šaman
- Člen | 2640
IMHO jsi začal tím složitějším, a to jak se taková továrnička napíše v Nette s využitím generovaných továrniček a DI containeru.
Základ v čistém PHP je:
Služba (service): vytvořím ji jen jednou a pak už pokaždé
předávám tuto jednu instanci. (Např. připojení k databázi.) Kdysi se na
to používal „návrhový vzor“ singleton. Nette má daleko lepší
nástroj – DI kontejner, ve kterém se vytváří všechny třídy a ve
kterém se pak skládají dohromady. Tento kontejner tedy vytvoří jedinou
instanci služby a pak ji předá všem, kteří ji potřebují.
Továrna na třídy (factory): je to služba, která umí vytvářet nové instace. Kontejner ji tedy předá jako službu, ale v kódu už si nad touto službou zavoláš metodu create() a ta vytvoří novou instanci.
Nejjednodušší tovární třída bez vazby na nette by vypadala třeba takhle:
<?php
class MyControlFactory
{
public function create()
{
return new MyControl;
}
}
?>
Výhoda oproti použití operátoru new
místo továrny je jen
jedna, ale důležitá. Pokud bys potřeboval změnit způsob vytváření té
komponenty, stačí upravit továrnu a nikoliv procházet celou aplikaci.
Takovou si můžeš udělat i v Nette a zaregistrovat jako službu a pak ji
používat: $myControl = $this->myControlFactory->create();
Problém je, když potřebuješ předávat nějaké další závislosti.
Všechny pak musíš injectovat do té továrny jen proto, aby je ta továrna
zase předala té komponentě. Spousta rutinního kodu. Proto vznikly (už jako
Nette specialitka) generované továrny, které umí Nette vytvořit
samo z rozhraní, kterým popíšeš, co má ta továrna vracet.
(Proto jsou všechny anotace povinné, podle nich se určí, co má továrna
dělat.) Název metody create()
je jen Nette konvence, kdyby sis to
psal sám, klidně ji můžeš pojmenovat jinak.
A tohle celé nijak nesouvisí s kódem té komponenty MyControl!
Celé je to jen o tom, jak vytvořit třeba v presenteru instanci komponenty
co nejvýhodnějším způsobem (upravitelnost, testovatelnost, neskrývání
závislostí, …). Ta komponenta vůbec neví, jestli použiješ tenhle aprát
s metodou create()
, nebo někde na prasáka zapíšeš
$myControl = new MyControl;
.
Editoval Šaman (24. 4. 2015 17:49)
- ChocoTUx
- Člen | 31
Ahoj,
prosím, když chci při vytváření komponenty přes továrnu předat nějakou jinou třídu. U mě spojení na databázi? Jde to nějak zařídit?
Aby to fungovalo nějak takhle, jak je v továrně co jsem si napsal ručně?
protected $analytics;
protected $db;
function __construct(\Nette\Database\Context $db) {
$this->db = $db;
}
public function create()
{
$this->analytics = new \App\Model\Analytics($this->db);
return $this->analytics;
}
- jiri.pudil
- Nette Blogger | 1029
Prostě si v konstruktoru komponenty řekni o závislosti, jinak se vůbec
nic nemění, tzn. v rozhraní bude create
metoda bez parametrů.
Nette si samo zjistí, co je té komponentě potřeba předat, a ve
vygenerované továrničce jí to předá.
- ChocoTUx
- Člen | 31
Jo to jsem zkoušel a nefungovalo to (pak jsem smazal starou továrnu – kterou nette najde a i když nepoužívá tak ji zvaliduje). Super díkec.
Ale ještě když si s tím hraji, tak jsem narazil na problém. Když bych místo instance jiné třídy tam chtěl z konfigu dostat nějakou konstantu. Šel jsem na to podle tohoto návodu, ale nějak mi to padá. http://www.zeminem.cz/…nitive-guide
- implement: App\Model\IAnalyticsFactory
create: App\Model\Analytics(test: %promena%)
interface IAnalyticsFactory
{
/** @return \App\Model\Analytics */
function create();
}
function __construct($test){
\Tracy\Debugger::barDump($test);
}
Tak mi to napíše tuhle chybu – Service ‚68‘: Parameter $test in App\Model\Analytics::__construct() has no type hint, so its value must be specified.
A porůznu když zkusím nějaké kombinace, takt o vždy na něčem spadne. Když zkusím v configu místo create tam dát arguments, tak to zahlásí že nezná službu arguments… jiná kombinace mě už nenapadla :/