Oznámení
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řebaDibiDateTime
). 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říkladm:filter(LimitFilter::limit)
ještě spolu suse
). - Jako filtr půjde zaregistrovat cokoliv, co pojme
call_user_func
jako$callback
, anebo instance třídyClosure
. 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
zaLeanMapper\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)