model uvnitr modelu a costructor
- vosy
- Člen | 532
ahoj, mam dotaz na nahrani modelu uvnitr jineho modelu.
mam hlavni model
class Repository extends Nette\Object
{
protected $db, $MyDb;
public function __construct(Nette\Database\Connection $db,
MyDb $MyDb)
{
$this->MyDb = $MyDb;
$this->db = $db;
}
}
pak mam dalsi submodel s funkci:
class JazykoveMutaceModel extends Repository
{
public function pridatJazyk($sqlData)
{
$this->db->table(Glb::$NAZEV_WEBU.'_jazykoveMutace')
->insert($sqlData);
$fData = array('lang'=>$sqlData['url']);
$StrankyModel->createTable($fData);
}
problem je ze musim definovat $StrankyModel ktera take dedi z Repository
ale v repository v kconstructoru mam
Nette\Database\Connection $db, MyDb $MyDb
takze toto musim zopakovat i u $StrankyModel
Ale jak by to melo vypadat?
$StrankyModel = new StrankyModel(Nette\Database\Connection $db, MyDb $MyDb);
pripadne jak resit kdyz v Repository construktoru budu implementovat dalsi tridu, to budu muset rucne zmenit u vsech modelu?
- Šaman
- Člen | 2666
Třídu $strankyModel
si nech vytvořit v DI kontejneru jako
službu a $jazykoveMutaceModel
taky, jen ji přidej další
závislost na $strankyModel
(ideálně autowiringem, pomocí
rozhraní).
Přečti si nějaké články
o předávání závislostí (dependency injection – DI) – pak pokud
použiješ operátor new
, tak se radši dvakrát rozmyslíš,
jestli je nutné nějakou instanci třídy vytvářet a jestli by ji
nestačilo předat jako závislost.
Edit: Také si prohlídni tento
příspěvek. Je to stejný případ, jako u tebe, až na to, že ty
používáš operátor new
, což je ještě ‚horší‘ (v tomto
konkrétním případě), než si ty služby tahat ručně z kontejneru.
Editoval Šaman (16. 9. 2013 22:37)
- vosy
- Člen | 532
neco takoveho jsem zzkousel
class JazykoveMutaceModel extends Repository
{
public function __construct(StrankyModel $StrankyModel)
{
parent::__construct();
$this->StrankyModel = $StrankyModel;
}
ale musim tam dat parent::__construct();
abych ziskal pripojeni k DB ktere je v Repository.
- vosy
- Člen | 532
Takto mi to funguje
class JazykoveMutaceModel extends Repository
{
protected $StrankyModel;
public function __construct(StrankyModel $StrankyModel,
Nette\Database\Connection $db,
MyDb $MyDb)
{
parent::__construct($db, $MyDb);
$this->StrankyModel = $StrankyModel;
}
ale prave mi prijde blbe kdyz v Repository injectnout dalsi tridu, tak tuto tridu budu muset volat v kazdem modelu kde volam parent::__construct()
- Šaman
- Člen | 2666
Navzdory HosipLanovu doporučení (proč nepoužívat injectování setterem) toto používám právě v takovýhle případech, kde je jedna závislost povinná už z předka a další mohou být specifické pro potomka. Já mám například templateLocator v abstraktní komponentě, tvůj problém je analogický.
Tvoje řešení (volání parent::construct()) je asi nejčistší, ale pokud ti připadá natolik nepohodlné, že ti nevadí, že nějaká závislost není vidět na první pohled, tak si tu databázi injectni automaticky (metoda inject*() v Nette 2.1) a nebo nastav ručně. Zůstane ti prázdný konstruktor pro další závislosti.
P.S. Ta závislost ve skutečnosti není skrytá (závislost předka je
stále řádně deklarovaná), jen hrozí, že vytvoříš objekt a zapomeneš
injectnout databázi, pak bude nefunkční. A v samotném kódu konkrétního
repozitáře nebude ani zmínka o zděděné závislosti na databázi, takže
taková chyba hrozí třeba pokud se ke kódu dostane cizí programátor.
Můžeš se o tom zmínit třeba v dokumentační anotaci.
Pokud bys všechny povinné závislosti předával konstruktorem, tak by se ti
nemohlo stát, že na nějakou zapomeneš.
Edit: Ten odkazovaný příspěvek řeší jednodušší zápis místo používání inject* metod, ale HosipLanovo vysvětlení proč toto nepoužívat mimo presentery (5.příspěvek) se týká i těch inject metod.
Resume: Pro povinné závislosti používej konstruktor, pokud nemáš dobrý důvod použít jiný způsob předávání závislostí. Tento dobrý důvod je například v presenterech. Jestli je potřeba mít volný konstruktor i v tvém případě, to už musíš posoudit sám.
Editoval Šaman (17. 9. 2013 2:54)
- Šaman
- Člen | 2666
Je tu ještě jedna věc k zamyšlení, zcela z jiného soudku, proto
píšu samostatný příspěvek. Skutečně potřebuje
JazykoveMutaceModel
ke své práci StrankyModel
?
Modely by na sobě neměly být závislé, každý by si měl autonomně
spravovat svoji problematiku. Pokud chceš vytvořit kód který potřebuje
více modelů, tak k tomu slouží servisní vrstva (fasáda), což je obyčejná
třída, které předáš (ideálně konstruktorem) oba modely. Ta může
například pomocí jednoho modelu najít správnou stránku a pomocí druhého
modelu s ní něco udělat.
Co přesně se pokoušíš naprogramovat, to nevím, takže je na tobě, jestli to jinak nejde a nebo by pomohl refaktoring. Já mám všechny modely závislé jen na databázi, takže klidně mohu používat konstruktor.
Editoval Šaman (17. 9. 2013 3:08)
- vosy
- Člen | 532
v JazykoveMutaceModel mam pridani jazykove mutace a kdyz pridam novou jazykovou mutaci (ru) tak chci vytvorit sqltabulku pages_ru a vytvareni tabulky pages_ chci mit v modelu StrankyModel.
je to blbost?
mohl bys mi ukazat priklad te servisni vrstvy?
Editoval vosy (17. 9. 2013 8:48)
- Šaman
- Člen | 2666
Jestli jsem dobře pochopil to vytváření jazykové mutace, tak z hlavy nástřel:
<?php
class Repository
{
/** @var Nette\Database\Connection */
protected $db;
public function __construct(Nette\Database\Connection $db)
{
$this->db = $db;
}
}
/**
* Repozitář pro práci s jazykovými mutacemi
*/
class LanguageRepository extends Repository
{
/** @param string */
public function addLanguage($lang)
{
# přidání nové jazykové mutace do seznamu jazyků
}
}
/**
* Repozitář pro práci se stránkami
*/
class PageRepository extends Repository
{
/** @param string */
public function addLanguagePage($lang)
{
# přidání tabulky nové jazykové mutace
}
}
/**
* Model pro komplexní práci s jazykovými mutacemi a souvisejícím vytvářením stránek
*/
class LanguagePageService
{
/** @var LanguageRepository */
protected $languageRepository;
/** @var PageRepository */
protected $pageRepository;
public function __construct(LanguageRepository $languageRepository,
PageRepository $pageRepository)
{
$this->languageRepository = $languageRepository;
$this->pageRepository = $pageRepository;
}
/** @param string */
public function addLanguage($lang)
{
# komplexní vyřešení přidání nové jazykové mutace
$this->languageRepository->addLanguage($lang);
$this->pageRepository->addLanguagePage($lang);
}
}
?>
Když to, čemu říkáš model označíš jako repository, tak bude víc zřejmé, že si navzájem nemají fušovat do svých dat. On to ve skutečnosti model není, je to jen jedna ze tříd modelu (v nejjednodušším případě ti jedna třída stačí, ale na složitější věci je potřeba komplexnější model).
Servisní vrstva pak pracuje o úroveň výš a zajišťuje funkcionalitu napříč repozitáři.
Mimochodem doporučuji používat anotace @var
,
@param
a @return
kvůli napovídání v IDE. Pokud
je typ parametru uvedený přímo v kódu, tak se anotace psát nemusí
(možná ani nemá, aby nemohl vzniknout rozpor).
Editoval Šaman (17. 9. 2013 9:27)
- Šaman
- Člen | 2666
Model je většinou jen jeden a řeší veškerou funkčnost aplikace. Představ si, že by nějaká
aplikace měla běhat nejen v prohlížeči, ale byla by funkční i přes
telnet (z příkazové řádky). V tu chvíli máš model (kterému to je
jedno) a dvakrát presenter+view vrstvu pro každé rozhraní. Model je tedy
všechno, čemu nezáleží na interface. Komunikace s databází, řízení
oprávnění, veškeré výpočty, entity, stavy a já nevím co ještě. Slovo
model
v názvu třídy se většinou nepoužívá, leda pokud je
práce s daty tak jednoduchá, že ji pokryje jediná třída. Pak ji můžeme
pojmenovat třeba UserModel – část modelu pro práci s uživateli.
Repository sice také není úplně přesný název pro to, co je ukázáno
v quickstartu (QS), ale pro začátek je to dostačující. Repozitář je
třída, která se stará o práci s daty jedné entity. V QS sice entity
nejsou, ale rozdělení repozitářů podle dat, které spravují je zřejmé
(uživatel, stránka, jazyk, článek, ..). Jeden repozitář by měl řešit
jednu entitu, nebo skupinu příbuzných entit, často to odpovídá jedné
tabulce, ale nemusí tomu tak být vždy.
Pokud by chtěl pohledat na googlu, tak náš repozitář je ve skutečnosti
'repository pattern'
, 'DAO - data access object'
a
'mapper'
dohromady.
Ale protože by se veškerá aplikační logika měla nacházet v modelu,
tak je potřeba ještě nějaká třída nad jednotlivými repozitáři. A té
se říká servisní. Nebo také fasáda, nebo model ('service'
,
'facade pattern'
).
Opět doporučuji článek 5 vrstev modelu, většina toho tam je vysvětlená. I když v QS je všechno od repozitáře níž spojeno dohromady a pojmenováno repozitář.
Jako bonus přihodím jeden obrázek, který situaci docela vystihuje.
DAO je u nás repozitory, Bussines objects jsou entity (které nemáme, místo
nich přenášíme data v nějakém objektu Nette\Database).
Editoval Šaman (17. 9. 2013 10:25)
- Filip Procházka
- Moderator | 4668
Právě v tomhle případě je obecná třída Model
zlo,
protože aby sis ušetřil pár řádků (slovy pět), tak vždy dědíš a
vždy musíš řešit dependency hell v konstruktoru. Pokud píšeš model,
který vyžaduje nějaké repository, nepiš basemodel, který bude přijímat
jakékoliv repository (nebo connection), napiš prostě třídu, která přijme
svou závislost a nebude ji zbytečně soukat přes rodiče.
A jen pro pořádek, jsou i případy kde se inject metoda hodí, ale skoro vždy jsem (třeba za týden, nebo měsíc) potom vymyslel lepší návrh, kde už nebyla potřeba.