model uvnitr modelu a costructor

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

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

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

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

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

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

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

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

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)

vosy
Člen | 532
+
0
-

Tak toto je super, dekuji.
Mam jeste jednu trapnou otazku :)
Da se nekde docist rozdil mezi Repository a Model, jake metody kam psat?

dix

Šaman
Člen | 2666
+
0
-

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

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.