Oznámení
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.
- Jsme na objektové, PHP straně a to, že proměnnou
$userName
mám uloženou ve sloupciuser_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ístoarray('user' => User)
mítarray('user_id' => 1)
array('user' => 1)
. - 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é zNette\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 sloupciauthor_id
,$owner
ve sloupciowner_id
– tohle nemá spojitost s tím, v jaké tabulce bude author a owner uložený, u nás budou asi obě v tabulceperson
) - spojovaví tabulky ve tvaru
person_group
(pořadí záleží na tobě, často si místo podtržítka můžeš dát spojkuhas
, tedybook_tag
, někde to nejde, protože je vazba rovnocenná) - co je v PHP
$camelCase
, to je v SQL sloupeccamel_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()
agetRawData()
/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 jejichid
. 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:
- PORM nemá pořádnou dokumentaci, jen kratičký QS.
- U PORM si nikdo není moc jist jeho výkonem (ale v praxi jsme s tím nikdy neměli problém).
- 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.
- 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.