FacadeORM + generátor Entit

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

RFC

Už nějakou dobu si v hlavě pohrávám s myšlenkou na vytvoření modelové resp. databázové vrstvy, jejíchž značná část bude generovaná podle aktuální struktury databáze. Na databázovou vrstvu používám návrhový vzor Facade, ale vadí mi to velké psaní, které tento vzor vyžaduje (mapper je v podstatě jeden, ale repository, entity a facade tříd je zhruba tolik, co tabulek, tedy strašně moc). Dal jsem si tedy dohromady požadavky na vygenerovaný výsledek:

  • změna struktury tabulky se musí projevit v patřičné entitě a případně i repository (např. přidáním metody findByFoo() pro nový sloupec foo)
  • aktualizace těchto struktur musí být na vyžádání a nesmí to být standardní část aplikace (tj. aby to bylo nějak jako u složky test). Možné řešení přes compilerExtension.
  • automatické generování služeb pro jednotlivé repository v config*.neon, který by se načítal automaticky do aplikace

Samotná struktura tříd v rámci Facade by pak byla klasická

  • facade
  • repository
  • mapper

a data jakožto ORM by byly v Entity resp. EntityCollection. Dále platí, že každá vrstva používá ty nižší bez znalosti jejich implementace. O vrstvách nad nimi nic neví.

Princip navrhovaného ORM

Začnu od konce:

  • Mapper – obecná práce s daty, rozhraní IMapper. Databázový mapper stačí jeden, pravděpodobně dibi (hlavně kvůli query, jež na rozdíl od NDB vrací výsledek select dotazu stejného typu a snáz se v něm dělají složitější dotazy), ale to není moc podstatné.
  • Repository – vyžaduje implementaci IMapper. Kvůli speciálním požadavkům databází (transakce, query) udělám nejspíš i BaseDatabaseRepository vyžadující IDatabaseMapper < IMapper
    • zde bych chtěl zapojit generované repository mezi BaseDatabaseRepository a *Repository a to tak, abych nepřepisoval *Repository, ale zároveň mu vnucoval určité metody (např. findByFoo). Nabízí se tedy asi jen další třída, ale kdyby to šlo udělat přes rozhraní, byl bych raději.
    • repository musí být schopné měnit mechanismus mazání a ukládání (např. aby šlo lehce přidat implementaci Closure Table, např. přes trait)
    • repository vrací data ve formě EntityCollection nebo *Entity < Entity
  • Entity – instance pro řádek tabulky
    • generátor zajistí tvorbu patřičných atributů včetně PHPDoc komentářů pro správný datový typ
    • pravděpodobně bude třeba nějak ošetřit osekání hodnot, které nelze danou entitou uložit. Otázkou je, zda to udělat tiše nebo ne
  • EntityCollection – snad jasné
  • Facade – až teprve zde se řeší aplikační logika. Data získává ve formě Entity/EntityCollection z jednotlivých repository případně od jiných služeb.

V současné době mám částečně fungující generátor (ještě si potřebuju ujasnit drobnosti ohledně generovaných výsledků) a první nástřel samotného ORM, ale ten se asi bude ještě dost měnit.

Chtěl bych Vás požádat o Váš názor na tento koncept a Vaše postřehy a featury, které byste jako vývojáři uvítali.

Editoval castamir (14. 3. 2013 14:27)

fh
Člen | 18
+
0
-

U entit repository i mapperu pak ale bude potřeba řešit, jak se systém vypořádá s vlastními úpravami. Například u entity bude hezké, že bude vygenerovaná i s datovými typy a nějakými gettery, ale časem budeš potřebovat přidat vlastní funkcionalitu, nějakou logiku, která k entitě náleží. Co se stane pak, až uděláš změnu v db? Generátor by měl tvé úpravy ponechat a změnit jen ta strojově generovatelná data.

První možnost je, že tvoje entity budou dědit od těch generovaných a magie/automatika bude v těch generovaných a tvůj kód v poděděné. Tam je ale problém s tím, že bys musel mít ty property protected, což je fuj.

Druhé řešení by mohlo být přes traity. Generátor ti bude generovat traity s private property a tvoje třída ji použije a postaví nad ní logiku. To platí samozřejmě pro všechny vrstvy.

Je to jen taková první věc, co mě napadne, když se nad tím zamyslím.

-- EDIT: I když koukám, že něco z toho jsi už nastínil. Už jsem unaven:)

Editoval fh (13. 3. 2013 21:22)

castamir
Člen | 629
+
0
-

@fh Díky za názor.

Mapper se nebude generovat. Ten stačí pro každý typ dat (databáze, xml apod) pouze jeden. Že budu zasahovat do Repository vím a taky už jsem to naznačil, u entit mě ale momentálně žádná speciální logika nenapadá, resp. mě nenapadá žádná specialita, která by nemohla být už v abstrktní Entity, od které všechny další budou dědit. Pokud ale na to dojde, princip řešení se tím pádem sjednotí s Repository.

V případě, že bych tedy zvolil to dědění, tak by mohly být klidně public – celou třídu bych totiž hodil abstract a bylo by to vyřešený. Traity na druhou stranu vypadají taky lákavě, ale přecijen je to 5.4+, což mi momentálně ještě nevyhovuje.

Editoval castamir (14. 3. 2013 11:14)

fh
Člen | 18
+
0
-

Speciality se týkají operací, které náleží k entitě nějakého typu. Například uživatel na sobě může mít metodu $user->ban('reason') nebo vyvoláni události $user->registred() atd.

Když uděláš třídu abstract, tak jak se vyřeší, že do public property ti nikdo nebude moci zasáhnout a že si ohlídáš datové typy?

castamir
Člen | 629
+
0
-

Jasně, plácnul jsem ptákovinu, abstract nepomůže.

Možná bych i byl schopný to obejít pomocí reflexe a umožnit, aby properties byly private, ale mám pochybnosti o čistotě řešení.

Pro PHP 5.4 a novější jsou pak traity jasnou volbou.

Zkusím to ještě promyslet.