tiny ‘n’ smart
database layer

Odkazy: dibi | API reference

Oznámení

Omlouváme se, provoz fóra byl ukončen

Lean Mapper – tenké ORM nad dibi

před 6 lety

Tharos
Člen | 1042

@Šaman: Je hned více možností, jak tohle v Lean Mapperu řešit. Pokud bych z nějakého důvodu osobně nechtěl použít standardní řešení ve smyslu mít pro Sex entitu, šel bych na to asi takhle přímočaře:

Řešení jsem smazal, protože existuje elegantnější. :) Nechci, aby zde staré zůstalo jako podnět k hackování.

Ad referenced: Úplně přesně si odhalil první komplikaci – pak bys musel mít repositář i pro tyhle číselníky. Rozviň si ve volné chvíli v hlavně jednoduchou myšlenku: entita nebude načítat související entity přímo z databáze, ale přes patřičné repositáře. Správné řešení, tak se to má dělat! Ale pak vyřeš, jak předávat repositáře, jak bude vypadat Identity Map, jak to skloubit s kladením konstantního počtu dotazů, jak mapovat a persistovat entity rozlezlé přes více tabulek… Já jsem tohle v posledních dnech řešil a dobral se k výsledku, že by to asi nějak šlo, ale ORM by bylo třikrát takové, rozhodně zabugované a umožňovalo by toho ve výsledku asi méně (ale bylo by nezávislé na úložišti a abstraktní!). Lean Mapper je Lean a takový i zůstane, to jsem si v posledních dnech ověřil. :) Nechci psát další Doctrine 2 a nakonec řešit podobné problémy

Ad závislost na Nette: Není to žádné strategické rozhodnutí, proti Nette samozřejmě nic nemám. Jen jsem v průběhu vývoje zjistil, že z Nette používám jenom AnnotationsParser a že kvůli téhle jediné třídě je fakt škoda mít nástroj závislý na Nette. A tak jsem si napsal svůj vlastní minimalistický parser a Nette zkusil odstřihnout.

Pokud potřebuješ repositáře obohatit o vlastní anotace, má to úplně jednoduché řešení: ve svém BaseRepository už použij normálně AnnotationsParser z Nette.


Jinak, ještě bych rád dodal, že uvedené řešení je fakt přímočaré (považ, na kolik je řádek kódu). Pokud bys chtěl nějaké „učesanější“, šlo by to řešit i jinými způsoby. Asi by to pak nebylo tak WTF :).

Kdyby se Ti to nelíbilo, řekni – mám v hlavně hned jedno další řešení, které by nebylo takovým harakiri :).

Edit: Večer to další řešení doplním. Je fakt, že výše předvedené použití hraničí s hackováním, které mohu provádět aktuálně asi jenom já, který vidím pod pokličku, ale nikomu kolem moc nepomůže. :–P Hold to bude o pár řádků delší. :)

Editoval Tharos (12. 6. 2013 0:35)

před 6 lety

Šaman
Člen | 2275

Díky, tohle mi připadá dobré. :)

Jen se mi moc nelíbí potřeba volat ručně tu cleanReferencedRowsCache(). Bez ní by docházelo k WTF chování? Nebo co by se stalo, kdyby člověk na tuto neintuitivní věc zapomněl?

// P.S. Bylo by dobré doplnit do dokumentace i ty nízkoúrovňové funkce, pokud mají být běžně používány. Ale na to, že podrobnosti jsou jen ve zdrojácích a možná je ví autor, na to jsem si bohužel zvykl v NDatabase.

Editoval Šaman (11. 6. 2013 16:35)

před 6 lety

Tharos
Člen | 1042

To bude řešit to mé druhé řešení, které sem ještě dneska pošlu. :) Tohle je fakt až moc low-level a taky trochu hackující (proměnná sex_id se zneužívá pro uložení stringu)…

Editoval Tharos (11. 6. 2013 16:36)

před 6 lety

Šaman
Člen | 2275

Díky. Jinak standardní řešení pomocí samostatných entit a repozitářů používat nechci, protože takových počitatel mívám běžně velké množství. Používám je pro různé stavy (projekt aktivní, odevzdaný, archivovaný), skupiny (kontakt je přítel, rodina, obchodní partner), role (admin, user, moderator) a tvoří zhruba čtvrtinu databáze.
A navíc tyto pomocné tabulky nejsou v Entitně-Relačním diagramu. A na objektové straně ORM bychom měli být pracovat jen s entitami, nikoliv s tabulkami. Takže bych přivítal i nějakou nativní podporu pro výše zmíněný problém, usnadnilo by to práci.

Entity a repozitáře pro tyto tabulky bych asi nevytvářel, ani kdyby byl požadavek na editaci této tabulky. Podle mě by měl třeba žánr knihy zvládat repozitář BookRepository v metodách addGendre($string), removeGendre($id|$string).

Dokonce by bylo ideální mít možnost takto pracovat i s tagy, tedy mít mezi tabulkou a počitadlem vazební tabulku. Pokud by byl tag skutečná entita, pracovalo by se s ní jako s entitou (echo $this->tags[0]->text;). Pokud by to bylo jen počitadlo, entita by se tvářila, jako by to bylo její vlastní asociativní pole (echo $this->tags[0]).

//pozn. Zkusil jsem to řešit postaru (tedy pomocí samostatných entit) a narazil jsem na další rozdíl oproti „pravé“ entitě. Všechny pravé entity mají nějakého vlastníka, který je vytvořil (používám je v ACL pro přístup jen ke svým datům), ale u počitadel žádný takový vlastník nemá smysl. Počitadla jsou skutečně jen vlastností rodičovské entity, které jsou z interního důvodu úložiště uložené samostatně. V objektových databázích by neexistovaly.

Editoval Šaman (11. 6. 2013 17:19)

před 6 lety

Etch
Člen | 404

@Tharos:

Mimochodem ještě by se mi častokrát hodila jedna věc, ale nejsem si úplně jistý, jestli by se to hodilo přímo do „základu“ LeanMapperu.

namespace Model\Entity;

/**
* @property int $id
* @property string $name
* @property int $pages
*/

class Book extends \LeanMapper\Entity{

}
$bookRepository = new \Model\Repository\BookRepository($connection);

$book = $bookRepository->find(1);
$bookOriginal = clone $book;

$book->name = 'foo';
$book->pages = 192;

$book->compare($bookOriginal);

před 6 lety

Jan Tvrdík
Nette guru | 2550

Šaman wrote: počitatel mívám běžně velké množství. Používám je pro různé stavy (projekt aktivní, odevzdaný, archivovaný), skupiny (kontakt je přítel, rodina, obchodní partner), role (admin, user, moderator) a tvoří zhruba čtvrtinu databáze.

Můžeš mi vysvětlit, proč nepoužíváš normální enum?

před 6 lety

Šaman
Člen | 2275

Protože je občas požadavek na editaci těch tabulek. A i když není, je možnost občas něco přidat bez zásahu do kódu. Nelíbí se mi návrh, kde mám čísla v databázi a texty v modelu.

před 6 lety

Tharos
Člen | 1042

@Šaman: Tak jak jsem slíbil, posílám ještě hezčí řešení, které bych osobně použil, kdybych z nějakého důvodu nemohl/nechtěl pro ty číselníky použít enum nebo nadefinovat samostatné entity.

Následující kód funguje ve verzi z aktuální develop větve. (Co jsem upravoval popíšu vzápětí.)

<?php

namespace Model\Entity;

/**
 * @property int $id
 * @property string $name
 */
class Author extends \LeanMapper\Entity
{

    /**
     * @return string
     */
    public function getSex()
    {
        return $this->row->sexName !== null
                ? $this->row->sexName
                : $this->row->referenced('sex')->name;
    }

    /**
     * @param string $sex
     */
    public function setSex($sex)
    {
        $this->row->sexName = $sex;
    }

}
?>
<?php

namespace Model\Repository;

use DibiConnection;

class AuthorRepository extends \LeanMapper\Repository
{

    private $genders;


    public function __construct(DibiConnection $connection)
    {
        parent::__construct($connection);
        $this->genders = $connection->select('*')->from('sex')->fetchPairs('name', 'id');
    }

    public function find($id)
    {
        $entry = $this->connection->select('*')->from($this->getTable())->where('id = %i', $id)
                ->fetch();

        if ($entry === false) {
            throw new \Exception('Entity was not found.');
        }
        $entry->sexName = null;
        return $this->createEntity($entry);
    }

    protected function beforePersist(array $values)
    {
        if (isset($values['sexName'])) {
            $values['sex_id'] = $this->genders[$values['sexName']];
            unset($values['sexName']);
        }
        return $values;
    }

}
?>

Tentokrát už je to bez hacků a potřeby vyznat se dopodrobna ve vnitřnostech knihovny. :) Zase jsem upravil i ten archiv, kdyby sis s tím chtěl víc hrát… Co jsem teď commitoval a není zdokumentované je ta metoda beforePersist() (a existuje i beforeCreate() a beforeUpdate(), viz GitHub), které umožňují upravit surová data těsně před tím, než se předají databázi. Nově tedy kvůli podobným věcem není zapotřebí přetěžovat a z části přepisovat celou metodu persist().

Jinak pokud bys těch číselníků měl opravdu hodně a chtěl s nimi pracovat tímto stylem, dokážu si představit nadefinování BaseEntity a BaseRepository tak, abys s tím pak měl minimum psaní. Třeba bys pak jen mohl vždy vyjmenovat v anotacích, které ty položky jsou enum typu (pojatého tímto způsobem).


Zdokumentovat i ty nízkoúrovňové funkce plánuji. Chci mít v dokumentaci nějakou stránku věnovanou právě detailům, jak Lean Mapper vnitřně funguje. Návrhově je IMHO jednoduchý a k plnému pochopení myslím stačí málo. Takže to bude spíš test pro mě, jak srozumitelně to svedu popsat. :)

V podstatě dokončit dokumentaci bych chtěl po vydání verze 1.4.0, což bude v horizontu několika dní. Do té doby nejasnosti rád enormně podrobně zodpovím zde. :)

Co se Tvých dalších dotazů týče, Tebou navrhovaná použití jsou už dost specifická. :) Pokud bys chtěl navést v řešení nějakého dalšího problém, poprosil bych o co nejkonkrétnější zadání.

Editoval Tharos (12. 6. 2013 0:39)

před 6 lety

Tharos
Člen | 1042

@Etch: Určitě zajímavá myšlenka. Jen jsem taky na vážkách, jestli by tohle mělo být nativní součástí knihovny. :) Tohle by totiž mělo jít hravě doimplementovat v nějaké BaseEntity.

Nicméně v souvislosti s tím mě napadlo, že by se asi hodilo mít možnost získat všechny hodnoty entity (analogie k současné getModifiedData() metodě, prostě nějaká getData()), která by pak implementaci compare() usnadnila.

Implementaci tohohle zvážím. Eventuálně by mohlo jít nad entitou (nebo alespoň nad LeanMapper\Row) iterovat a číst tak hodnoty…

před 6 lety

Šaman
Člen | 2275

Díky. Ještě by mě zajímalo, jestli plánuješ zavést podporu pro převod camelCase v PHP na camel_case v SQL. Je to zase spíš věc mapperu, camelCase se v databázi nepoužívá, protože některé relační db s tím mají údajně problém. (Pro jistotu jsem to teď zkoušel a ten problém je v nekonzistentní case sensibilitě na Win a Linux.)

Další drobnost, která mě na chvíli překvapila je sloupec id ve spojovací tabulce book_tag. Ten je zbytečný, že?

// a další: Kolekce je teď prosté pole, nebylo by dobré použít nějaký ArrayObject s tím, že by jediná podmínka byla Iterable a ArrayAccess a byla by možnost nastavit si vlastní třídu Collection? Ještě jsem nezačal prozkoumávat filtry, ale předběžně předpokládám, že bych ji využil třeba na řazení a právě filtrování. A na co už jsem při přepisování z NDatabase narazil je, že se nedá volat např. $users->count(), musí se count($users). Přecijen na objektové straně ORM bych očekával objekty.

Editoval Šaman (12. 6. 2013 6:30)

před 6 lety

Tharos
Člen | 1042

Podpora pro mapování položek na libovolné sloupce už existuje, psal jsem o tom tady.

Jo, ten sloupec ID ve spojovací tabulce vůbec být nemusí. :) ORM ho v jednoduchém hasMany vztahu vůbec nepoužívá.

Kolekci pro sdružení více entit k sobě stopro zavedu – ona bude zapotřebí i pro tu persitenci a správu jednoduchých M:N vazeb. Count by ta kolekce klidně obsahovat mohla, to do ní tedy rovnou naimplementuji.

před 6 lety

Šaman
Člen | 2275

Ahoj, to jsem rád, že už jsi tu.
Dá se nějak udělat $entita->toArray(), případně v entitě $this->row->toArray()?
Nebo jak plníš formuláře? Po jednotlivých položkách?

před 6 lety

Tharos
Člen | 1042

Měl jsem to akorát rozdělané. :) Tak jsem to dopsal a pushnul.

Použij aktuální verzi z GitHubu (z develop větve). V ní má entita kromě getModifiedData() nově i metodu getData(), která řeší přesně to, co potřebuješ.

před 6 lety

Šaman
Člen | 2275

Díky, tomu říkám fast support :)

Jinak tu vazbu na Nette\Object jsem si nakonec nasimuloval překopírováním NObject kódu do svého Repository. Nelíbilo se mi, že není možné volat parent::__call() a stejné by to bylo i s ostatníma magickýma funkcema. A pokud bych toho parenta nevolal, tak by mi to přestalo fungovat, pokud ty bys někdy do Repository nějaké __call přidal.

před 6 lety

Tharos
Člen | 1042

U toho, co jsi popsal, mám dojem, že to kopírování kódu z NObject ani není nutné. :) Mělo by stačít použít Nette\ObjectMixin (ten k tomu přímo slouží – totiž aby se chování Nette\Object dalo „naroubovat“ na třídy, které z Nette\Object přímo nedědí).

Každopádně __call do výchozího repositáře asi nikdy přidávat nebudu, protože mě nenapadá, k čemu by mohlo být dobré. :)

Editoval Tharos (12. 6. 2013 9:55)

před 6 lety

Šaman
Člen | 2275

Tak ta getData() funguje, dokonce tak, jak jsem původně chtěl, ale pak jsem došel k názoru, že vlastně nechci data s klíči podle SQL sloupců, ale s těmi názvy proměnných, které používám v PHP.

  1. Jsme na objektové, PHP straně a to, že proměnnou $userName mám uloženou ve sloupci user_name by mě při práci s entitou nemělo zajímat. Výjimka je práce s $this->row, ale ta není součástí veřejného rozhraní. I když sám nevím, jak bych řešil tvorbu pole z navázaných objektů. Asi by bylo praktické místo array('user' => User) mít array('user_id' => 1) array('user' => 1).
  2. Když změním název sloupce a upravím mapování, tak nyní bych musel upravovat i formuláře, protože getData mi najednou vrací jiné pole.. Ideální by bylo, kdyby se po změně názvy sloupce muselo upravit jen mapování a samozřejmě i dotazy ke kterým využívám $row.

Editoval Šaman (12. 6. 2013 10:05)

před 6 lety

Šaman
Člen | 2275

Tharos napsal(a):

U toho, co jsi popsal, mám dojem, že to kopírování kódu z NObject ani není nutné. :) Mělo by stačít použít Nette\ObjectMixin (ten k tomu přímo slouží – totiž aby se chování Nette\Object dalo „naroubovat“ na třídy, které z Nette\Object přímo nedědí).

Každopádně __call do výchozího repositáře asi nikdy přidávat nebudu, protože mě nenapadá, k čemu by mohlo být dobré. :)

Já ho používám na magické findByFooAndBar($foo, $bar). Našeptávání řeším anotací, ještě musím dopsat kontrolu, aby šly magicky volat jen metody, které jsou v té anotaci vypsané.

před 6 lety

Tharos
Člen | 1042

Ty bys potřeboval mít nějakou metodu getValues(), která by vracela pole „vysokoúrovňových“ hodnot entity. To, že getData() a getModifiedData() vrací nízkoúrovňová data má svůj smysl a účel (druhá zmíněná se používá při persitenci).

Já tu metodu, která Ti chybí, ještě doplním. Jen už to asi nebude tak fast support v řádu minut, protože musím začít něco dělat :–P. Každopádně zatím se tohle dá obejít individuálním přístupem k jednotlivým položkám. Ještě chci taky rozmyslet, jak by to bylo nejlepší z hlediska názvosloví… Možná by mělo existovat spíš něco jako getData() a getRawData().

Editoval Tharos (12. 6. 2013 11:02)

před 6 lety

Šaman
Člen | 2275

Díky, já to zatím nepotřebuji (jen jsem zkoušel, jak funguje to mapování na jiný název sloupce a rozsypal se mi formulář). Možná to tu nech uležet jako námět do diskuze. Ale argument je jasný – při práci s entitou zvenčí chci data entity a ne databáze. Ta $rawData mě budou zajímat při persistenci, $data při práci s formuláři.
To getData() a getRawData() / getRowData() by bylo fajn.

Editoval Šaman (12. 6. 2013 10:12)

před 6 lety

Tharos
Člen | 1042

Jo, přesně tak mi to přijde nejrozumnější. Asi, ještě to nechám uležet… A pak to kdyžtak v develop větvi upravím. Díky za inspiraci. :)

před 6 lety

Michal III
Člen | 84

Tento počin se mi velice zamlouvá. Vypadá to jako solidní mezischůdek mezi samotným dibi, kde mi přestává stačit DibiDataSource, a Doctrine 2, která je na mě prozatím přeci jen příliš obrovská.

Nicméně chtěl bych se zeptat, zda-li nestojí za úvahu nějakým způsobem dodefinovat vlastní konvence. Myslím, že spousta lidí (minimálně já) používá množná čísla u názvů tabulek (books, tags…). Mohl by být tedy řetězec mezi názvem tabulky a _id definovatelný?

Další věc je konvence pomocných m:n tabulek. Já osobně používám raději než znak _ znak T pro oddělení názvů tabulek čistě pro grafické přednosti písmene T (bookTtag) – přesněji to vymezuje oddělení spíše než _, protože podtržítko může být použito i v názvu samotné tabulky . Každopádně opět nechat možnost nadefinovat vlastní řetězec oddělující názvy tabulek.

Jsem si vědom, že názvy sloupců a tabulek lze definovat ručně, ale jak již bylo zmíněno, je to více psaní, proto by bylo lepší ponechat tuto možnost až pro speciální případy (např. categories).

před 6 lety

Šaman
Člen | 2275

Název tabulky si můžeš změnit v repozitáři přepsáním metody getTable().
Množné číslo se poslední dobou moc nepoužívá, protože se špatně získává z názvu entity (Tag v tabulce tags, ale Category v tabulce categories). Ten znak T uprostřed vidím poprvé, podtržítko je běžně používané. Asi to půjde, ale za cenu více psaní v dotazech a persistorech pracujících s m:n vazbou. Já zase dřív používal pro spojovací tabulku slovní popis (bookHasTag), takže konvencí je spousta a definovat jiný oddělovač než znak oddělovač (podtržítko) mi připadá už moc WTF. Spíš mít možnost v definici vazby napsat přes jakou tabulku se vazba realizuje, a to už myslím LeanMapper podporuje.

Rozhodně ti však doporučím držet konvence, kterou dodržuje LeanMapper, NDatabase a tuším že stejnou má i ta Doctrina. Ušetříš si tím spoustu psaní a problémů, které před tebou ještě nikdo neměl (takže se špatně googlí). Navíc se pak program stává přehlednější, pokud bys pracoval v týmu, tak se nějaká obecně rozšířená konvence stává nutností.

Tedy:

  • název tabulky stejný jako název entity, malým písmem jednoným číslem (entita Author, AuthorRepository, tabulka author)
  • v každé tabulce sloupec id (kromě jednoduchých vazebních tabulek)
  • cizí klíče podle názvu proměnné v entitě ($author ve sloupci author_id, $owner ve sloupci owner_id – tohle nemá spojitost s tím, v jaké tabulce bude author a owner uložený, u nás budou asi obě v tabulce person)
  • spojovaví tabulky ve tvaru person_group (pořadí záleží na tobě, často si místo podtržítka můžeš dát spojku has, tedy book_tag, někde to nejde, protože je vazba rovnocenná)
  • co je v PHP $camelCase, to je v SQL sloupec camel_case

Není to žádný zákon, přes který vlak nejede, ale ušetří ti to spoustu nervů a jednou se stejně budeš muset nějakým konvencím přizpůsobit (typicky v práci).

Editoval Šaman (12. 6. 2013 18:34)

před 6 lety

Michal III
Člen | 84

Dejme tomu, že na tuto konvenci bych časem mohl přistoupit. Horší by to však bylo s používáním prefixů. Většinou pracuji s větším množstvím tabulek a abych se v nich alespoň trochu vyznal, jednotlivým skupinám dávám stejný prefix. Abych pak ale měl prefix i v názvu sloupce s cizím klíčem, se mi však nelíbí (pe_group_id), natož aby se prefixy dostaly do m:n pomocných tabulek. Vím, že je to víceméně můj boj, ale vlastně by mě obecně zajímalo, jak to řeší ostatní, pokud mají větší počet tabulek.

před 6 lety

Šaman
Člen | 2275

Prefixy nepoužívám, ale chápu jejich užitečnost. Pokud by byly spojované tabulky všechny v jednom prexixu, tak by neměl být velký problém je přidat. Jen by to chtělo sepsat RFC, tedy se nejdřív dohodnout jak se vlastně budou chovat.

Jinak jak už jsem psal, cizí klíče nemají vztah na cizí tabulku, ale na proměnnou v entitě ($group bude v group_id). Ze které tabulky se má skupina načíst je věc mapperu (v LeanMapperu se to píše do závorky v anotaci).

před 6 lety

Tharos
Člen | 1042

Nad tímhle jsem se už také lehce zamýšlel… Můj pohled na věc:

To, že občas databáze nevypadá podle programátorovo představ, je prostě fakt. Důvody jsou různé, běžně za to programátor vůbec nemůže (návrh po někom „podědí“ atp.). Lean Mapper se s tím nyní vypořádat umí, ale pravdou je, že pokud je odklon od konvencí masivní, je s tím spojeno dost psaní navíc.

Moc se mi líbí, jak jsou konvence řešené v NotORMu. Chápu, že „nově příchozímu“ z toho možná jde chvíli hlava kolem, ale je to úžasně silný koncept, který umožňuje vyjádřit snad úplně vše (bohaté osobní zkušenosti). To, zda by některé metody v konvencích šly třeba lépe pojmenovat a učinit více intuitivní, je otázka.

Ať už by to vypadalo jakkoliv, rozhodně bych v Lean Mapperu ponechal možnost přímo v entitě a repositáři definitivně přetížit, co se na co mapuje (a jak se traverzuje mezi tabulkami).

Napadlo mě ale zavést pod to vrstvu ve stylu NotORM_Structure, které by se využívalo, pokud není mapování explicitně určené.

S NotORMem se mi dříve běžně stávalo, že jsem v databázi používal jiné konvence a pak jsem ještě někde měl nějakou výjimku z těch mých vlastních konvencí :). I tyhle výjimky se pak v NotORMu definovaly v NotORM_Structure, což mi nepřišlo úplně nejpraktičtější.

Líbilo by se mi, kdyby tedy Lean Mapperu šlo podstrčit nějaké vlastní obecné konvence (coby instanci nějaké speciální třídy) a kdyby programátor někde měl například nějakou vazební tabulku pojmenovanou ještě jinak, vyřešil by to už v anotaci (@hasMany(:myfancytable)).

Pak už je jen otázkou, jak nejhezčeji tohle naimplementovat a hlavně jak si instanci třídy s konvencemi předávat (chtěl bych, aby to byla normální instance s pár metodami pro konverzi). Tu instanci potřebují jak repositáře, tak i entity (přesněji řečeno je potřebují instance LeanMapper\Result zapouzdřené uvnitř entit).

Optimální by bylo, aby o té upravené konvenci v praxi programátor „vůbec nevěděl“. Tj. aby měl pouze v kontejneru nadefinované konvence jako službu a pak už by tam měl repositáře, do kterých by se konvence injektovaly pomocí auto-wiringu. Do entit se pak konvence dostanou stejným způsobem, jako například nyní DibiConnection. V podstatě by se pak to, zda se používají nebo nepoužívají vlastní konvence, lišilo jenom jedním řádkem v config.neon. :)

Tohle vidím jako věc k zamyšlení do verze 1.5.0. :) Určitě bych sem před tím dal RFC.

Editoval Tharos (13. 6. 2013 0:21)

před 6 lety

Vojtěch Dobeš
Člen | 1317

Takové konvence by se určitě dalo štosovat jako routy, takže nějaká IConventionCollection by mohla být užitečná.

před 6 lety

llook
Člen | 412

Já množná čísla pro názvy tabulek používám, těch nepravidelných je relativně málo a není problém je buďto uvést, nebo se smířit s hrubkou. Category zrovna mezi ně nepatří, cokoli končící na -y má množné číslo -ies.

Pro spojovací M:N tabulky mně osobně nejpřehlednější přijde konvence s _x_, např. articles_x_categories. Kolik lidí, tolik chutí a jmenných konvencí…

vojtech.dobes napsal(a):

Takové konvence by se určitě dalo štosovat jako routy, takže nějaká IConventionCollection by mohla být užitečná.

Ještě lépe ConventionsCollection implements IConvention.

před 6 lety

Tharos
Člen | 1042

@vojtech.dobes, @llook: To zní rozhodně zajímavě. Mohu poprosit o nějakou ukázku, jak by se s tím pracovalo? (Třeba v pseudokódu, fakt jenom nástřel.)


Moje myšlenky zatím tíhly k něčemu takovému:

<?php
/*
Názvosloví:
    table - název tabulky
    column - název sloupce v tabulce
    entityClass - třída entity (fully qualified)
    field - název položky v entitě
*/

interface IStructure
{

    public function getPrimaryKey($table);

    public function getTable($entityClass);

    public function getEntityClass($table);

    public function getColumn($entityClass, $field);

    public function getEntityField($table, $column);

    public function getRelationshipTable($sourceTable, $targetTable);

    public function getRelationshipColumn($sourceTable, $targetTable);

}
?>

Je to hodně inspirované NotORM_Structure, ale řeší to i názvy sloupců a troufale si myslím, že zde použité názvy jsou intuitivnější. :) Klidně dopíšu, jak by vypadala výchozí implementace (která by generovala stávající zadrátované konvence).

Čím víc nad tím přemýšlím, tím víc mi to celé připadá jako v praxi hodně přínosné. Kromě zmíněných problémů by to řešilo například i to, že když je nyní tabulka pojmenovaná nekonvenčně, v některých případech (v takových, kdy se na vztažnou entitu odkazují jiné entity) musí název té tabulky zaznít na více místech – v repositáři a pak ještě v anotacích každé entity, která na tu odchýlenou odkazuje /kromě mě :) si toho všiml například i Honza Tvrdík/. To je samozřejmě nepříjemné a tohle by to vcelku elegantně vyřešilo.

Editoval Tharos (14. 6. 2013 0:38)

před 6 lety

Tharos
Člen | 1042

Přispěji ještě jednou takovou zajímavostí, která s tímhle částečně souvisí… Lean Mapper neimplementuje koncept „předbíhání budoucnosti“. Předbíhání budoucnosti mi vždycky přišlo jako skvělá myšlenka, ale v NotORMu a v NDB to nikdy nebylo odlazené natolik, abych to mohl někde nechat na produkci. Běžně nám pak aplikace po promazání cache padaly na chybjících hodnotách – nastávaly zkrátka situace, ze kterých se knihovny neuměly zotavit. Nikdy jsem nezkoumal, proč přesně… A vzal jsem si z toho ponaučení, že tenhle koncept se naimplementovat pokoušet nemám. :–P

Nicméně pokud máme třeba článek a chceme fakt načíst jenom základní informace o něm, v Lean Mapperu to lze vyřešit vcelku snadno:

<?php

namespace Model\Entity;

/**
 * @property int $id
 * @property string $name
 * @property string $perex
 */
class ArticlePreface extends \LeanMapper\Entity
{
}

/**
 * @property string $text
 */
class Article extends ArticlePreface
{
}
?>
<?php

namespace Model\Filter;

class ArticleFilter
{

    public static function filterPreface(DibiFluent $statement)
    {
        $statement->removeClause('select')->select('[id], [name], [perex]');
    }

}
?>
<?php

namespace Model\Repository;

use Model\Filter\ArticleFilter;

class ArticleRepository extends \LeanMapper\Repository
{

    public function findAllPrefaces()
    {
        $statement = $this->createStatement();
        ArticleFilter::filterPreface($statement);
        return $this->createEntities($statement->fetchAll(), 'Model\Entity\ArticlePreface');
    }

    public function findAll()
    {
        return $this->createEntities(
            $this->createStatement()->fetchAll()
        );
    }

    private function createStatement()
    {
        return $this->connection->select('*')->from($this->getTable());
    }

}
?>

Jak jste z toho určitě správně pochopili, při volání $articleRepository->findAllPrefaces() se selectují jenom základní sloupce a nepřenáší se zbytečně celý obsah článků.

Praktické je, že na ten ArticleSummary se mohou odkazovat i jiné entity:

<?php

namespace Model\Entity;

use Model\Filter\ArticleFilter;

/**
 * @property int $id
 * @property string $name
 * @property ArticlePreface[] $articlePrefaces m:belongsToMany(:article) m:filter(ArticleFilter::filterPreface)
 * @property Article[] $articles m:belongsToMany
 */
class Author extends \LeanMapper\Entity
{
}
?>

Při volání $author->articles se načtou z databáze celé články včetně hlavních obsahů, zatímco při volání $author->articlePrefaces se načtou jenom instance ArticlePreface a select je optimální (vybírají se jenom sloupce id, name a perex).

Psal jsem ten kód z hlavy, a tak v něm jsou možná překlepy…


Proč o tom píšu je, že v důsledku jeden repositář může mít na povel více typů entit. Navržená metoda $structure->getEntityClass($table) ale může vrátit pouze jeden název. V tomto případě by tedy vrátila Model\Entity\Article a vytvoření Model\Entity\ArticlePreface by zůstalo i nadále jako ruční záležitost v repositáři a v anotacích (v nich jde ale o uvedení typu proměnné, a tak tam ArticlePreface zazní tak jako tak).

Editoval Tharos (13. 6. 2013 10:06)

před 6 lety

na1k
Člen | 289

@Tharos, knihovna se mi hodně líbí a mám v plánu ji brzo využít :)

A jelikož bude thread určitě silně zarůstat, moc bych prosil, jestli bys nemohl na věci jako například právě popsaný ArticlePreface vyčlenit sekci na webu. Něco jako best practises. Určitě jich bude časem víc a sledovat to na fóru je dost obtížné (nehledě na to, že v budoucnu může být něco outdated)

Editoval na1k (13. 6. 2013 0:30)

před 6 lety

Tharos
Člen | 1042

@na1k: Díky za kladný ohlas :).

Přesně tohle bych rád. Mimo jiné i proto se zde o řešení konkrétních problémů rozepisuji do takového detailu. Rád bych poté všechny tyhle ukázky dal pod jednu střechu na web.

S tímhle jsem trochu bojoval u NotORMu. Jakubovo návody, co jak vyřešit, byly všemožně po zdejším fóru a taky v Google skupině, takže je zpětně bylo skoro nemožné dohledat. Proto bych výhledově rád měl pro tohle sekci na webu a ideálně bych pak každý dotaz rovnou zodpovídal skrze ukázku na webu.

Dneska jsem aktualizoval roadmap. Myslím, že už vcelku definitivně vykrystalizovalo, co bude obsahem verze 1.4.0. Tu bych rád vydal v horizontu několika dní a pak bych vývoj dalších funkcí na chvíli zmrazil (setinkové verze s případnými bugfixy bych ale samozřejmě vydával) a zase posunul dokumentaci. Chci v ní minimálně zdokumentovat ty low-level záležitosti, filtry, novinky a také zavést tu sekci s ukázkami a „best practicies“.

před 6 lety

Tharos
Člen | 1042

Šaman napsal(a):

To getData() a getRawData() / getRowData() by bylo fajn.

Tak návrh se stal realitou. V develop větvi nyní existují metody getData, getRowData a getModifiedRowData. Věřím, že jsou jejich názvy intuitivní… Ukázky použití jsou v tomhle a tomhle testu.

Metoda getModifiedData by byla implementačně vcelku náročná a protože nemám v hlavě žádný use-case, na který by byla vyloženě zapotřebí, zatím jsem ji neimplementoval.

před 6 lety

Šaman
Člen | 2275

Díky, hned aktualizuji. Zatím mi to připadá nejpoužitelnější ORM, který jsem zkoušel (a bylo jich asi 6), mimojiné i kvůli dokumentaci.
Také mě nenapadá, kdy by se hodilo ModifiedData.

Ale vím, co by se ještě v souvislosti s tímto (a hlavně s formuláři hodilo).
Mít možnost předat entitě pole dat (setData(array $data)). S tím, že pokud by entita nějaký klíč neznala, tak by asi měla zařvat (nebýt moc benevolentní, pak by taková metoda sváděla k wtf). Zároveň by se tím vyřešilo i to, že nyní nejde vytvořit nová entita na základě pole dat (ale jen na základě existující Row).

Na objektové straně ORMu by se tedy pracovalo vždy s názvy properties, getData a setData.
Na relační straně by se používaly názvy sloupců, getRowData, create($row) a dalšími nízkoúrovňovými funkcemi, které by byly zvenku neviditelné.

před 6 lety

Tharos
Člen | 1042

Skoro si říkám, jestli jsi nepřehlédl metodu assign(), která je dokonce i zdokumentovaná. :)

Dělá přesně to, co popisuješ. Přijímá pole ve formátu název property => hodnota nebo instance a vyhazuje výjimku, pokud některá z položek neexistuje (lze ji ale kdyžtak vy-ignorovat tím whitelistem).


Nicméně, to s tím konstruktorem zatím naimplementované nebylo a je to rozhodně dobrý nápad. :) Jelikož metoda assign už existuje, byla maličkost upravit konstruktor entity tak, aby přijímal i pole a interně pak tu metodu assign použil.

Enjoy v develop větvi. :) Pro inspiraci je tu kdyžtak i test.

Editoval Tharos (13. 6. 2013 21:16)

před 6 lety

Tharos
Člen | 1042

Etch napsal(a):

Mimochodem ještě by se mi častokrát hodila jedna věc, ale nejsem si úplně jistý, jestli by se to hodilo přímo do „základu“ LeanMapperu.

$book->compare($bookOriginal);

Jak jsem dneska upravil ty getData a spol. metody, tak jsem si na Tebe vzpomněl. :)

Nyní je implementace jednoduchá:

class BaseEntity extends LeanMapper\Entity
{

    public function compare(LeanMapper\Entity $entity)
    {
        return md5(json_encode($this->getRowData())) === md5(json_encode($entity->getRowData()));
    }

}

Příklad použití:

/**
 * @property int $id
 * @property string $name
 * @property string $pubdate
 */
class Book extends BaseEntity
{
}

$firstBook = new Book(array(
    'name' => 'First book',
    'pubdate' => '2013-06-14',
));

$secondBook = new Book(array(
    'name' => 'First book',
    'pubdate' => '2013-06-13',
));

$firstBook->compare($secondBook); // returns false

$secondBook->pubdate = '2013-06-14';

$firstBook->compare($secondBook); // returns true

Pokud bys chtěl klonovat, důležité je nedělat mělkou kopii, protože ta by obsahovala identický LeanMapper\Row. Takže změny v originální entitě by se pak projevily i v klonu. Je zapotřebí udělat hlubokou kopii, například pomocí unserialize(serialize()) fíglu:

$firstBook = new Book(array(
    'name' => 'First book',
    'pubdate' => '2013-06-14',
));

$secondBook = unserialize(serialize($firstBook)); // deep copy

$firstBook->compare($secondBook); // returns true

$secondBook->name = 'Second book';

$firstBook->compare($secondBook); // returns false

Akorát je aktuálně kvůli přítomnosti DibiConnection v útrobách problematické serializovat již persistované entity… Pokud bys klonování vážně potřeboval, asi by bylo vhodné nadefinovat si clone metodu v BaseEntity, která by se s tímhle poprala (mělo by to být řešitelné).

Editoval Tharos (14. 6. 2013 1:13)

před 6 lety

Šaman
Člen | 2275

Ahoj, nevím přesně v čem je problém, ale poslední aktualizace nefunguje pod Nette.
Při startu session vyběhne hláška: You cannot serialize or unserialize DibiConnection instances.

//edit: Tak záhada vyřešena. Problém byl v tom, že jsem při vytváření identity po přihlášení předával jako třetí parametr $user->getData(). A ono se to pokouší uložit so session a tedy i serializovat, což nejde, protože getData obsahuje Dibi objekty. Je nutno použít getRowData(), nebo, raději, předávat identitě skutečně jen ta data, která chci zobrazovat v debugbaru.

Příspěvek tu nechávám jen pro případ, že by se to někomu ještě stalo, ať nad tím nemusí hodinu bádat.


Jiná věc:

Myslím, že už jsem to tu psal v souvislosti s ->count(). Mohl bys, prosím, používat nějaký ArrayObject místo obyčejných polí? Nette to tak dělá a nevím jak ostatní, já si na to velmi rychle zvykl. Takže se snažím o tohle, což nejde:

<?php
$userData = $user->getData();
$email = $userData->email;   # Trying to get property of non-object
$email = $userData['email']; # Projde, ale je s tím víc psaní a není to moc sexy
?>

//Edit: Tak jsem na to narazil znovu, tentokrát z druhé strany. Metodě assign() nejde předat ArrayObject, ani ArrayHash, protože striktně vyžaduje pole. Možná by bylo lepší používat méně striktní rozhraní ArrayAccess, IteratorAggregate a Countable.


Ještě jiná věc:
Bylo by super, kdyby bylo místo entit možné předávat jen jejich id. Opět typicky při práci s formuláři.

<?php
$authorId = $form->values->author; # $authorId = 5
$book->author = $authorId;         # InvalidValueException: Only entites can be set via magic __set on field with relationships.
$book->author = $this->authorRepo->get($authorId); # projde, ale přijde mi to zbytečně ukecané, navíc potřebuji repozitář
?>

Koukal jsem, že se ověřuje existence vazby mezi oběma objekty, ale LeanMapper by si mohl tuto entitu nejdřív sám vytvořit a pak s ní pracovat jako doposud. Kromě toho, že je s tím méně psaní, by si to mohl vytvořit přímo z db bez předávání repozitářů..


A tohle souvisí částečně zase s těmi array → object, tentokrát na úrovni kolekce.
Při vytváření formulářů potřebuji často z nějaké kolekce nadělat páry (->fetchPairs()). Teď nevím, jak na to, kromě toho, že si v repositáři vytvořím vlastní metody. Ideální by ale bylo mít možnost vytvořit páry z už existující kolekce. Taktéž by mohla kolekce obsahovat nástroje pro řazení výsledků a jejich limit. Nikoliv na úrovni databáze, ale hotových entit (tedy možnost řazení např podle vypočítané hodnoty, nebo hodnoty z číselníku).
Možná ale tohle umí filtry, ty ještě neznám.

Editoval Šaman (14. 6. 2013 10:29)

před 6 lety

David Ďurika
Člen | 341

Zdravim,

chcem sa spitat, ako spravit multi insert ?

insert into table (…,…,…) values (…), (…), …

dakujem za pomoc

před 6 lety

Jan Tvrdík
Nette guru | 2550

@Tharos: Já zase doufám, že tam zůstanou obyčejná pole a žádné pseudo-pole objekty tam nebudou, protože žádný z nich nefunguje pořádně.

před 6 lety

Tharos
Člen | 1042

@achtan: Řešil bych to následovně:

/**
 * @property int $id
 * @property string $name
 * @property string|null $web
 */
class Author extends LeanMapper\Entity
{
}

class AuthorRepository extends LeanMapper\Repository
{

    /**
     * @param Author[] $authors
     * @throws InvalidArgumentException
     */
    public function createUsingMultiInsert(array $authors)
    {
        $values = array();
        foreach ($authors as $author) {
            if (!($author instanceof Author) or !$author->isDetached()) {
                throw new InvalidArgumentException('Invalid set of authors given.');
            }
            $values[] = $author->getModifiedRowData();
        }
        $this->connection->query('INSERT INTO %n %ex', $this->getTable(), $values);

    }

}

Domysli si use statementy, namespace atp… Použít to poté lze následovně:

$authors = array(
    new Author(array(
        'name' => 'First author',
    )),
    new Author(array(
        'name' => 'Second author',
    )),
    new Author(array(
        'name' => 'Third author',
    )),
);

$authorRepository = new AuthorRepository($connection);

$authorRepository->createUsingMultiInsert($authors);

To vygeneruje SQL:

INSERT INTO [author] ([name])
VALUES ('First author') , ('Second author') , ('Third author')

Má to jedinou nevýhodu – multi insert Ti nevrátí ID vložených záznamů, takže se Ti poté bude vytvořené entity obtížně nastavovat jako „už persistované“ (metoda entity markAsCreated). Pokud ale jen potřebuješ dostat více dat do databáze najednou a chceš jen využít settery entity pro validaci atp., je to takhle optimální.

Editoval Tharos (14. 6. 2013 16:14)

před 6 lety

David Ďurika
Člen | 341

@Tharos super dik!

před 6 lety

Tharos
Člen | 1042

@Šaman, @Jan Tvrdík: Přiznám se, že také nejsem přílišný fanda do těchto kolekcí. Důvod je prostý – PHP obsahuje skvělou sadu funkcí pro práci s poli, skrze které lze výsledek hravě limitovat, setřídit, filtrovat a já nevím co vše ještě úplně podle libosti. Obzvláště pak v PHP 5.3 s anonymními funkcemi. Zavedením jakékoliv kolekce se těchto funkcí zříkáme. Taková kolekce by pro mě musela mít nějakou zásadní přidanou hodnotu, aby mi to převážilo, čeho se vzdávám (a tím rozhodně není, že namísto $data['name'] budu moci psát $data->name).

Šaman napsal(a):

Jiná věc:

Myslím, že už jsem to tu psal v souvislosti s ->count(). Mohl bys, prosím, používat nějaký ArrayObject místo obyčejných polí? Nette to tak dělá a nevím jak ostatní, já si na to velmi rychle zvykl. Takže se snažím o tohle, což nejde:

<?php
$userData = $user->getData();
$email = $userData->email;   # Trying to get property of non-object
$email = $userData['email']; # Projde, ale je s tím víc psaní a není to moc sexy
?>

Tady bych to skutečně nerad měnil na jakoukoliv kolekci. Důvod je prostý – pokud Ti kolekce vyhovují více, lze používat takovouto BaseEntity:

class BaseEntity extends LeanMapper\Entity
{

    public function getData()
    {
        return new ArrayObject(parent::getData());
    }

    public function getRowData()
    {
        return new ArrayObject(parent::getRowData());
    }

    public function getModifiedRowData()
    {
        return new ArrayObject(parent::getRowData());
    }

}

Podle chuti pak můžeš použít ArrayObject, Nette\ArrayHash nebo cokoliv jiného.

//Edit: Tak jsem na to narazil znovu, tentokrát z druhé strany. Metodě assign() nejde předat ArrayObject, ani ArrayHash, protože striktně vyžaduje pole. Možná by bylo lepší používat méně striktní rozhraní ArrayAccess, IteratorAggregate a Countable.

Jo, tohle by mohlo být fajn. :) Souhlasím, že instancí, která implementuje zmíněná rozhraní, by to pohrdat nemuselo. :) Tohle pravděpodobně naimplementuji. Edit: I když ještě to zvážím, ono zde lze zase použít iterator_to_array($values)


Ještě jiná věc:
Bylo by super, kdyby bylo místo entit možné předávat jen jejich id. Opět typicky při práci s formuláři.

<?php
$authorId = $form->values->author; # $authorId = 5
$book->author = $authorId;         # InvalidValueException: Only entites can be set via magic __set on field with relationships.
$book->author = $this->authorRepo->get($authorId); # projde, ale přijde mi to zbytečně ukecané, navíc potřebuji repozitář
?>

Tohle mi přijde mírně na hraně a myslím, že by podpora pro podobné prakticky neměl být přímo v jádru Lean Mapperu. Prostě přiřazovat $book->author = $authorId je WTF a proti principům objektové strany ORM. A to já jsem dost benevolentní ;).

Každopádně, tohle má velmi jednoduché řešení. Opět si stačí vytvořit BaseEntity (je zapotřebí aktuální verze z develop větve) a v ní mít:

class BaseEntity extends LeanMapper\Entity
{

    public function setById($field, $id) // this is only draft, it doesn't handle exceptional states!
    {
        $property = $this->getReflection()->getEntityProperty($field);
        $relationship = $property->getRelationship();
        $this->row->{$property->getColumn() . '_id'} = $id;
        $this->row->cleanReferencedRowsCache($relationship->getTargetTable(), $relationship->getColumnReferencingTargetTable());
    }

}

Pak budeš moci provést následující:

$bookRepository = new BookRepository($connection);

$book = $bookRepository->find(1);

echo $book->author->name; // prints some name

$book->setById('author', 2);

echo $book->author->name; // prints another name

Jsem si jist, že kdybys tohohle chtěl masivně využívat, půjde to zařídit v BaseEntity tak, abys s tím měl v konkrétních entitách už minimum psaní.

A tohle souvisí částečně zase s těmi array → object, tentokrát na úrovni kolekce.
Při vytváření formulářů potřebuji často z nějaké kolekce nadělat páry (->fetchPairs()). Teď nevím, jak na to, kromě toho, že si v repositáři vytvořím vlastní metody. Ideální by ale bylo mít možnost vytvořit páry z už existující kolekce. Taktéž by mohla kolekce obsahovat nástroje pro řazení výsledků a jejich limit. Nikoliv na úrovni databáze, ale hotových entit (tedy možnost řazení např podle vypočítané hodnoty, nebo hodnoty z číselníku).
Možná ale tohle umí filtry, ty ještě neznám.

Filtry tohle neumí, ty slouží k dolazení vygenerovaného SQL dotazu těsně před tím, než je poslán na databázi.

Já bych třeba tohle řešil buďto v repositáři, anebo možná přes nějakou array_* funkci. Myslím, že se v PHP málo používají a je to škoda ;).

V Lean Mapperu jedna kolekce určitě vznikne a to kvůli té plánované persistenci jednoduchých M:N vazeb. Ta bude ale nízkoúrovňově lazená a koncový programátor s ní běžně nepřijde vůbec do styku.


Díky, že Lean Mapper takhle proklepáváš a dělíš se o to, na co jsi narazil!

Editoval Tharos (14. 6. 2013 16:16)

před 6 lety

Tharos
Člen | 1042

Uvedu jenom takovou triviální alternativu k fetchPairs:

$books = $bookRepository->findAll();

$pairs = array_map(function ($book) {
    return $book->name;
}, $books);

Klíče tvoří ID entit (o to se stará protected createEntities v LeanMapper\Repository).

před 6 lety

Šaman
Člen | 2275

Aha, logiku to má. Jediné, za čím si stále stojím je to akceptování ArrayObjectu (přes nějaké rozhraní).
U všeho jsi mi ukázal, jak si to mohu implementovat do BaseEntity (nebo jinam) a já to asi udělám – ty jsi připravil nejjednodušší a nejčistější základ a mě nic nebrání přizpůsobit si ho podle svého.
Kolekci výsledků si ale rozhodně zobjektuju, to mi dokonce připadá nejdůležitější z těch věcí, co jsem napsal. Do kolekce mohu přidávat speciální metody pro postprocessing už hotových entit. (Typicky potřebuji řadit výpis – třeba knih – podle žánru, který je v číselníku. Buď bych si musel připravit vlastní orderBy, kde si ten číselník přijoinuji, nebo mi to bude řadit podle gendre_id, nikoliv však podle abecedy. Ty mi ale připravíš pole už hotových entit, na kterých není problém si ještě dodatečně přeházet pořadí, klidně i podle vypočítané property, která v db vůbec není. A to je typická úloha pro kolekce.) Další metoda, která tam u mě bude, bude to vytvoření fetchPairs.

Offtopic: Zajímal by mě názor více programátorů na ArrayObject / ArrayHash / ArrayList, či jiné podobné. Davidův názor tuším, podle masivního nasazení v Nette. Na názor Honzy Tvrdíka (nebo to byl HosipLan?), který je kritizoval, jsem už na fóru narazil. Ale ještě jsem neviděl nějaký rozbor v čem přinášejí problémy, jak je používají jiní programátoři apod. Mě osobně se zatím líbí – vytvořit z nich pole, abych mohl pracovat s array_* funkcemi je jednoduché (aspoň v případě jednorozměrných struktur ArrayHash a ArrayList).
Nicméně, pokud by se o tomto někdo rozhodl sepsat víc, než jen link na nějaké vhodné čtivo, bylo by dobré pořešit to mimo tohle vlákno.

Editoval Šaman (14. 6. 2013 17:46)

před 6 lety

Filip111
Člen | 244

Ahoj, asi jsem úplně nepochopil co se tu řeší s array přístupem, ale od ORM si slibuji, že nebudu muset používat array access nikdy (při práci s entitou nebo result setem).
Zvlášť, když jsem si na to v Nette tak zvykl.

Jinak se mi moc líbí, jak jak se LeanMapper pěkně vyvíjí a fandim mu. Doufám, že si získá oblibu stejně jako dibi u mnoha lidí, což mu zajistí i širší podporu :)

před 6 lety

Šaman
Člen | 2275

Taky bych od ORMmu čekal na výstupu objekty. Teď je to částečně ARM, tedy array-relation mapper.
Ale Tharos taky nikde netvrdí, že je to ORM, ale mapper. A ORM by se nad tímto mapperem měl nechat udělat celkem snadno. Protože už by se neřešilo mapování, jen zobjektování výsledků.

před 6 lety

hrach
Člen | 1810

Mel bych dotaz na @JanTvrdik: znas Petrovo ORM. Proc bych mel pouzit toto, a ne to jeho. (Ted neresme vzhled zdrojaku, ale treba dlouhodobou odladenost na desitkach projektu, …). Diky :)

před 6 lety

Tharos
Člen | 1042

@Šaman, @Filip111: Pro mě osobně to ORM znamená především to, že na jedné straně jsou entity, na druhé relace. :) To, jestli skupinu entit drží pohromadě array nebo nějaká kolekce, osobně považuji za druhořadé.

Každé řešení má svá pro i proti a jelikož co lidí, tolik chutí, pokusil jsem se to vyřešit diplomaticky. Lean Mapper bude i nadále nativně používat array, ale přidal jsem do Entity i Repository metodu createCollection, kterou lze v nějaké vlastní BaseEntity a BaseRepository přetížit a tím kompletně upravit, co Lean Mapper v takovém případě vrací. Podle chuti to může být ArrayObject, Nette\ArrayHash nebo nějaká úplně vlastní kolekce.

Jak snadno to lze upravit je vidět v tomhle a tomhle testu.

Editoval Tharos (15. 6. 2013 0:06)

před 6 lety

Jan Tvrdík
Nette guru | 2550

hrach wrote: Mel bych dotaz na @JanTvrdik: znas Petrovo ORM. Proc bych mel pouzit toto, a ne to jeho.

Obrácená argumentace by byla snazší (PORM pro oproti Lean Mapperu mraky možností navíc), zkusím ale i tak shrnout nevýhody Petrova ORM (dále jen PORM) oproti Lean Mapperu:

  1. PORM nemá pořádnou dokumentaci, jen kratičký QS.
  2. U PORM si nikdo není moc jist jeho výkonem (ale v praxi jsme s tím nikdy neměli problém).
  3. Kvůli tomu, že PORM zachovává zpětnou kompatibilitu (kde je to jen trochu možné, případné rozdíly mezi verzemi jsou pečlivě zdokumentovány), obsahuje spoustu historického balastu. Vnitřní návrh nepoužívá DI.
  4. Vyčlenění samostatné vrstvy pro mappery si nevyhnutelně vyžádá o něco více psaní, i když PORM podle mě vyžaduje jen nutné minimum.

před 6 lety

Šaman
Člen | 2275

@Tharos: Díky :) Čekal jsem, že budu muset přetěžovat createEntities(), tohle je luxus podpora.

@hrach: U mě je hlavní důvod nepoužívat PORM ta dokumentace. Mimochodem totéž paralyzuje NDatabase. LeanMapper (LM) má základní dokumentaci sepsanou, vidím tomu do zdrojáků (LM je výrazně menší než PORM) a jako zadní vrátka mám možnost psát čisté SQL, když si s nějakým složitějším spojením nevím rady. (Oproti NDb, kde při použití SQL dotazu ztrácím výhodu pracovat s výsledkem jako s objekry typu Row.)

Na druhou stranu, co si pamatuji, tak PORM měl možnost vracet buď kolekci typu pole (hotové entity) anebo nějaký DibiDataSource, se kterým se dalo dělat i po načtení z repozitáře všechno možné a teprve až v šabloně se z toho fetchnuly entity. Tady se asi limity a řazení bude muset řešit těmi očekávanými filtry, jinak mi getAll() z megatabulky zabije apače.

Editoval Šaman (15. 6. 2013 3:33)

před 6 lety

Šaman
Člen | 2275

Našel jsem jednu nevychytávku. Pokud nastavím některé property nějakou třídu, tak mi z databáze musí přijít instance stejné třídy (neprojde potomek).

Konkrétně jsem narazil u \DateTime – dibi mi do $row dodá \DibiDateTime (což je potomek \DateTime) a mapper mi vyhodí chybu ve třídě Entity: Property ‚timeXxx‘ is expected to contain an instance of ‚DateTime‘, instance of ‚DibiDateTime‘ given.

Rád bych v projektu používal standardní DateTime, jeho Netťácký a Dibi potomek snad byl potřeba jen v PHP 5.2. (?) Vypadá to ale, že budu muset kvůli kompatibilitě používat DibiDateTime i uvnitř apliakce.

Stránky: Prev 1 2 3 4 … 23 Next