Jak na „fluent model“ pro použití v šabloně?

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

Zdravím,

jakým způsobem řešíte fluent zápis modelu v šabloně? Řekněme, že mám tuto přímočarou strukturu databáze:

  • users(id,email,name)
  • roles(id,title)
  • user_roles(id,user_id,role_id)

Snažím se na to jít skutečně přímočaře, co entita, to jeden řádek, vnitřní properties reflektují databázi (rozdíl je akorát mezi pascal_case v db a camelCase v entitách). Možná to není úplně košér, ale velice jednoduše se s tím pracuje – stačí mi jeden obecný DatabaseMapper, který si s tím pohraje přes reflection. Navenek mohu s entitou pracovat jen přes metody.

Šablonu naplním takto:

$this->template->userList = $this->userService->getAll();

Proměnná userList nyní obsahuje pole entit UserEntity.

V šabloně bych nyní chtěl vypsat všechny uživatele a jejich role (psáno z hlavy, případné syntax chyby prosím nehroťte):

<div n:foreach="$userList as $user">
    <h4>{$user->name}</h4>
    <ul>
        <li n:foreach="$user->roles as $role">{$role->title}</li>
    </ul>
</div>

V podstatě chci vyjádřit vazby mezi daty nějak přímo v entitách, tzn. má-li nějaká entita property $userId, pak nejspíš bude mít metody

    public function setUser(UserEntity $user) {
        $this->userId = $user->id;
    }

    /** @return UserEntity */
    public function getUser() {
        // Jak implementovat?
    }

A ideálně mít to nějak pořešené i pro M:N vazby.

UserEntity vypadá třeba takto:

class UserEntity extends Nette\Object {

    protected $id;
    protected $email;
    protected $name;

    public function getId() {
        return $this->id;
    }

    // ...

    public function getRoles() {
        // Jak implementovat?
    }

Jak byste implementovali metodu getRoles?

Jediné, co mě napadlo (nástřel):

    public function getRoles(UserRoleService $userRoleService, RoleService $roleService) {
        $ret = array();

        $userRoles = $userRoleService->getUserRoleByUser($this);
        if(!$userRoles)
            return false;

        foreach($userRoles as $userRole) {
            $ret[] = $roleService->getRole($userRole->roleId);
        }

        return $ret;
    }

Ale to nejde použít v šabloně. Pak mě ještě napadlo něco jako:

    public function injectRoleServices(UserRoleService $userRoleService, RoleService $roleService) {
        $this->userRoleService = $userRoleService;
        $this->roleService = $roleService;
    }

V presenteru pak iterovat nad všemi entitami a injectnout jim všem služby předtím než to celé pošlu do šablony. Takovéhle řešení ale taky moc nemusím, zhoršuje to čitelnost, není vidět, že getRoles potřebuje předtím nějaké služby. Ona i samotná představa že entitě cpu nějaké služby mi jde tak nějak proti srsti. Taky jsem přemýšlel že bych si udělal ještě nějakou fasádu, která by nějak obalovala uživatele i role a měla metodu třeba findAllUsersAndTheirRoles(), která by vše nějak načetla a naplnila uživatele rolemi. To už by ale chtělo přepsat entity a způsob, jakým mapper pracuje s properties a ve výsledku bych měl dvě metody, které pracují se stejnými entitami, ale „jinak“ (prostě je to divný).

Třeba na to je nějaké jednoduché řešení, které mě prostě nenapadá, ale momentálně začínám mít pocit, že všechny tyhle x-vrstvé modely v PHP jsou prostě BS, šíleně to odvádí pozornost, už tak mi snaha naučit se to sežrala hodně času, a stejně pro většinu věcí použijeme MySQL nebo něco podobného, tak proč to prostě všechno nenapráskat rovnou v Nette\Database a bude to hned, dokonce pravděpodobně s lepší optimalizací dotazů…

Inu, přiznám se, že mi teď modelem pro tyhle případy probublává funkce getTable z mapperu až do service a zkuste hádat co dělá :-(

Určitě nejsem sám, kdo něco takového řešil, jen bych potřeboval trochu popostrčit správným směrem. Pro mě je to celkem zákys..

Předem díky!

Editoval Zax (20. 3. 2014 12:51)

greeny
Člen | 405
+
0
-

Můžeš vyzkoušet LeanMapper, tam je to řešený celkem hezky ;)

mystik
Člen | 320
+
0
-

Já to řeším tak, že entitě předám referenci na mapper, který ji vytvořil. Entita si pak podle potřeba volá metody mapperu pro donačtení souvisejících dat. Service tedy předáváš do mapperu, do entity předáváš mapper. Alternative je, že si do entity dáš proxy objekty pro lazy loading. Tedy v mapperu nenastavíš hodnotu, ale přidáš proxy, které to hodnotu v případě potřeby donačte.

Zax
Člen | 370
+
0
-

LeanMapper jsem zkoumal dřív, ale chci radši používat Nette\Database než Dibi ;-)

Narazil jsem ale na YetORM a ten taky vypadá dost slibně a používá NDb. Sice ta struktura modelu je dost jiná než bych si představoval (škoda že je to velice úzce svázané s databází), ale aspoň to řeší můj problém, zároveň využívá efektivitu NDb, zbaví mě nutnosti reflektovat tabulku sloužící k M:N vazbám a spousta se toho dá napsat přes anotace, což ušetří čas :-) To je víc, než jsem potřeboval před měsícem, a jestli to bude fungovat (bez větších zádrhelů), tak se bez toho v budoucnu už asi neobejdu :-D Uvidíme, jen co přepíšu model (zítra)…

Editoval Zax (20. 3. 2014 20:59)

Zax
Člen | 370
+
0
-

Sorry za doublepost, ale chtěl jsem jenom oznámit, že mám model přepsaný do YetORM a funguje to naprosto parádně! Akorát byl drobný zádrhel že do identity nemůžu ukládat YetORM entitu (kvůli serializaci), ale na to jsem si vytvořil bokem jinou entitu, která kopíruje strukturu, ale chová se jen jako tupý přenašeč dat, takže žádný velký problém.

Naprosto suprový nástroj, doporučuji a děkuji tvůrcům!

Šaman
Člen | 2668
+
0
-

V identitě jsem si udržoval jen id uživatele, role a v datech pouze info pro ladění. Nevím, jak máš koncipovaný model, já ve starých projektech nad YetOrm používal service locator a $model->user už byla plnohodnotná entita kteru jsem měl přístupnou prakticky odevšad. Pro tvoření chytrého ACL k nezaplacení.

Jinak YetOrm byl fajn, ale narazil jsem při používaní NDb (resp. jak jsem byl po čase poučen, problém byl v NDb\Table). A LeanMapper mi zatím naprosto vyhovuje. A jestli jsem správně pochopil nedávný post, tak do budoucna bude možné sestavit si vlastní balíček Nette bez NDb s Dibi, takže padne i ten důvod, že NDb je nativní. Dobré je, že už je na výběr pár dobrých a lehkých ORM i databázových vrstev, takže každému co je libo.

Zax
Člen | 370
+
0
-

Nad service locatorem jsem taky dřív uvažoval, ale zavrhl jsem ho, protože si nechci předávat klavír, osobně mám radši, když vidím přesně které závislosti presenter potřebuje. Ale je fakt, že zápis $this->user->identity->userEntity je hodně naprd, ale na většinu případů v poho stačí $this->user->id nebo $this->user->roles, Nette\Security\User si to beztak tahá z identity. V identitě mám ještě navíc name a email („odhlásit (jméno)“ mám na každé stránce, přemýšlím, jestli má smysl si napsat potomka třídy User a přidat mu metody getName a getEmail…

Ten LeanMapper určitě vypadá líp, asi ho někdy budu muset zkusit. Možná i někdy brzo, po třetím kompletním přepsání modelu už to začíná být celkem rutina :D

Šaman
Člen | 2668
+
0
-

S LM doporučuji počkat na dokumentaci, pak bude epesní. S nepoužitím service locatoru souhlasím, já už ho taky nepoužívám. Ale s tím „odhlásit <jméno>“ jsem už narazil, když bylo možné si jméno změnit a na tomto odkazu zůstalo stejné (identita je v session, takže pokud zůstáváš přihlášený, tak máš stejnou klidně i týdny až měsíce). S rolemi by to bylo stejné, ale zatím jsem měl všude role napevno, často jen ‚admin‘, (‚manager‘) a ‚user‘.

Zax
Člen | 370
+
0
-

Dík za radu, počkám.

Hm to s tou změnou je fakt. Ale vždycky se dá v případě nutnosti kontrolovat natvrdo proti databázi a když to nesedí, tak identitu přepsat. A u změny jména bych třeba vyhodil flashMessage s upozorněním (ničemu to nevadí, je to jen kosmetická drobnost). Kdybych ho používal k přihlašování, tak bych pochopitelně vynutil odhlášení.