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

peter.z
Člen | 37

V prvom rade by som chcel autorovi vzdat velku pochvalu a velke podakovanie! Rozhodol som sa do mojho CMSka implementovat LeanMapper a az na par drobnosti som s nim velmi spokojny! Avsak.. mam par navrhov, ktore sice momentalne riesim dedicnostou – udalosti. LeanMapper momentalne sice disponuje funkciami ako beforeCreate, beforeUpdate… Ja by som navrhol pridat este – beforeDelete / afterDelete.

Priklad pouzitia:
Mam model, ktory sa stara o galerie. Kazda galeria ma svoj vlastny priecinok, kde su fotky. Ked odstranim galeriu, potrebujem aby sa mi odstranil aj jej priecinok – cize tu by bolo vhod pouzit bud beforeDelete alebo afterDelete (vysledok bude taky isty).

Este ma napadlo aj afterPersist, ale jediny, ktory ma napadol by bolo invalidovanie cache…

před 6 lety

Tharos
Člen | 1042

castamir napsal(a):

hlavně ať to není case sensitive ať půjde udělat oba zápisy ;o)

@property bool $published = true
@property bool $published = TRUE

Neboj, mám rozum :).


Šaman napsal(a):

S defautlní hodnotou NULL u nullable položek souhlasím (pokud explicitně neuvedu jinou defaultní hodnotu).

Já taky, ale pouze na úrovni databáze. :) Něco podobného už se řešilo zde. Mně prostě jako nejlepší přijde následující chování (zejména proto, že je nejméně náchylné k chybám):

/**
 * @property int $id
 * @property bool|null $active = null
 */
 class Author extends Entity
 {
 }

 $author = new Author;
 $author->active === null; // true

/**
 * @property int $id
 * @property bool|null $published
 */
 class Book extends Entity
 {
 }

 $book = new Book;
 $book->published === null; // throws InvalidArgumentException (Missing 'published' value for requested row.)

S tím, že pokud nově vytvořenou $book bezprostředně budu persistovat, do databáze se hodnota null může dostat, ale jen díky defaultní hodnotě nullable sloupce v databázi (ve vygenerovaném SQL insertu zkrátka sloupec published vůbec nebude zmíněn).


Defaultní hodnoty u DateTime atp.

Z proběhlé diskuze vyplývá řada poznatků:

  • Je to hodně o DateTime. :) Existuje ještě nějaký jiný případ smysluplného užití?
  • Troufám si tvrdit, že výskytů DateTime u entit v běžné aplikaci není tolik, aby byla jejich inicializace pomocí initDefaults() vyloženě pruda.
  • To, že je položka typu DateTime, neznamená, že bude obsahovat instanci přesně této třídy. Může obsahovat i instanci nějakého potomka (často třeba DibiDateTime). Tohle činí méně atraktivní vyloženě jednoduché zápisy ($published = 'yesterday' atp.), protože by byly pouze polovičatým řešením. Navíc co kdyby se namísto volání new mělo využít například nějaké statické továrny?

Já ještě přihodím:

  • Tohle není validní ani v samotném PHP (a pro mě osobně je to dost směrodatné):
class Author
{

    private $born = new DateTime;

}

Resumé je pro mě osobně umožňovat v anotaci nastavení defaultní hodnoty pouze pro základní typy a na vše ostatní je tu metoda initDefaults(). U polí bych pro začátek umožnil nadefinovat pouze prázdné pole (btw. kdy se vůbec něco takového hodí?).

před 6 lety

Tharos
Člen | 1042

peter.z napsal(a):

velke podakovanie!

Není zač, jsem moc rád, že knihovna slouží. :)

udalosti. LeanMapper momentalne sice disponuje funkciami ako beforeCreate, beforeUpdate… Ja by som navrhol pridat este – beforeDelete / afterDelete.

Priklad pouzitia:
Mam model, ktory sa stara o galerie. Kazda galeria ma svoj vlastny priecinok, kde su fotky. Ked odstranim galeriu, potrebujem aby sa mi odstranil aj jej priecinok – cize tu by bolo vhod pouzit bud beforeDelete alebo afterDelete (vysledok bude taky isty).

Este ma napadlo aj afterPersist, ale jediny, ktory ma napadol by bolo invalidovanie cache…

Tohle je rozhodně dobrý nápad, ale rád bych skutečně dobře rozmyslel API. Viděl bych to jako feature do verze 1.6.

Tak trochu problém je, že já sám pro něco takového využití nemám, protože já zpravidla s repositáři pracuji v nějaké servisní vrstvě a když je například zapotřebí po odstranění záznamu z databáze něco smazat i na disku, řeším to v rámci jedné metody v té servisní vrstvě.

Mně tenhle styl práce vyhovuje, ale samozřejmě chápu, že kdekdo má model navržený jinak a události by mu usnadnily život…

Co tím ale chci říct: je otázkou, jestli jsem ten pravý pro návrh API téhle funkcionality. :–P


Určitě bych byl rád, aby to bylo maximálně jednoduché. Napadlo mě mít prostě dostupné události a k těm mít možnost registrovat (rozuměj štosovat) callbacky (podobně fungují třeba Nette formuláře).

Současné metody beforeCreate(), beforeUpdate() a beforePersist() mají trochu jiný účel. Ty slouží k úpravě dat bezprostředně před jejich odesláním do databáze (jako například zde). Tyhle metody bych rozhodně zachoval, ale vedle nich bych zavedl tzv. události pro realizaci čehosi, co bych nazval vedlejšími efekty.

Mohlo by to fungovat třeba takhle?

$repository = new Repository($connection, $mapper);
$repository->addEventCallback(Repository::EVENT_BEFORE_UPDATE, function () {
    // do something here
});

S tím, že by pak v základním repositáři mohla existovat i nějaká protected metoda initEventsCallbacks() ve stylu initDefaults(), která by umožnila nastavit nějaké výchozí callbacky u konkrétního (poděděného) repositáře. Callbacky by tak bylo možné nastavit jak pomocí této metody, tak i dynamicky za běhu.

Bylo by to takhle dobře použitelné? Řešilo by to vše potřebné? Jaké události by měly existovat? A jaké parametry by se měly předávat těm callbackům?

Editoval Tharos (7. 7. 2013 23:23)

před 6 lety

Šaman
Člen | 2275

Nenapadá mě jiný případ inicializace složitějšího typu, než DateTime. Na druhou stranu, DateTime je poměrně často používaný a i jednoduchá inicializace samotného DateTime ('now', '1.1.2001') by mi pomohla (není to však kritické, mám jich v aplikaci odhadem 5). Kdo chce jinou instanci, ten už si to pořeší přes metodu. Pokud to nepůjde, není to nic, co by způsobovalo problém. Pokud by to bylo hodně potřeba, tak přidat se to dá kdykoliv později.

Tvé řešení s NULL je akceptovatelné (ač malinko WTF), protože se defaultní hodnota dá jednoduše dopsat do anotace. Ten malý wtf faktor vzniká tím, že nemám-li $book->published === null;, tak jak je možné, že se mi tak zapsalo do db? (Existuje databáze, kde nulable hodnota bez předané hodnoty hodí chybu?). ORM/mapper by nás měl IMHO odstínit od db a jejích specifik.  A pokud danou hodnotu nemohu číst, jak je možné ji zapsat? Použití bych viděl spíš u vazeb: if($this->author === null). Jak by se vlastně chovala isset?
Nicméně nedělá mi problém tam defaultní hodnoty psát explicitně, pak je při jejich existenci wtf nulové.

Editoval Šaman (8. 7. 2013 0:44)

před 6 lety

Tharos
Člen | 1042

Dneska mám takový vtipný den… Rozhodl jsem se vyprofilovat Lean Mapper na větších datech. Pořídil jsem si MySQL databázi Employees a pustil se do práce. Po nejrůznějších experimentech jsem zanesl do knihovny několik málo nenápadných optimalizací, které ale dokáží v mnoha případech výrazně zvýšit výkon.

Když už se v Lean Mapperu nenacházela žádná další vyloženě úzká hrdla, napadlo mě ještě alespoň bleskově porovnat, jak si vede ve srovnání s jinými knihovnami. Protože výkonu Lean Mapperu velmi věřím, zaměřil jsem se na srovnání s NotORM a s Nette\Database (s cache i bez ní).

Nechce se mi věřit vlastním očím a už hodiny přemýšlím, jestli náhodou něco nedělám špatně…


Zadání: Vypište z Employees databáze pět set zaměstnanců a pro každého uveďte všechny jeho výplaty (vztah belongs to many) a také všechna oddělení, do kterých patří (vztah has many). Zkrátka získejte z databáze takovýto přehled.

Doba trvání:

Lean Mapper ~ 1.6 s
NotORM ~ 6.2 s
NDB ~ 16.7 s
NDB s cache ~ 25.6 s

Není moc pracné zjistit, v čem je zakopaný pes. Stačí se podívat na následující výstupy z WinCacheGrind seřazené sestupně podle Total Self (pro účely profilování jsem musel snížit limit zaměstnanců na 200, protože záznam z NDB s pěti sty zaměstnanci se mi ani nepodařilo kvůli nedostatku paměti otevřít :–P).

NotORM i Nette\Database trpí tím, že jakmile jsou vystavené většímu množství dat, dokáží je sice získat efektivně z databáze (pomocí IN), ale pak s nimi provádějí příliš mnoho operací. NotORM_Result->quote je voláno 80 410 krát, ActiveRow->accessColumn dokonce 140 011 krát!

Ptám se proč tolikrát? Nevidím do vnitřností těch knihoven a samotného by mě to vcelku zajímalo… Jde o velmi rychlé metody, ale při takovém množství zavolání jde prostě jakýkoliv výkon do kytek. A největší kuriozitou je: chcete zlepšit výkon Nette\Database? Vypněte si cache! :)


Motivován touto zkušeností připravím nějaký ucelenější benchmark, protože mě to celé až vyděsilo. Pokud jsem ještě zvýšil limit zaměstnanců (třeba na 10 000), nůžky se ještě více rozevřely. Je možné, že je Lean Mapper při větším množství dat o řád výkonnější než NotORM?

Mile rád zveřejním kompletní kód benchmarku pro oponenturu. Skoro doufám, že mám v metodice nějakou chybu!

Editoval Tharos (8. 7. 2013 21:50)

před 6 lety

peter.z
Člen | 37

Ucelom beforeUpdate, create, persist rozumiem :) S navrhovanym riesenim udalosti by som bol spokojny :) ako som pisal uz vyssie, uvital by som nasledovne udalosti: EVENT_AFTER_PERSIST (tu by som predaval entitu ako parameter), EVENT_DELETE (znova by som predal entitu, ktora sa ide zmazat alebo ktora sa zmazala (?)).

před 6 lety

David Grudl
Nette Core | 6806

Taky mě překvapilo, jak je NDB šíleně pomalé a kolikrát se accessColumn volá. Asi by to chtělo zapracovat na optimalizaci.

před 6 lety

bazo
Člen | 625

k tym udalostiam, co pouzit doctrine event manager, symfony event dispatcher alebo kdyby events? naco vymyslat vlastne api?

alebo len opajcnut 1–2 triedy, to by malo stacit

před 6 lety

enumag
Člen | 2128

@Tharos: Zajímalo by mne jak by ve tvém srovnání obstála Doctrine. ;-)

před 6 lety

Majkl578
Moderator | 1379

enumag napsal(a):

@Tharos: Zajímalo by mne jak by ve tvém srovnání obstála Doctrine. ;-)

Zkus sám:
https://github.com/…es-doctrine2

Mně to na laptopu doběhlo cca za 2.5 sekundy (měřil jsem dobu běhu celého php scriptu přes time v konzoli). ;)

před 6 lety

Filip111
Člen | 244

@Tharos: můžeš do výkonostního testu zahrnout taky samotné dibi? (nebo je to hlavně o ORM?)
Docela by mě zajímala režije samotného LeanMapperu.

před 6 lety

Tharos
Člen | 1042

@bazo: Rešerši určitě provedu. Drobný problém všech zmíněných řešení je, že si s sebou „táhnou“ nějaký velký framework (Doctrine 2, Symfony 2 nebo Nette 2). Kdyby\Events je závislé na Nette. Lean Mapper je závislý pouze na dibi.

Takovým Kdyby\Events (které je mi z uvedených variant rozhodně nejsympatičtější) bych se ale možná nechal vydatně inspirovat, protože jej považuji za skutečně povedené. Ještě před tím bych ale rád provedl důkladnou rešerši, k emu všemu by to všechno bylo vlastně dobré a jaké by mělo být API, aby nejlépe plnilo svůj účel.

@Majkl578: Moc díky za bleskurychlou implementaci. Výsledek je moc hezký!

Když jsem benchmark spustil u sebe, narazil jsem bohužel na to, že u sebe na lokále nemám zprovozněnou Memcache a když jsem ji nahradil za FilesystemCache, cache se vygenerovala, ale další běhy pak trvaly kolem 50 s… Což samozřejmě znamená, že jsem asi ještě něco nepodchytil v nastavení.

On je to celé takové hodně vratké srovnávání. Jak jsem psal, až budu mít chvilku, připravím lepší benchmark, který si bude moci každý snadno spustit u sebe (dodám k tomu i tu databázi – stačí malá podmnožina Employees). Stávající výsledky napříč různými počítači a různým nastavením (s či bez Memcache) jsou strašně zavádějící.

Každopádně z Tvé ukázky je vidět, že Doctrine s Memcache si rozhodně nevede vůbec špatně – osobně jsem čekal mnohem horší výsledek.

@Filip111: Jo, čisté dibi doplním snadno a rád. :) Jen navrhni, jakou bys preferoval „strategii“ dotazů. Totiž jestli to mám vyřešit přes joiny (kde se bude přenášet v tomto případě nemalé množství redundandních dat), jestli mám pak třeba pro zajímavost použít fetchAssoc, anebo jestli mám sestavit IN dotazy, aby byla vidět vyloženě režie Lean Mapperu… Vyber si. :)

Troufám si tvrdit, že režie Lean Mapperu nad dibi je díky optimalizaci hodně malá. Kdykoliv profiluji od nejproblematičtějších částí, velmi brzy narážím na metody z dibi. Třeba takové DibiResult->normalize dokáže v součtu kus času sežrat. Občas si říkám, jestli se nepokusím zoptimalizovat některé části dibi :).

Často je cítit rozdíl mezi DibiFluent a klasickým zápisem, který je rychlejší. Toho lze v Lean Mapperu využít – v repositářích lze běžně vše zapisovat pomocí klasického zápisu. Já jsem také nedávno upravil kód knihovny tak, aby se interně co nejvíce používal klasický zápis (různé inserty při persistenci atp.).

před 6 lety

Majkl578
Moderator | 1379

@**Tharos**: Zkus tu definici memcache keše nahradit za:

use Doctrine\Common\Cache\FilesystemCache;
$cache = new FilesystemCache(__DIR__ . '/temp');

Při druhém běhu by to mělo být OK (u mě vpodstatě stejný čas jako s memkeší).

Ale samozřejmě souhlasím, že je to celkem vratké srovnání. Nicméně sám jsem byl potěšen výsledkem, kdy se zdá, že Doctrine je rychlejší než NDB (což mě ostatně vlastně ani nepřekvapuje) a dokonce i než NotORM. S přihlédnutím ke komplexnosti daných knihoven Doctrine jasně vede.

před 6 lety

Tharos
Člen | 1042

Už se teď tomu srovnávání nemohu víc věnovat (strávil jsem tím vším dnes až příliš času), ale jakmile budu mít chvilku, připravím lepší benchmark i s veřejným kódem. Hlavně se chci podívat na Nette\Database, protože mi tam prostě něco nehraje. Říkám si, jestli jsem tam nenarazil třeba na nějaký bug…

Použil jsem stable verzi, protože v master větvi je nové API se kterým neumím a zatím není zdokumentované :–P. Vím, že v master větvi je dost věcí opravených, ale předpokládám, že takhle základní použití není zabugované ani v současné stable verzi:

function write($value, $indent = 0) {
    echo str_repeat(' ', $indent), $value, "\n";
}

$ndb = new Connection('mysql:dbname=employees', 'root', 'XXXXXX');

$fileStorage = new FileStorage(__DIR__ . '/temp');

$ndb->setDatabaseReflection(new DiscoveredReflection($fileStorage));
$ndb->setCacheStorage($fileStorage);

foreach ($ndb->table('employees')->limit(500) as $employee) {
    write("$employee->first_name ($employee->emp_no)");
    write('Salaries:', 5);
    foreach ($employee->related('salaries') as $salary) {
        write($salary->salary, 10);
    }
    write('Departments:', 5);
    foreach ($employee->related('dept_emp') as $relationship) {
        write($relationship->ref('departments', 'dept_no')->dept_name, 10);
    }
}

Editoval Tharos (8. 7. 2013 23:24)

před 6 lety

Filip111
Člen | 244

@Tharos:
Právě že „strategií“ dotazů jsem si nebyl jistý, proto ten otazník :)
Samotná výkonnost dibi mě momentálně tolik netrápí, resp. už se určitě v minulosti řešila jinde. Proto by asi bylo lepší zaměřit se pouze na režii LeanMapperu a proto tedy dotazy s IN?
(dávám zase s otazníkem, protože se přiznám, že jsem zatím nesledoval dotazy generované LeanMapperem – ještě jsem ho nenasadil v ostré aplikaci)

před 6 lety

hrach
Člen | 1810

Používat stable verzi Nette\Database doporučuje –20 (mínus 20) z 100 programátorů (tj. nedoporučují to dokonce i ti co nette database nepoužívají).

Co se týká výkonnosti, tak o „problémech“ vím. Největším problémem je „předpovádání“ budoucnosti, které je příčinou toho všeho:

  • potřebuje celý resultset indexovaný dle sloupců → nemožnost použít unbuffered, zároveň vysoká paměťová náročnost
  • neustálý „touch“ na pouižité sloupce
  • v případě cachování taky režie cache

Velkým krokem bude nové api v 2.2, které tutu funkcionalitu oddělí, a bude tak možné nabídnout výrazně rychlejší api, které bude dělat to stejné co všichni ostatní – vybere všechny sloupce. A to je další aspekt tvého testování: kdybys vybírat jenom „id a title“, (což se většinou nad velkými listy děla, standardně pro číselníky), tak to bude minimálně srovnatelné.

před 6 lety

David Grudl
Nette Core | 6806

Úplně nejlepší by bylo udělat repo, kde by byl benchmark všech zmíněných DB layerů.

Mimochodem, v Jakubově srovnání NotORM a dibi vyšlo dibi hůř z triviálního důvodů: delimitovalo (obalovalo do zpětných uvozovek) identifikátory, tj. názvy tabulek a sloupců. NotORM to nedělalo a právě o to bylo rychlejší. Takový fakt pak vrhá trošku jiný pohled na výsledky.

NDB by měla být stejně rychlá jako dibi, nejslabším místem (jak už Tharos zmiňoval) je funkce normalizeRow a ta by se mohla poladit. NDB\Table (nemám rád zaměňování NDB a NDB\Table) dosud žádnou rychlostní optimalizací neprošla, což je vzhledem k vývoji pochopitelné, ale rozhodně ji dostane. Nicméně nejde o vrstvu, která je primárně stavěná na načítání 10.000 záznamů.

před 6 lety

Tharos
Člen | 1042

Jakmile budu mít chvíli, ten benchmark připravím.

Ještě si posvítím na položené dotazy – to jsem zatím nedělal (spouštěl jsem benchmark z konzole a vypisoval jen data). Myslím, že v NDB byla dříve chyba, která způsobovala položení dotazu při každém průchodu cyklem. Takže si říkám, jestli jsem zrovna nenatrefil na něco takového…

Bohužel teď nejsem u PC, na kterém bych to mohl ověřit.

před 6 lety

David Grudl
Nette Core | 6806

Btw, je možné, že mám 40× rychlejší počítač než ty? Dávám to za 0.39 sekundy. (Na PHP 5.4.x, na 5.3.x je to 0.7 sekundy).

$connection = new Nette\Database\Connection('mysql:dbname=employees', 'root', 'xxx');

$dao = new Nette\Database\SelectionFactory(
    $connection,
    new Nette\Database\Reflection\DiscoveredReflection($connection)
);

$time = -microtime(TRUE);

foreach ($dao->table('employees')->limit(500) as $employe) {
    echo "$employe->first_name ($employe->emp_no)\n";
    echo "Salaries:\m";
    foreach ($employe->related('salaries') as $salary) {
        echo $salary->salary, "\n";
    }
    echo "Departments:\n";
    foreach ($employe->related('dept_emp') as $department) {
        echo $department->dept->dept_name, "\n";
    }
}

$time += microtime(TRUE);
echo 'time: ',$time;

před 6 lety

Tharos
Člen | 1042

Tak jsem spustil logování dotazů a záhada je konečně vyřešena. Takhle se Nette\Database ze stable verze zeptá v kódu, který jsem uvedl

Což hned vysvětluje ten extrémní počet volání několika metod.

Když jsem z benchmarku vypustil ten výpis departments, kde se ten bug projevuje, hned se NDB zařadila někam mezi Lean Mapper a NotORM:

Lean Mapper ~ 1.32 s
NDB s cache ~ 1.45 s
NDB ~ 1.66 s
NotORM ~ 3.23 s

Takže konec záhady a mrzí mě, že jsem způsobil takový povyk pro nic…

Jedinou vadou na kráse je představa, že na tenhle bug narazím někde v produkci a nebudu mít na co aktualizovat, protože NDB ve větvi 2.1 má koukám dost pozměněné API :(.

Editoval Tharos (8. 7. 2013 23:32)

před 6 lety

David Grudl
Nette Core | 6806

Tak to už vypadá lépe :-) Můžeš zkusit změřit ještě Doctrine na svém HW a ideálně to doplnit o paměťové nároky via memory_get_peak_usage()?

To dost pozměněné API mě zajímá, můžeš sdělit, na co jsi zásadního narazil? (Tedy krom Connection::table vs. SelectionFactory::table). Ale připomněl jsi mi, že jsem zapomněl vydat Nette 2.0.11, čas to napravit.

BTW, NDB je v dev verzi 6× rychlejší než v 2.0.x, dobrá práce Hrachu!

před 6 lety

Šaman
Člen | 2275

Rozhodně nechci zaožit ani offtopic diskuzi, ani flamewar, ale nemohu si nepostesknout, proč není v Nette raději Dibi, než Ndb.

Kromě toho, že Ndb je a asi ještě chvíli bude nejzabugovanější částí Nette, která se navíc strmě vyvíjí v době, kdy Nette se snad už ustaluje, tak pro nováčky je to nepoužitelné z důvodu absence dokumentace. A nejhorší je, že to vypadá dobře, člověk si stáhne Sandbox a začne na tom vyvíjet a za nějakou dobu zjistí, že není schopen položit specifické dotazy, které v prostém SQL umí bez problému.

Kromě toho, že LeanMapper dělá přesně co chci, tak mu musím poděkovat za návrat k Dibi. Rvaní vlasů při zásekách nad databází se snížilo alespoň o polovinu.

A Davidovi chci poděkovat za to, že Dibi neumřelo, ale nedávno se opět aktulizovalo pro kompatibilitu s Nette 2.1.

před 6 lety

hrach
Člen | 1810

Predpokladam, ze je jeste necherry-picknuta nova syntaxe pro backjoin (pac je BC break), ktera ale v Tharosove testovacim scriptu neni vyuzita.

před 6 lety

Tharos
Člen | 1042

David Grudl napsal(a):

BTW, NDB je v dev verzi 6× rychlejší než v 2.0.x, dobrá práce Hrachu!

Chceš říct, že v dev verzi přijdu o výkonnostní prvenství? :) Tak to tu DibiResult->normalize asi nezoptimalizuješ, viď? ;)

Tak to už vypadá lépe :-) Můžeš zkusit změřit ještě Doctrine na svém HW a ideálně to doplnit o paměťové nároky via memory_get_peak_usage()?

Doctrine určitě do toho benchmarku, který bych rád připravil, zahrnu. S benchmarkem od Majkla578 se mi nedaří ani s FilesystemCache dostat pod nějakých 40 s, ale to je tím, že s Doctrine prostě neumím a přiznám se, že se mi to nechce teď řešit… Mně osobně stačí jeho výsledek – zkrátka Doctrine nijak nezaostává.

To dost pozměněné API mě zajímá, můžeš sdělit, na co jsi zásadního narazil?

No hele, když jsem nahradil stable Nette za dev verzi, postupně na mě vybafly následující hlášky:

Deprecated: Nette\Database\Connection::setDatabaseReflection() is deprecated; use setSelectionFactory() instead.
Deprecated: Nette\Database\Connection::setCacheStorage() is deprecated; use setSelectionFactory() instead.
Nette\Database\Connection::table() is deprecated; use SelectionFactory::table() instead.

Nabyl jsem dojmu, že teď se řada věcí řeší přes SelectionFactory, o které ale vůbec nic nevím. :( Navíc hláška, že mám namísto setDatabaseReflection() použít setSelectionFactory(), mně, přiznám se, moc nepopostrčila…

Editoval Tharos (9. 7. 2013 1:14)

před 6 lety

David Grudl
Nette Core | 6806

Na mém HW to vypadá takto:

vrstva čas paměť
NDB v 2.0.11 bez cache: 1.66 s 10 MB
NDB v 2.0.11 s cache: 2.35 s 8 MB
NDB v dev bez cache: 0.39 s 9 MB
NDB v dev s cache: 0.39 s 8 MB
Doctrine2 bez cache 1.35 s 19 MB
Doctrine2 s cache 1.31 s 18 MB
NotORM bez cache 0.91s 5 MB
NotORM s cache 0.92 s 4 MB
Lean Mapper 0.30 s 9 MB

A když navíc srovnám délku kódu potřebnou k vyřešení úkolu, není snad potřeba existenci NDB víc obhajovat.

(Poznámka: rychlost měření ovlivňují faktory, které vůbec nečekáte, třeba nastavení date_default_timezone_set() může zvýšit výkon o 10 %).

před 6 lety

Tharos
Člen | 1042

Tak pokud bys měl chuť, můžeš do srovnání zahrnout i Lean Mapper a NotORM. :) Tady máš kód, použití je velmi jednoduché (jenom nastav připojení k DB v indexu, přepni na požadovanou metodu a nech to proběhnout).

Alespoň by to vše bylo z jedné mašiny.

Editoval Tharos (9. 7. 2013 0:58)

před 6 lety

Majkl578
Moderator | 1379

David Grudl napsal(a):

Na mém HW to vypadá takto:

Zkoušel jsi to na 5.5 a s OPcache nebo bez? Zrovna opcode keš může u velké vs. malé knihovny znamenat zajímavý rozdíl (zejména paměti).

Ale připomněl jsi mi, že jsem zapomněl vydat Nette 2.0.11, čas to napravit.

před 6 lety

David Grudl
Nette Core | 6806

Majkl578 napsal(a):

Zkoušel jsi to na 5.5 a s OPcache nebo bez? Zrovna opcode keš může u velké vs. malé knihovny znamenat zajímavý rozdíl (zejména paměti).

Pod 5.5 je kupodivu Doctrine o 30 % pomalejší a OPcache nejak nepomáhá…

před 6 lety

David Grudl
Nette Core | 6806

Tharos napsal(a):

Tak pokud bys měl chuť, můžeš do srovnání zahrnout i Lean Mapper a NotORM.

Jasně, díky za kód, je to tam :-) Jseš vítěz!

Docela by bylo zajímavé změřit Lean Mapper nad NDB místo dibi (nemyslím teď NDB\Table). Ale nemám moc čas to zkoušet předělávat, byť nemyslím, že by to bylo složité.

před 6 lety

Tharos
Člen | 1042

David Grudl napsal(a):

Jasně, díky za kód, je to tam :-) Jseš vítěz!

Díky! :) Tak uvidíme, jak dlouho mi prvenství vydrží ;).

Docela by bylo zajímavé změřit Lean Mapper nad NDB místo dibi (nemyslím teď NDB\Table). Ale nemám moc čas to zkoušet předělávat, byť nemyslím, že by to bylo složité.

To by opravdu nebylo složité. Tipuji, že výsledný čas by pak byl úplně nejlepší. :) Lean Mapper by mělo být možné kompletně překlopit na jakoukoliv knihovnu, která podporuje „fluent“ sestavování dotazů (NDB, NotORM, FluentPDO…). Nicméně já do tohohle teď čas určitě také investovat nebudu, protože mně prostě dibi ve všech směrech vyhovuje.

Editoval Tharos (9. 7. 2013 10:20)

před 6 lety

Tharos
Člen | 1042

Abych trochu změnil téma…

Lean Mapper má od neděle jednu zajímavou novou funkcionalitu: výrazně lepší podporu pro single table inheritance. Věřím, že příklad vydá za tisíc slov.

Nosnou úpravou je přidání druhého parametru do metody getEntityClass v mapperu. Nově tedy mapper může o relevantní entitní třídě rozhonout i na základě konkrétních dat, které má entita zapouzdřit. Užití je velmi široké.

Doposud bylo nutné single table inheritance ošetřovat na úrovni metod v entitě a bohužel často ještě duplicitně v repositáři. Nyní lze vše elegantně řídit z jednoho místa.


Možná jste si všimli, že tím typ v anotaci trochu ztrácí váhu. Je tomu skutečně tak – v položce nadefinované pomocí @property Product $product se ve výsledku může objevit PC, Printer, TV

Možná vás potěší, že Lean Mapper v tomhle případě dbá na konzistenci a nedovolí, aby se v dané položce objevilo něco jiného, než instance podle type hintu anebo instance nějaké odvozené třídy. Pokud se například do výše uvedené položky pokusím pomocí mapperu dostat instanci entity Customer, která nedědí od Product, Lean Mapper vyhodí následující výmluvnou výjimku:

Inconsistency found: property 'product' is supposed to contain an instance of 'Model\Entity\Product' (due to type hint), but mapper maps it to 'Model\Entity\Customer'. Please fix getEntityClass() method in mapper, property annotation or entities inheritance.

Enjoy

před 6 lety

Šaman
Člen | 2275

Člověče, ty stíháš přidávat důležité fičury rychleji, než je stačím zkoušet :)
Skvělá práce! Přesně takhle jsem si ten entití polymorfismus představoval.

Editoval Šaman (9. 7. 2013 13:01)

před 6 lety

David Grudl
Nette Core | 6806

Dal jsem ty včerejší testy do repa https://github.com/dg/db-benchmark, můžeme na to navázat a sadu testů rozšířit. Jo, jak jsem oddělil NotORM do samostatného adresáře, tak hodně klesla paměťová náročnost, tak jsem to v tabulce upravil.

před 6 lety

Tharos
Člen | 1042

Skvěle zpracované. Díky za práci, kterou sis s tím dal!

Já už tím pádem ten benchmark předělávat nebudu. Beztak jsem to chtěl taky jenom rozdělit a maximálně ještě přidat třeba nějaké zápisy… Namísto toho radši připravím ten pull request s čistým dibi.

před 6 lety

Filip111
Člen | 244

@Tharos:
Zase trochu z jiného soudku – vícejazyčné entity.

Neměl jsem v posledních dnech moc času, takže jsem se zase vrátil k testování v minulosti navrhovaného řešení, tedy entita s translatable prvky a translatable repository, které automaticky pomocí filtru načte entitu z tabulky hlavičky i textů, resp. uloží do těchto tabulek.

Entita:

/**
* @property int $id
* @property Gallery|null $gallery m:hasOne(gallery)
* @property bool $status
* @property string $lang m:translatable
* @property string $title m:translatable
* @property string $text m:translatable
*/
class Content extends TranslatableEntity {
}

TranslatableRepository samo o sobě dokáže s entitou pracovat dobře a dělá to co od něj očekávám. Problém ale nastává u relací, konkrétně u téhle entity

@property Gallery|null $gallery m:hasOne(gallery)

Pokud načtu entitu Content a použiji $content->gallery, LeanMapper načte data přímo z tabulky gallery a na nějaké moje translatable repository se úplně vykašle. Výsledkem je tedy objekt Gallery definovaný jako translatable, ale v $row jsou načtená data jen z tabulky gallery.

Ono je to samozřejmě naprosto logický chování – bohužel jsem si to na začátku neuvědomil a pro mě je naprosto nežádoucí. Má představa je načtení celé konzistentní translatable entity Gallery (tedy s pomocí GalleryRepository, resp. TranslatableRepository).

Nějaký nápad co by se s tím dalo dělat nebo jsem skončil?

Díky.

před 6 lety

Tharos
Člen | 1042

Důvod, proč se při čtení dat z tabulky gallery nepřijoinuje překlad, je, že v té definici chybí potřebný filtr, který by to zajistil.

Podívej se úplně na konec tohoto mého příspěvku, konkrétně na to, jak je nadefinovaná položka pages a jak se s ní poté pracuje.


Neskončil jsi :), ono tohle má dokonce i více možných řešení. Já dnes bohužel odjíždím a až do neděle budu mít velmi omezený přístup k internetu, ale po mém návratu tohle můžeme třeba soukromě nějak více probrat. Z optimálního řešení bych pak připravil ukázku.

Také bych zvážil, jestli by Lean Mapper podobným případům neuměl ještě nějak elegantněji vyjít vstříc. I když jsem přesvědčen, že současné možnosti jsou velmi slušné, jen prostě zatím nejsou úplně zdokumentované…

před 6 lety

Filip111
Člen | 244

@Tharos:
Tak to mam opět radost :)

Použití filtru v anotaci jsem úplně přehlédl a samotný filtr jsem pojal trochu jinak. Tvůj (PageFilter) jsem zrušil a vytvořil si metodu v TranslatableRepository, protože se mi nechtělo pro každou entitu definovat vlastní filtr.

public function joinTranslation(DibiFluent $statement, $lang) {
    $statement->leftJoin($this->translationTable)
        ->on('['. $this->translationTable .'.id] = ['. $this->getTable() .'.id]')
        ->where('[lang] = %s', $lang);
}

Volal jsem ho tedy ručně v repository, když bylo třeba.

$this->joinTranslation($statement, $lang);

Při použití m:filter a definování konkrétního filtru pro Gallery entitu se mi načte celá entita správně, jen mi to boří koncept, kde nepotřebuji mít pro každou entitu vlastní filtr, ale generuje se automaticky podle anotace v repository

/**
 * @translationTable(gallery_text)
 */
class GalleryRepository extends TranslatableRepository {

}

Ještě to promyslím.

před 6 lety

Tharos
Člen | 1042

Finta je, že ten filtr, byť je to jednoduchá staticky volaná utility třída (připomínám, že je krajně nevhodné, aby udržovala nějaký stav!), nemusí být úplně tupá. Dokážu si představit filtr, který bude chytře využívat __callStatic a vystačí mi pro překlad úplně všech entit v celé aplikaci. :) Prostě když nastavím položce filtr m:filter(Translator::translateGallery), volaný filtr podle názvu metody inteligentně rozpozná, že má prijoinovat překlady z tabulky gallery atp. Takový filtr se pak dá zrecyklovat úplně všude.

Dokážu si představit řešení fakt na pár řádků a po svém návratu v neděli Ti ho klidně napíšu. :)

To, jak lze nyní překlady vyřešit, z mého pohledu trpí pouze jedním kosmetickým nedostatkem. Pokud je celá položka nadefinovaná jenom pomocí anotace, filtr se musí nějak dozvědět, jaký jazykový překlad má vlastně přijoinovat, a proto v té mé ukázce volám to $author->getPages('cs') (samozřejmě by šlo volat třeba i $author->getPages($author->lang)). Pokud bys chtěl, aby se automaticky připojil překlad podle jazyka autora, nevyhnul by ses nutnosti nadefinovat tu položku pomocí metody. Ono by to bylo také velmi jednoduché, ale prostě o něco méně pohodlné to je…

Po neděli se těm překladům ještě rád budu věnovat.

Editoval Tharos (10. 7. 2013 23:55)

před 6 lety

Filip111
Člen | 244

@Tharos:
Zkusím do té doby taky něco udělat.

Jak zmiňuješ $author->getPages('cs'), tak podobné použití je žádoucí.
Můžu třeba v šabloně potřebovat vypsat vedle sebe názvy v několika jazycích, takže třeba následující kód smysl má.

$author->getPages('cs')->title
$author->getPages('en')->title
$author->getPages('de')->title

V praxi je tomu ale obvykle jinak – jazyk se nastaví jednou a dost. Tedy chtěl bych zkusit vymyslet jak jazyk nastavit jednou (asi v TranslatableRepository) a pak už ho vůbec nepředávat, aby fungovalo i následující:

$author->pages->title

Ještě jsem uvažoval zda by nebylo užitečný taky přiřazování

$author->getPages('cs')->title = 'nazev';
$author->pages->title = 'nazev';

ale někde jsi psal, že Entita neumí persistovat sama sebe, tedy takovouhle úpravu nedokáže uložit a je potřeba pracovat s repository (v tomhle případě s AuthorRepository a PagesRepository).

před 6 lety

Šaman
Člen | 2275

Zkouším používat typ ENUM a po projití zdrojáků se mi zdá, že to bere jedině konstanty.
Osobně bych preferoval spíše kdyby to akceptovalo property typu pole, nebo metodu, která pole vrací.

Nebo jaký je best practise při přaci s výčtem? Rád bych ho použil místo číselníku (např. pohlaví: „muž“, „žena“), ale nevím jak elegantně vyřešit to, aby v databázi byl nějaký kód („m“, „f“) ale proměnná by mi vracela slovní název. Výhoda by byla ve chvíli, kdy by se tento název změnil, stačilo by změnit překládací tabulku (asociativní pole). Chtěl bych tedy pracovat s tímto:

<?php
protected $sex = array(
        'm' => "muž",
        'f' => "žena"
    );
?>

Konstanty nejsou úplně vhodné, protože hodnot může být daleko víc (kolik stavů může mít projekt? navržený, v jednání, zadaný, pracuje se, pozastaven, odevzdaný, fakturovaný, hotový, stornovaný, ..?)

před 6 lety

Jan Tvrdík
Nette guru | 2550

@Šaman: Je-li hodnot více, je lepší místo enumu použít extra tabulku. Chceš-li vracet přeloženou hodnotu (tj. např. místo m můž), přidej si na to např. extra getter.

před 6 lety

Šaman
Člen | 2275

Extra tabulka by byla fajn, ale nechci zavádět další entitu. Časem si to chci pořešit tak, aby mi jednoduše repository nadřízené entity spravoval i číselník, ale zatím to řeším takto:

<?php
/**
 * Entita událost
 *
 * @property User $user m:hasOne
 *
 * @property int         $id
 * @property string      $title
 * @property string|NULL $description
 * @property string      $category <--- toto dělá WTF
 */
class Event extends Entity
{

    private $categories = array
        (
            'nameday' => "svátek",
            'birthday' => "narozeniny",
            'wedding' => "svatba",
            'christmas' => "vánoce",
            'death' => "úmrtí",
            'annivers' => "výročí",
            'event' => "jiná událost"
        );


    public function getCategory()
    {
        return $this->categories[$this->row->category];
    }

    public function setCategory($category)
    {
        $categoryCode = array_search($category, $this->categories);
        $this->row->category = $categoryCode;
    }

}
?>

Funguje to krásně a s minimem kódu (ještě jsem nezkoušel zápis, ale o to teď nejde)
Kód: $event->category mi vrací správně český název. Ale pokud chci, aby mi to IDE napovídalo, přidám ten označený řádek do anotace a najednou mi to můj getter ignoruje.
Tharos to píše v dokumentaci – Priorita definic, takže najít háček netrvalo dlouho. Ale stejně si myslím, že by zápis $entity->foo a $entity->getFoo() měl udělat pokaždé totéž (zvlášť, když jsme si na to zvykli i v Nette).
Navíc mě teď nenapadá jak vytvořit virtulání property category, která volá můj getter a setter.

@Tharos: Je nějaký důvod, aby při volání $entity->foo nezkoušel LeanMapper nejprve najít getFoo() a teprve potom to zpracovat podle anotace? Přeci jen když mi anotace přestane stačit, tak přidám metodu a očekávám, že se mi použije místo defaultního chování.

Editoval Šaman (13. 7. 2013 20:29)

před 6 lety

Tharos
Člen | 1042

Tak už jsem zase zpátky. :) Vezmu to postupně…

@Šaman:

Je i není (k tomu nějaký důvod). Prapůvodně v tom byl jistý záměr – aby šlo explicitně ovlivnit, jestli se zavolá metoda nebo použije magický přístup… Ale s odstupem mi to přijde trochu nešťastné. Zejména proto, že to svádí k tomu mít nadefinovanou nějakou položku kombinovaně pomocí anotace i metody, což je „bad practice“. Pravidla, kterými se Lean Mapper v takovém případě řídí, jsou sice jasně daná (viz ta dokumentace), ale totálně neintuitivní… Říkám si, jestli to z dokumentace časem nevyhodím…

Doporučení zní:

  • Položky definujte pomocí anotací, pokud není nutné něco vyloženě extra.
  • K položkám přistupujte pomocí magické __get metody ($book->name), dokud položka nemá nadefinovaný filtr, kterému potřebujeme při volání předat nějaké parametry ($book->getTopBooks(5) – vrátí pět nejlépe hodnocených knih).
  • Položky definujte buďto pomocí anotace, anebo pomocí metody. Nikoliv pomocí obojího. Od verze 1.5 bude dostupný příznak m:hintOnly, který umožní, že IDE bude při přístupu přes __get korektně napovídat i položky ve skutečnosti nadefinované pomocí metod.

Tobě by měl problém vyřešit právě ten příznak m:hintOnly. :) Než bude vydaná verze 1.5, je nutné tu anotaci odstranit (a tím bohužel přijít o hint v IDE – sorry a budu to samozřejmě řešit).

Pokud tedy zůstaneš u svého návrhu… To, co řešíš, se z mého pohledu podobá překladům a já osobně bych zvážil i nějaké řešení ala jazykové překlady.

Pro úplnost už jen uvedu: to, že lze k položkám nadefinovaným pomocí anotace přistupovat pomocí metody, má ten význam, že jedině tak lze předat parametry případným filtrům ($author->getTopBooks(5)).

před 6 lety

Šaman
Člen | 2275

Díky, na to hintOnly v minulých příspěvkách jsem zapomněl.
Jinak nejsou to úplně překlady, je to skutečně číselník (bez čísel, lol). Jde o to, abych do databáze nezapisoval česká slova (která se navím mohou někdy změnit), ale jen jejich ID, nebo kód (který jsem si v tomto případě zvolil anglický jednoslovný). Prostě zkouším jak nejčistěji a s minimem kódu pořešit všechny možné typy, kategorie, pohlaví, apod. Tohle se mi zatím docela líbí, pokud samozřejmě není požadavek na správu kategorií. Pak z nich musím udělat entitu a přesunout je do samostatné tabulky.
Pokud ale požadavek na jejich editaci a přidávání není (typicky pohlaví, nebo předem známé typy), tak se mi nechce kvůli nim vytvářet entitu i repozitář.

před 6 lety

Tharos
Člen | 1042

@Filip111: Během dovolené mi v hlavě uzrálo jisté řešení a rád bych jej zde nadhodil jako další RFC…

Filtry vznikly jako jednoduchý koncept (který jsem já osobně původně používal pouze pro řazení, limity a taky join v entitě přes více tabulek) a až zde na fóru vykrystalizovalo, na co všechno je lze ještě použít (překlady, closure table atp.).

Přiznávám, že od začátku mám jistý vnitřní problém s jejich „statičností“. Dokážu si využití statických metod zde obhájit – filtry jsou běžně jednoduché utility třídy neudržující žádný stav, použití je velmi jednoduché… ale celé to prostě není moc hezké. A taky je to limitující: pokud by například filtr potřeboval ke svému filtrování nějakou jinou službu, nelze mu ji nijak „hezky“ předat (pomocí auto-wiringu atp.).

Napadlo mě provést v Lean Mapperu následující úpravy, které by sice znamenaly drobné BC breaky, ale za mě by za to jednoznačně stály…


Úplně bych zrušil statickou povahu filtrů.

Lean Mapper by zavedl vlastní LeanMapper\Connection, které by bylo poděděné DibiConnection a obohacovalo by jej o správu filtrů:

$connection = new LeanMapper\Connection(...); // same as DibiConnection initialization

function translate(Fluent $statement, Entity $entity = null, Property $property = null, $lang = null)
{
    ...
}

/*function translate($args) {
    if (func_num_args() === ...) { // variant with polymorphism
        ...
    }
}*/

class LimitFilter
{

    public function limit(Fluent $statement, $limit = null)
    {
        ...
    }

}

$limitFilter = new LimitFilter;

$connection->registerFilter('limit', array($filter, 'limit')); // equals $connection->registerFilter('limit', array($filter, 'limit'), Connection::FILTER_ARGS_BASIC);

$connection->registerFilter('translate', 'translate', Connection::FILTER_ARGS_RICH);

V aplikaci by tím vznikly pojmenované filtry. LeanMapper\Connection by vytvářelo vlastní LeanMapper\Fluent, což by bylo „fluent“ odvozené od DibiFluent a umělo by navíc následující:

$statement->where(...)->applyFilter('translate', 'cs');

$statement->where(...)->applyFilterRichWay('translate', $entity, $property);

$statement->where(...)->applyFilter('limit', 10);

Nyní už jen rozvedu, co znamenají technicky ty Connection::FILTER_ARGS_RICH a applyFilterRichWay a za okamžik se dostanu k tomu, k čemu je to dobré.

Pokud filtr zaregistruji bez příznaku (nebo s výchozím příznakem Connection::FILTER_ARGS_BASIC), entita při načítání souvisejících entit předá filtru při jeho spouštění jako parametr $statement a následně všechny další parametry, které byly použity při přístupu k položce pomocí get metody (například při volání $author->getBooks(2) může pomyslný filtr mít hlavičku public function limit($statement, $count) a obdrží právě statement a hodnotu 2.

Pokud filtr zaregistruji s příznakem Connection::FILTER_ARGS_RICH (což je zapotřebí jen výjimečně), entita filtru předá: statement, mateřskou entitu (tj. tu, která načítá související entity), reflexi položky, přes kterou se přistupuje, a za tím všechny další parametry, které byly použity při přístupu k položce pomocí get metody (viz předchozí odstavec).

Pokud filtr zaregistruji s příznakem Connection::FILTER_ARGS_RICH, mohu jej volat pomocí applyFilter i applyFilterRichWay (entita jej ale v takovém případě vždy volá pomocí applyFilterRichWay). Pokud jej zaregistruji bez tohoto příznaku (nebo použiji výchozí příznak Connection::FILTER_ARGS_BASIC), mohu jej volat pouze pomocí applyFilter.

Tohle celé se dá velmi výhodně využít třeba v tom překladovém filtru: při volání z entit (pomocí applyFilterRichWay) lze jazyk požadovaného překladu získat z mateřské entity, při ručním volání z repositáře (pomocí applyFilter) se jazyk požadovaného překladu uvede explicitně.

Význam

  • Úplně se zruší statická volání a odstraní se související nedostatky.
  • Filtry se dostanou do DBAL vrstvy, kde jim to bude absolutně nejvíce slušet (vždyť ony jen modifikují fluent dotaz).
  • Významně se zvýší možnosti filtrů. Například půjde napsat úplně triviální překladací filtr o pár řádcích, který umožní následující:
$book = $bookRepository->find(5, 'cs');

echo $book->author->name; // vypíše český název autora

echo $book->getAuthor('en')->name; // vypíše anglický název autora, jazyk překladu je explicitně uveden

Takovýto filtr bude možné zrecyklovat úplně všude. Na tohle bych připravil nějakou komplexní ukázku ke stažení.

  • Díky pojmenování se zkrátí příznaky m:filter a zpřehlední anotace (m:filter(limit) namísto například m:filter(LimitFilter::limit) ještě spolu s use).
  • Jako filtr půjde zaregistrovat cokoliv, co pojme call_user_func jako $callback, anebo instance třídy Closure. Opět významné rozšíření možností.
  • Usnadní se použití filtrů v repositářích ($connection->select('*')->from('...')->applyFilter(...)).

Zpětné nekompatibility

  • Pro ty, kteří filtry nepoužívají, půjde o triviální úpravu. Pouze v config.neon nahradí DibiConnection za LeanMapper\Connection.
  • Ti, kteří filtry používají, je budou muset eventuálně převést na tenhle nový systém. Což by ale IMHO také nemělo být až tak náročné…

Jestli to je z mého popisu absolutně nepochopitelné, rovnou to v nějaké veřejné feature větvi naimplementuji a vysvětlím lépe na ukázkách. :)

Editoval Tharos (14. 7. 2013 23:34)

před 6 lety

castamir
Člen | 631

K těm enumům… když už si ten enum definuju pomocí konstatnt (např m:enum(TYPE_*)), bylo by super, kdybych mohl získat právě to (associativní) pole všech těch konstant, dalo by se to ihned předhodit např. selecboxu nebo radiolistbuttonku ;-)

A na ten rozsáhlejší příklad filtrů se docela těším… už jsem na jejich použití párkrát narazil a bylo to nemastné neslané :p. Takto se s tím bude pracovat snad mnohem líp.

před 6 lety

echo
Člen | 134

Ad Filtry:
Co místo $author->getBooks(3) použít syntax $author->getBooksFluent()->limitFilter(3)->onlyPublishedFilter()->fetchAll().

Snad by šlo vypustit i to fetchAll() implementací ArrayIterator.

Filtry by byly umístěny v samostatné třídě, která by přijímala v konstruktoru DibiFluent. Třída by byla „na jedno použití“ a její název by se získával z příznaku u anotace.

před 6 lety

castamir
Člen | 631

Nevím jak ostatní, ale já jsem tak strašně líný, že ani m:hasOne se mi psát nechce v případě, kdy mám jednoduchou vazbu. Bylo by možné, aby byla m:hasOne defaultní v případě, že daná třída dědí od Entity a jiná vazba není explicitně určena?

Jinak vypozoroval jsem, že když chci naplnit např. formulář daty z entity, zavolám si

$form->setDefaults($entity->getData());

Jenže v případě, že mám u entity něco podobného

/*
 * @property Address|null $address m:hasOne(address)
 */

a entita nemá nastavenou adresu, pak se mi provádí podle mě zbytečný dotaz do databáze:

SELECT `address`.*
FROM `address`
WHERE `address`.`id` IN (NULL)

Editoval castamir (15. 7. 2013 13:25)

před 6 lety

Tharos
Člen | 1042

Ad zbytečný dotaz

Na tohle před pár dny narazil i besanek.

Tohle je rozhodně good point a v každém případě kód upravím tak, aby Lean Mapper takovéto bezvýznamné dotazy vůbec nekladl.

Editoval Tharos (15. 7. 2013 14:15)

před 6 lety

Šaman
Člen | 2275

@castamir: Chvíli jsem s tou defaultní m:hasOne polemizoval, ale nakonec mi to připaddlo docela v pohodě. Prostě je to atribut typu třída, stejně jako string nebo int tam bude jednou, jen není v téže tabulce, ale v závislé. Jenom any to nezesložitilo parsování.

Na druhou stranu to by se také dala automaticky detekovat vazba m:hasMany, pokud chceme vrátit pole entit.

No a pokud bychom chtěli jít ještě dál, tak podle toho, jestli daný sloupec mám, či nemám v databázi, tak to bude buď has:.., nebo belong:...

Pokud jsou násobné vazby z obou stran, musí mezi nimi být ještě vazební tabulka.

Takže znovu polemizuji s tím, jestli není lepší si vazby dopsat (není to moc znaků) a mít všechno explicitně vyjádřené. Ledaže by skutečně šlo odtranit explicitní vypisování vazeb u všech typů vazeb.

Editoval Šaman (15. 7. 2013 15:48)

Stránky: Prev 1 … 3 4 5 6 7 … 23 Next