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 5 lety

castamir
Člen | 631
+
0
-

@Casper spíš se mrknu na to, jak je udělaná metoda getData a lehce ji upravím, aby mi nevracela žádnou entitu, ale jen jejich id, a hodím si to do BaseRepository…

před 5 lety

Tharos
Člen | 1039
+
0
-

@Casper:

A jak tato data poté vložíš do entity? To nelze, pokud máš mapper s konverzemi mezi názvy sloupců a atributů. Například some_entity / someEntity.

Máš pravdu. Uvědomuji si, že to mám zjednodušené tím, že já v databázi mám prostě pojmenované sloupce tak, jako položky. Takže položku firstName persistuji normálně do sloupce firstName atp. Podtržítka se v mých názvech sloupců zpravidla vyskytují jen u vazebních sloupců…

před 5 lety

Casper
Člen | 253
+
0
-

@Tharos:

Lze nějakým jednoduchým způsobem získat Entity z druhé strany M:N vazby? Anotace Tag[] $tag m:hasMany u Book předpokládá vazební tabulku ve tvaru book_tag. Pokud tedy existuje v tomto tvaru, nemohu použít anotaci pro vztah z druhé strany (u entity Tag směrem k Book). Předpokládám, že není problém napsat si na to metodu, nicméně se trochu ztrácím v těch referenced a referencing metodách (předpokládám, že bude později v dokumentaci). Mohl bys prosím trochu nastínit, jak to co nejjednodušeji řešit?

před 5 lety

Šaman
Člen | 2248
+
0
-

@Casper: Z druhé strany tu vazbu musíš popsat stejně. Tedy:

<?php

/**
 * @property Tag[] $tags m:hasMany
 */
class Book
{...}


/**
 * @property Book[] $books m:hasMany
 */
class Tag
{...}

//-------
dump($book->tags);
dump($tag->books);

?>

//Edit: Aha, tohle znáš. Proč není možné použít definici i z druhé strany?

Editoval Šaman (24. 7. 2013 18:39)

před 5 lety

Tharos
Člen | 1039
+
0
-

@Casper: Jsou hned tři způsoby, jak tohle vyřešit. :)

1) Pomocí anotace (nejstručnější varianta)

/**
 * @property int $id
 * @property Book[] $books m:hasMany(:book_tag)
 */
class Tag extends Entity
{
}

2) Pomocí vlastního mapperu (asi „nejsystémovější“ varianta)

public function getRelationshipTable($sourceTable, $targetTable)
{
    if ($sourceTable === 'tag' and $targetTable === 'book') {
        return 'book_tag';
    }
    return parent::getRelationshipTable($sourceTable, $targetTable);
}

3) Pomocí get metody (v entitě)

public function getBooks()
{
    $value = array();
    foreach ($this->row->referencing('book_tag') as $row) {
        $value[] = new Book($row->referenced('book'));
    }
    return $value;
}

Editoval Tharos (25. 7. 2013 9:42)

před 5 lety

Tharos
Člen | 1039
+
0
-

@Šaman:

//Edit: Aha, tohle znáš. Proč není možné použít definici i z druhé strany?

Problém je, že bez některého z možných navedení (viz můj předchozí příspěvek) se Lean Mapper z té „druhé strany“ ptá do spojovací tabulky tag_book, která samozřejmě neexistuje.

před 5 lety

Petr Daňa
Člen | 106
+
0
-

Casper napsal(a):

@Petr Daňa

Dle Wikipedie https://cs.wikipedia.org/wiki/INSERT… je to součástí standardu SQL-99, ale osobně nemám zkušenost s jinou DB než MySQL.

Implementace standardu 99 není na všech databázových systémech úplná (možná dokonce není úplná na žádném). Co se týče ON DUPLICATE KEY UPDATE, rozhodně jej nepodporuje PostgreSQL, MSSQL ani Oracle.

Hm, takže nic. A co tedy udělat jinou věc, a sice upravit Repository::persist() tak, že by se ta část

<?php
$this->connection->query(
    'INSERT INTO %n %v', $this->getTable(), $values
);
?>

přesunula do nějaké protected metody, která by šla přepsat v potomkovi? Samozřejmně to samé i pro query pro update. Tím by si to každý mohl pro MySql upravit jak by potřeboval…

před 5 lety

Šaman
Člen | 2248
+
0
-

Tharos napsal(a):

@Šaman:

//Edit: Aha, tohle znáš. Proč není možné použít definici i z druhé strany?

Problém je, že bez některého z možných navedení (viz můj předchozí příspěvek) se Lean Mapper z té „druhé strany“ ptá do spojovací tabulky tag_book, která samozřejmě neexistuje.

Aha. Já zatím vlastní konvence do mapperu nepřidal, takže prakticky používám variantu 1)

před 5 lety

Petr Daňa
Člen | 106
+
0
-

Tak jsem tu zas, pro změnu s jednoduchou vazbou M:N a její perzistencí. Je to naimplementované tak jak bylo navrženo v příspěvku na první straně diskuze? Nějak mi to totiž nefunguje.

Property v entitě mám nadefinovanou takhle:

<?php
@property Flat[] $flats m:hasMany(:flat_2_agent::)
?>

V controlleru pak v action mám:

<?php
$this->agent = $this->flatService->getAgent($id);
?>

a v metodě pro zpracování formuláře pak (zkráceně):

<?php
$flat = $this->flatService->getFlat($values['flat']);
$this->agent->flats[] = $flat;
?>

U toho přiřazení mi to ale háže notice:

Indirect modification of overloaded property SvjGjd\Model\Entity\Agent::$flats has no effect

a když jsem zkoušel

<?php
$this->agent->flats->add($flat);
?>

tak metoda prý neexistuje.

V dokumentaci jsem taky k tomuto nic nenašel. Dík za pomoc.

před 5 lety

Tharos
Člen | 1039
+
0
-

Současné API téhle funkconality je takovéto. V tom mém postu je i vysvětleno, proč nebylo technicky možné naimplementovat API navržené na první stránce.

Jelikož tohle v dokumentaci skutečně ještě není, ptej se a já rád odpovím. :)

P.S. m:hasMany(:flat_2_agent::) lze zkrátit na m:hasMany(:flat_2_agent) :)

Editoval Tharos (24. 7. 2013 22:48)

před 5 lety

Petr Daňa
Člen | 106
+
0
-

Tharos napsal(a):

Současné API téhle funkconality je takovéto. V tom mém postu je i vysvětleno, proč nebylo technicky možné naimplementovat API navržené na první stránce.

Jelikož tohle v dokumentaci skutečně ještě není, ptej se a já rád odpovím. :)

P.S. m:hasMany(:flat_2_agent::) lze zkrátit na m:hasMany(:flat_2_agent) :)

Díky za rychlý support :) Podle nového popisu mi ukládání nové vazby už funguje, ale mazání ne. Kód mám následující (zkráceně):

<?php
$agent = $this->flatService->getAgent($agentId);
$flat = $this->flatService->getFlat($flatId);

$agent->removeFromFlats($flat);
$result = $this->flatService->saveAgent($agent);
?>

saveAgent volá klasicky persist metodu. Ta vrátí zas zpátky entitu Agent, vše projde a tváří se ok, ale z databáze se ta vazba nesmaže? Netušíš čím by to mohlo být, respektive na co se zaměřit při hledání problému? Díky…

před 5 lety

Tharos
Člen | 1039
+
0
-

@Petr Daňa: Vyzkoušej odchytit SQL dotazy (třeba pomocí $connection->onQuery[] nebo přes dibi panel). Klidně mi napiš soukromou zprávu (sem bych pak jen sepsal nějaké resumé pro ostatní).

Jo a taky prosím ověř, že používáš aktuální develop verzi. Před pár dny jsem právě v téhle funkcionalitě opravil jeden bug…

před 5 lety

Petr Daňa
Člen | 106
+
0
-

@Tharos:
Nevím, jak se tady dají posílat soukromé zprávy, nic takového tu nevidím, tak to postuju sem. Mám aktuální develop verzi ze 17.7. (zkoušel jsem teď i stahovat znova z odkazu ze stránek Lean Mapperu). Dibi panel ukázal, že proběhne pouze select nad vazební tabulkou:

SELECT flat_2_agent.*
FROM flat_2_agent
WHERE flat_2_agent.agent_id IN (1)

před 5 lety

Tharos
Člen | 1039
+
0
-

A má ten konkrétní Agent onen Flat skutečně přiřazen? Lean Mapper má jistou inteligenci, která způsobuje, že nepokládá takový DELETE dotaz, který by stejně nic nevymazal.

Pokud nezabere tohle střílení od oka, pokusím se u sebe chybu zreprodukovat…

Edit: Koukám, že se to tady nejmenuje soukromé zprávy, ale „Poslat e-mail“ na cizím profilu. :)

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

před 5 lety

Petr Daňa
Člen | 106
+
0
-

Zkusil jsem to prodebuggovat a zjistil jsem, že removeFromTabs() dospěje přes removeFromReferencing v Row až do removeDataEntry v Result. Ve $values jsou správně hodnoty těch dvou sloupců vazební tabulky, které chci smazat, ale vůbec to neprojde tou podmínkou

<?php
if ($values === array_intersect_assoc($entry, $values))
?>

ačkoliv v $this->data to je. Jediné co mě napadá, že je asi problém, že v $entry a $values je prohozené pořadí těch dvou prvků, tak asi kvůli tomu to neprojde?

Edit: Prohozené to je asi proto, že k té vazbě přistupuju „z druhé“ strany.

Editoval Petr Daňa (24. 7. 2013 23:41)

před 5 lety

Tharos
Člen | 1039
+
0
-

No skvělý. Takhle kdyby mi reportovali chyby i klienti ;D.

Díky za vzorný report, jdu se hned zamyslet nad fixem. S těmito informace by to už pro mě neměl být problém napodobit.

před 5 lety

Tharos
Člen | 1039
+
0
-

Tak, vyzkoušej prosím aktuální develop verzi. :) Teď jsem pushnul opravu (doufám).

Editoval Tharos (25. 7. 2013 0:15)

před 5 lety

Petr Daňa
Člen | 106
+
0
-

Super! Díky moc, už to funguje.

Jojo, taky bych si dal líbit podrobné reporty chyb od zákazníka a ne jen „nefunguje to“ :D
Ale taky – klobouk dolů za tvůj support k Lean Mapperu, díky.

před 5 lety

Tharos
Člen | 1039
+
0
-

Nemáš vůbec zač.

Pushnul jsem před chvílí ještě „vylepšení“ té opravy (zjednodušení té podmínky). Kdybys měl chuť, vyzkoušej to prosím ještě na té úplně poslední verzi… Pokud už se tu neozveš, budu to brát jako potvrzení, že i v ní je vše OK. :)

před 5 lety

Casper
Člen | 253
+
0
-

@Tharos

Problém je, že bez některého z možných navedení (viz můj předchozí příspěvek) se Lean Mapper z té „druhé strany“ ptá do spojovací tabulky tag_book, která samozřejmě neexistuje.

Nicméně jsem vůl, že mě první metoda nenapadla, ta je z dokumentace více než jasná, takže sorry za zbytečnou otázku :) I tak bych to možná dal do dokumentace k tomu tagu. Ale alespoň jsem trochu pochopil referenced a referencing z metody 3, díky!

před 5 lety

castamir
Člen | 631
+
0
-

Musím se přiklonit k tomu, co tu prosazoval šaman ohledně priority metody nad anotací. Je to opravdu potřeba. Pokud mám třeba nějaký číselník, pro který nechci nový repozitář (např. country), a chci přistupovat k antributu country entity Adress tak, abych dostal jméno státu a nikoliv id, pak bude nastávat problém při ukládání, protože getter mi v takovém případě v persist vrátí text nikoliv id. Zatím to obcházím duplicitní read-only property countryName, ale to se mi moc nelíbí…

před 5 lety

daniel.mejta
Člen | 21
+
0
-

Ahoj,
předem musím říci, že LeadMapper je skvělá práce, zrovna jsem začal něco podobného psát, než jsem narazil na toto. Každopádně mám ale několik návrhů, jak to vylepšit.

Vztahuje se k verzi 1.4.0.

1) Anotací nadefinuji properties Entity a u jedné property nadefinuji setter:

<?php
/**
 * @property int id
 * @property string username
 * @property string password
 */
class User extends \LeanMapper\Entity
{
    public function setPassword($password)
    {
        $this->password = sha1($password);
    }
}
?>

Intuitivně bych očekával, že se setter zavolá, když budu ke všem property přistupovat stejným způsobem:

<?php
$user = new User;
$user->username = 'john.doe';
$user->password = 'the most secret password ever';
echo $user->username; // outputs: john.doe
echo $user->password; // outputs: the most secret password ever
?>

Ale ono ne, pokud to nalezne property, tak se setter nezavolá. Navrhuji proto, aby v případě že existuje setter, se vždy použil setter namísto property. V tuto chvíli je pořadí property/setter. Ono když očekávám, že bude něco ošetřeno setterem a musím myslet na to, že místo property musím volat setter, je to nešikovné. Když tam setter je, tak v 99 % 100 % případů chci aby se vše přiřazovalo setterem.

2) Array access nad Entity

Mohou nastat případy, kdy je třeba pracovat s entitou jako s polem, např. předat do metody očekávající array, rychle vypsat data entity pomocí foreach (uznávám, většinou pro testovací účely, ale ušetřilo by to trochu času), apod. Je tam sice metoda na převod do pole, ale to není úplně ta jednoduchost, kterou hledám.

První bod je pro mě docela důležitý, druhý bod je jenom kosmetika, ale myslím že docela dobrá. Jsem schopen oba body implementovat a poslat pull request.

Díky.

Editoval daniel.mejta (26. 7. 2013 13:38)

před 5 lety

Tharos
Člen | 1039
+
0
-

Poslední příspěvky (castamir, daniel.mejta) se týkají věci, kterou aktuálně také řeším (zrovna u sebe upravuji ten parser anotací).

Uargumentovali jste mě, že současné chování není úplně nejlepší. Mám ještě jeden nápad, jak by to šlo řešit, a neskromně si myslím, že by to mohlo být nejlepší řešení. Totiž:

1) Nezaváděl bych příznak m:hintOnly, ale namísto jej bych zavedl příznak m:usesMethods.

2) Všechny položky entity by musely být nadefinované v anotaci. Všechny. Myslím, že anotace se v PHP už natolik vžily, že není problém jejich používání vyžadovat.

3) Položky, které mají anotaci m:usesMethods, by interně využívaly uživatelských get/set metod.

Když nad tím tak přemýšlím, tak by to mělo řešit vše zde uvedené. Třeba tu prioritu volání to jasně určuje, protože zaniknou položky, které by nebyly uvedené v anotacích. Anotace bude přítomná vždy a jen se volitelně pro práci s položkou použijí nebo nepoužijí explicitně určené metody.

Plus v tom vidím několik dalších výhod – například že je při pohledu na entitu okamžitě jasné, jaké má položky. Osobně jsem se teď setkal s tím, že jsem měl v nějaké entitě dost dalších podpůrných metod a pak bylo trochu obtížné zjistit, jaké všechny položky ta entita vlastně má.


Moje otázka… Když bude ten příznak uveden bez parametrů, například u položky name se budou hledat metody getName a setName (pokud položka nebude nadefinovaná přes anotaci @property-read). Má to být takhle natvrdo, anebo má ten příznak mít možnost doupřesnění těch metod v parametru (závorkách)?

Příklady:

@property $name m:usesMethods  // použijí se metody getName a setName
@property $name m:usesMethods(readName|writeName)  // použijí se metody readName a writeName
@property-read $name m:usesMethods(readName)  // použije se metoda readName
@property-read $name m:usesMethods(readName|writeName)  // logická chyba, bude vyvolána výjimka (díky property-read)
@property $name m:usesMethods(|writeName)  // použijí se metody getName a writeName

Plus se samozřejmě ptám, jestli by takovéto řešení řešilo vaše problémy a jestli vám přijde vhodné. :)

Editoval Tharos (26. 7. 2013 14:05)

před 5 lety

Tharos
Člen | 1039
+
0
-

@Petr Daňa:

Hm, takže nic. A co tedy udělat jinou věc, a sice upravit Repository::persist() tak, že by se ta část

Nad tím, že bych ještě trochu dekomponoval metodu persist, už jsem přemýšlel. Píšu si to na seznam a určitě to zvážím.

Editoval Tharos (26. 7. 2013 14:13)

před 5 lety

daniel.mejta
Člen | 21
+
0
-

Tharos napsal(a):
Uargumentovali jste mě, že současné chování není úplně nejlepší. Mám ještě jeden nápad, jak by to šlo řešit, a neskromně si myslím, že by to mohlo být nejlepší řešení. Totiž:

1) Nezaváděl bych příznak m:hintOnly, ale namísto jej bych zavedl příznak m:usesMethods.

Přijde mi, že pokud někdo nadefinuje get|set methods, tak neexistuje (tzn. že mě nenapadá) případ, kdy by chtěl přistupovat k property jinak, než přes get|set metody. Implicitní chování by tedy podle mého názoru mohlo být takové, že se budou hledat gettery|settery a teprve pak (když nebudou metody) se bude přistupovat přímo k property. m:usesMethods(…) bych klidně zavedl, aby bylo možno nadefinovat get|set metody s jinými konvencemi, než standardní.

2) Všechny položky entity by musely být nadefinované v anotaci. Všechny. Myslím, že anotace se v PHP už natolik vžily, že není problém jejich používání vyžadovat.

Na 100 % souhlasím! Koneckonců i hinting v IDE pak bude fungovat pro všechny property.

3) Položky, které mají anotaci m:usesMethods, by interně využívaly uživatelských get/set metod.

Pokud se ale anotace vynechá, tak stejně hledat get[PropertyName]|set[PropertyName] metody, viz výše.

Když nad tím tak přemýšlím, tak by to mělo řešit vše zde uvedené. Třeba tu prioritu volání to jasně určuje, protože zaniknou položky, které by nebyly uvedené v anotacích. Anotace bude přítomná vždy a jen se volitelně pro práci s položkou použijí nebo nepoužijí explicitně určené metody.

Plus v tom vidím několik dalších výhod – například že je při pohledu na entitu okamžitě jasné, jaké má položky. Osobně jsem se teď setkal s tím, že jsem měl v nějaké entitě dost dalších podpůrných metod a pak bylo trochu obtížné zjistit, jaké všechny položky ta entita vlastně má.


Moje otázka… Když bude ten příznak uveden bez parametrů, například u položky name se budou hledat metody getName a setName (pokud položka nebude nadefinovaná přes anotaci @property-read). Má to být takhle natvrdo, anebo má ten příznak mít možnost doupřesnění těch metod v parametru (závorkách)?

Příklady:

@property $name m:usesMethods  // použijí se metody getName a setName
@property $name m:usesMethods(readName|writeName)  // použijí se metody readName a writeName
@property-read $name m:usesMethods(readName)  // použije se metoda readName
@property-read $name m:usesMethods(readName|writeName)  // logická chyba, bude vyvolána výjimka (díky property-read)
@property $name m:usesMethods(|writeName)  // použijí se metody getName a writeName

Plus se samozřejmě ptám, jestli by takovéto řešení řešilo vaše problémy a jestli vám přijde vhodné. :)

Ano, u mě by to problémy řešilo, ale jak jsem psal výše – je to moc psaní. m:usesMethods bez parametrů by se mohlo klidně vynechat. m:usesMethods(…) s parametry jsou podle mě více než vhodné, dobrý nápad.

Závěrem: Díky :)

Editoval daniel.mejta (26. 7. 2013 14:37)

před 5 lety

Casper
Člen | 253
+
0
-

@Tharos:

Mám ještě jeden nápad, jak by to šlo řešit, a neskromně si myslím, že by to mohlo být nejlepší řešení.

  1. Nezaváděl bych příznak m:hintOnly, ale namísto jej bych zavedl příznak m:usesMethods.
  2. Všechny položky entity by musely být nadefinované v anotaci. Všechny. Myslím, že anotace se v PHP už natolik vžily, že není problém jejich používání vyžadovat.
  3. Položky, které mají anotaci m:usesMethods budou interně využívat uživatelských get/set metod.

Rozhodně vydím jako plus, že veškeré atributy budou hned zřejmé z anotací (stejně bychom to tak s hintOnly asi všichni psali). Nedokážu si však moc představit, jak uživateli zakážeš používat vlastní metody bez uvedení anotace? Jinak mne nenapadá usecase, kde bych potřeboval vlastní pojmenování getterů a setterů, takže myslim, že si klidně můžeš ušetřit práci a nechat to natvrdo. Přecijenom get/set metody je dost zažitý základ zapouzdření z OOP.

Co se týče názvu m:hintOnly, pak na něm myslím příliš nesejde, zajímalo by mě však, jak to vypadá s implementací defaultních hodnot? To je popravdě funkcionalita, která mi v LM velmi chybí (i kdyby jen v dev verzi).

před 5 lety

Šaman
Člen | 2248
+
0
-

Podle mě je jedno, jestli hintOnly, nebo usesMethods – pořád mi to přijde trochu zbytečné. Pokud existuje setter/getter/obojí, tak chci použít metody.
To, že by všechny všechny property měly být v anotacích s tím souhlasím (napovídání), ale pokud simulujeme Nette přístup, tak reálně nemusí, protože k metodě ->getFoo() ` se dá přistupovat i zkratkou `->foo.

Abych pravdu řekl, pohrávám si s myšlenkou, že bych nějakou stable verzi forknul a převedl na Nette (dědění od Nette/Object, chování s přednostními gettery/settery). Na druhou stranu, pokud by to nebylo nutné, byl bych radši.


Řešením toho případu vazby m:n a přístupu z „druhé strany“ by mohl být nějaký příznak (secondSide, slaveSide, n:m – ten by se mi líbil nejvíc), který by zajistil nalezení vazební tabulky s opačným (platným) pořadí tabulek. Když už mám konvence, tak abych nemusel v definici natvrdo psát název tabulky. Kdyby se pak někdy změnil, mělo by stačit upravit mapper.

Editoval Šaman (26. 7. 2013 16:12)

před 5 lety

castamir
Člen | 631
+
0
-

Jak píše šaman: prvně se ptej na existenci get|setteru a pokud tam není, tak se zachovej defaultně podle anotace (tak jak nyní). Není potřeba to nějak v anotaci specifikovat. Tím, že chceš přidat get|setter, bys ho totiž musel definovat na 2 místech (jako metodu a i jako příznak v anotaci)…

Ještě takovej syntax sugar pro volání getteru s dalším parametrem. Něco jako

$entity->foo($args);

místo

$entity->getFoo($args);

před 5 lety

redhead
Člen | 1315
+
0
-

@Tharos: Lean Mapper jsem sice ještě nepoužil (neb teď nedělám v Nette), ale líbí se mi ta myšlenka a provedení. Ale opravdu zauvažuj, nad tou prioritou setterů/getterů nad property. Protože jinak to ani není property. Definice property je, že k ní přistupuješ jako k fieldu, ale na pozadí se volá getter nebo setter, tedy nejen nastavení/vrácení hodnoty, ale třeba i nějaká další logika. Tohle teď nefunguje, pokud jsem správně pochopil, a pro uživatele je to velké WTF. Tedy aspoň já když jsem si četl toto vlákno, jen jsem kroutil hlavou proč.

PS: toto chování není specialita Nette\Object, viz jiné jazyky podporující (nesimulované) property.

Editoval redhead (26. 7. 2013 21:13)

před 5 lety

Tharos
Člen | 1039
+
0
-

Tak dobře no. :) Přesvědčili jste mě. Nahazuji následující řešení:

  • Při přístupu k položce přes $entity->foo se nejprve bude hledat getFoo() (respektive setFoo()) metoda a pokud bude nalezena, použije se. Jinak se bude hledat položka v anotacích.
  • Anotace bude moci obsahovat příznak m:usesMethods, který bude mít dva významy. Pokud bude uveden bez doplňku v závorce, bude v podstatě jen zajišťovat napovídání v IDE. V takovém případě nebude jeho přítomnost vyžadována, byť bude doporučená (kvůli napovídání a přehledu všech existujících položek na jednom místě). Pomocí volitelného doplňku v závorce bude možné určit, které metody se použijí. To se může hodit například tehdy, když chci mít položku pojmenovanou data a vyjádřit ji pomocí metod (odvozená metoda getData() koliduje s existující „systémovou“).

Má k navrženému řešení někdo nějakou výhradu? :)

před 5 lety

Tharos
Člen | 1039
+
0
-

@castamir:

Ještě takovej syntax sugar pro volání getteru s dalším parametrem. Něco jako

$entity->foo($args);

místo

$entity->getFoo($args);

Jo, tohle mě nedávno taky napadlo, ale přijde mi to jako už docela dost „velkej haluz“. :) Je to sice úsporné, ale – jak bych to je řekl – takové trochu míchání hrušek s jablky. Skoro bych pak měl tendenci psát následující :).

$entity->foo($args) = $value;

před 5 lety

daniel.mejta
Člen | 21
+
0
-

Tharos napsal(a):
Tak dobře no. :) Přesvědčili jste mě. Nahazuji následující řešení

Má k navrženému řešení někdo nějakou výhradu? :)

Bez výhrad, takto by to bylo skvělé :)

A jak jsem psal o tom array access u entity … stojí to za úvahu?
Array access beru zpět :)

Editoval daniel.mejta (27. 7. 2013 0:42)

před 5 lety

Šaman
Člen | 2248
+
0
-

To se mi líbí. I s tou možností používat jinak pojmenované metody. Jen přemýšlím, jestli není lepší useMethods.

před 5 lety

Tharos
Člen | 1039
+
0
-

Chtěl jsem, aby to bylo konzistentní například s hasOne, hasMany atp., ale už teď máme třeba filter, pracuje se na passThru… což jsou taky jiné tvary. Takže asi se taky přikloním ke stručnější verzi.

Nově parser tedy bude rozumět následujícím příznakům:

m:hasOne
m:hasMany
m:belongsToOne
m:belongsToMany

m:enum
m:filter
m:passThru
m:useMethods

Editoval Tharos (29. 7. 2013 1:45)

před 5 lety

Tharos
Člen | 1039
+
0
-

Abyste neřekli, že vývoj stagnuje, pushnul jsem dneska večer poctivou várku slibovaných featur. :)

Lepší parser anotací

Nyní už ho nerozhodí mezery mezi názvem příznaku a parametry příznaku, nevadí mu libovolné pořadí příznaků a ještě lépe než dříve si hlídá smysluplnost definice. Například skončí výjimkou s jasnou zprávou v případě, že jsou nějaké příznaky přítomné vícekrát atp.

Dokonce nakonec ani nebyl zapotřebí ten fígl s # kvůli komentářům. Anotace jen musí začínat druhem anotace (property nebo property-read) následovaným typem proměnné, názvem položky (volitelně následovaným nízkoúrovňovým sloupcem v závorkách) a pak už může následovat cokoliv a Lean Mapper si z toho vyzobá m: příznaky. Tohle je tedy plně funkční definice:

@property Author $author m:hasOne  Author of book

Nové příznaky

Lean Mapper nyní rozlišuje následující příznaky:

m:hasOne
m:hasMany
m:belongsToOne
m:belongsToMany

m:enum
m:filter
m:passThru
m:useMethods

m:passThru umožňuje prohnat nízkoúrovňovou hodnotu těsně (po načtení z|před vložením do) Row vlastní metodou (typicky protected a zpravidla v té samé entitní třídě), což významně usnadňuje validaci vybraných hodnot:

@property string $email m:passThru(checkEmail)

Možné tvary jsou:

m:passThru(checkEmail) // čtení i zápis využije stejné metody
m:passThru(beautifyEmail|filterEmail) // čtení jde přes metodu beautifyEmail a zápis přes filterEmail
m:passThru(|filterEmail) // čtení jde napřímo, zápis přes filterEmail
m:passThru(beautifyEmail|) // čtení jde přes metodu beautifyEmail, zápis jde napřímo

m:useMethods funguje přesně podle tohoto mého popisu (je se to nejmenuje usesMethods ale useMethods).

Whitelist u getData()

Jde jen o to, že metodě getData() lze volitelně předat pole s názvy položek, jejichž hodnoty (vysokoúrovňové) se mají vrátit.

Vlastní příznaky

Podle bodu 3 v tomto mém příspěvku. LeanMapper\Property má nově s tímto související metody hasCustomFlag($name) a getCustomFlagValue($name), které lze podle libosti využívat.

Odstranil jsem v souvislosti s tímto příznak m:extra, což je zároveň asi jediný významný BC break v posledních commitech (pokud jste jej využívali).

Upravení priorit volání při přístupu k položce

Nyní mají metody ve všech případech přednost, pokud existují. Teoreticky je to také BC break.


Podrobný přehled úprav je nejlépe vidět v přehledu commitů.

Nyní je na pořadí zdokonalit ty filtry a defaultní hodnoty (obojí už mám tak nějak rozdělané/rozmyšlené). A pak bych asi ještě do verze 1.5 zahrnul ty události – v takové podobě, v jaké jsem je zde navrhl a nikdo proti tomu nijak neprotestoval. :) Ihned poté vypustím preview verzi 1.5.

Vítám jakékoliv ohlasy a pokusím se obratem řešit případné bugy. Díky!

Editoval Tharos (29. 7. 2013 9:05)

před 5 lety

Šaman
Člen | 2248
+
0
-

Super, aktualizováno a odzkoušeno.
Ta změna priorit je drobný BC break a dá se snadno opravit. m:extra jsem nepoužíval.

<?php
/*
 * Implementace rozhraní IRole
 */
public function getId()
{
    // return $this->id; # teď už to hledá skutečnou členskou proměnnou, která neexistuje
    return $this->row->id; # nenapadá někoho, jak to napsat bez nízkoúrovňového přístupu k row?
}
?>

// doplněno: S použitím m:passThru jsem si skutečně mohl vytvořit propery výčet (číselník) přesně podle mého. Za vytvoření pole je nyní zodpovědná jediná metoda, takže je jedno, jestli to pole tahám z konstant, definuji jinak, nebo získávám z nějaké číselníkové tabulky.

Takže u mě spokojenost. :)

P.S. Ještě by někdo mohl zveřejnit mapper, který ctí stejná pravidla, jako Ndb. (Je teď moc práce a příští týden odjíždím na tábor. Pokud nějakou dobu nebude, tak si ho napíšu a zveřejním sám.) Tento mapper by mohl být i součást jádra, protože řeší převod $fooBar property na foo_bar sloupce a to je dost potřeba. Neřešení velikosti písmen v některých (MySQL) databázích může vést k WTF chování a používání $foo_bar proměnných v PHP není hezké, ani zvykem.

// ještě jednou doplněno: Trochu mě překvapilo, že komentář může začínat na m:comment. Takže používám m:virtual u zcela virtuálních property jen proto, abych hned věděl, že této property neodpovídá žádný sloupec v DB. Původně jsem chtěl použít m:useMethods, ale to jen říká, že se mají použít metody, ale neříká to nic o existenci sloupce. Jen by mě zajímal tvůj názor na podobné komentáře – neplánuješ si někdy rezervovat m:cokoliv pro sebe, už třeba proto, abys mohl házet chybu při překlepu? (m:pasThru(importantValidation) teď totiž nic nedělá.)

Editoval Šaman (29. 7. 2013 7:19)

před 5 lety

Glottis
Člen | 129
+
0
-

pecka :) porad cekam kdy zacnes vybirat penize, protoze neco takoveho snad nemuze byt zadarmo :))

a takovy dotaz/podnet: neslo by predefinovat nazvy nekterych anotaci? treba m:hasOne, m:hasMany, m:belongsToOne, m:belongsToMany? nemyslim abys to zmenil ty ale aby si uzivatel mel moznost to zmenit a vyuzit jina oznaceni. ted treba musim neco patlat v jave, jsou tam zas jina klicova slova na toto (OneToMany a atd) a sem pak jak schizofrenik.

Glo

před 5 lety

castamir
Člen | 631
+
0
-

@Tharos už jsem se na to docela i těšil. Zítra zkusím upgrade na novém projektu. Uvidíme, kolik se toho změnilo. Mám tam skoro všechny zde na foru řešené problémy, tak to bude takový komplexní test :o)

před 5 lety

Tharos
Člen | 1039
+
0
-

@Šaman:

P.S. Ještě by někdo mohl zveřejnit mapper, který ctí stejná pravidla, jako Ndb. (Je teď moc práce a příští týden odjíždím na tábor. Pokud nějakou dobu nebude, tak si ho napíšu a zveřejním sám.) Tento mapper by mohl být i součást jádra, protože řeší převod $fooBar property na foo_bar sloupce a to je dost potřeba. Neřešení velikosti písmen v některých (MySQL) databázích může vést k WTF chování a používání $foo_bar proměnných v PHP není hezké, ani zvykem.

Podívám se pro zajímavost, jak to NDB řeší. Pro tohle je dobrá inspirace taky Nette převod URL z tvaru nejaky-pohled na nejakyPohled. Už jsem tady někde na to přímo posílal odkaz, teď se mi to nechce hledat… Není to žádná velká regulární věda.

// ještě jednou doplněno: Trochu mě překvapilo, že komentář může začínat na m:comment. Takže používám m:virtual u zcela virtuálních property jen proto, abych hned věděl, že této property neodpovídá žádný sloupec v DB. Původně jsem chtěl použít m:useMethods, ale to jen říká, že se mají použít metody, ale neříká to nic o existenci sloupce. Jen by mě zajímal tvůj názor na podobné komentáře – neplánuješ si někdy rezervovat m:cokoliv pro sebe, už třeba proto, abys mohl házet chybu při překlepu? (m:pasThru(importantValidation) teď totiž nic nedělá.)

Tohohle jsem si vědom, ale vyhovuje mi, jak to teď je. Myslím, že vestavěných příznaků už moc nepřibude, pokud vůbec nějaké. I kdybych se náhodou trefil do něčeho, co někdo používá, uprava by měla být jednoduchá (projít fulltextem entity a upravit místo, kde se se tím vlastním příznakem pracuje).

Pokud by ses tomu chtěl za každou cenu vyvarovat, můžeš si své příznaky prefixnout třeba podtržítkem.

Překlepy typu pasThru teď principiálně nelze vyřešit, ale myslím, že to až tak nevadí.

před 5 lety

Tharos
Člen | 1039
+
0
-

@Glottis:

a takovy dotaz/podnet: neslo by predefinovat nazvy nekterych anotaci? treba m:hasOne, m:hasMany, m:belongsToOne, m:belongsToMany? nemyslim abys to zmenil ty ale aby si uzivatel mel moznost to zmenit a vyuzit jina oznaceni. ted treba musim neco patlat v jave, jsou tam zas jina klicova slova na toto (OneToMany a atd) a sem pak jak schizofrenik.

Noo, ono by to asi nebylo až tak složité :), ale momentálně bych to nezaváděl. Myslím si, že by to využil pra-málokdo a trochu zbytečně by to tak zesložitilo vnitřní kód (byť minimálně).

před 5 lety

Glottis
Člen | 129
+
0
-

Tharos napsal(a):

@Glottis:

a takovy dotaz/podnet: neslo by predefinovat nazvy nekterych anotaci? treba m:hasOne, m:hasMany, m:belongsToOne, m:belongsToMany? nemyslim abys to zmenil ty ale aby si uzivatel mel moznost to zmenit a vyuzit jina oznaceni. ted treba musim neco patlat v jave, jsou tam zas jina klicova slova na toto (OneToMany a atd) a sem pak jak schizofrenik.

Noo, ono by to asi nebylo až tak složité :), ale momentálně bych to nezaváděl. Myslím si, že by to využil pra-málokdo a trochu zbytečně by to tak zesložitilo vnitřní kód (byť minimálně).

jasny, jen me to tak napadlo, ze by to bylo hezke mit tohle nekde zadefinovane a jen to prepsat. nekde na zacatku vlakna jsem tam videl nejake breky o nestastne zvolenych slovech tak by si to mohl kazdej naladit jak je zvykli. pokud by mu to vadilo. tak treba az nebudes mit do ceho pichnout a uz to bude umet vsechno :)

před 5 lety

Šaman
Člen | 2248
+
0
-

Jenom pozor na to, že m:hasMany neříká nic o tom jaká je vazba na této straně. Takže to může být OneToMany, stejně jako ManyToMany.

před 5 lety

Petr Daňa
Člen | 106
+
0
-

Šaman napsal(a):

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.

Uff, díky za tenhle post, právě jsem řešil stejný problém, ačkoliv je divné, že doteď mi to fungovalo (na jedné z posledních develop verzí) a zničeho nic mi to začalo vyhazovat.

@Tharos:
Vím, že Lean Mapper je psán jako nezávislá knihovna na Nette, ale vzhledem k tomu, že je pravděpodobné, že největší vyuužití bude mít právě s tímto frameworkem, bylo by fajn v budoucí dokumentaci vytvořit nějaký „troubleshooting“ a v něm i sekci pro Nette…

před 5 lety

Michal III
Člen | 84
+
0
-

Nevím, zda-li dělám něco špatně, ale s aktuální develop verzí nejsem schopen pomocí anotace @entity u repository zvolit název třídy entity.

Mám entity:

<?php

namespace Model\Entity;

/**
 * @property int $id
 * @property string $firstName
 * @property string $lastName
 *
 * @property Book[] $books m:belongsToMany(:aa_books)
 */
class Author extends Entity
{
}

/**
 * @property int $id
 * @property string $name
 * @property Author $author m:hasOne
 */
class Book extends Entity
{
}

?>

a repozitáře:

<?php

namespace Model\Repository;

/**
 * @table aa_authors
 */
class AuthorRepository extends BaseRepository
{
}

/**
 * @table aa_books
 * @entity Book
 */
class BookRepository extends BaseRepository
{
}

?>

a když chci potom vypsat:

<?php

foreach ($authorRepository->findAll() as $author) {
    echo "$author->lastName\r\n";
    foreach ($author->books as $book) {
        echo "$book->name\r\n";
    }
}
?>

hází mi to výjimku: Fatal Error: Class ‚Model\Entity\Aa_books‘ not found na řádku 615 LeanMapper/Entity.php.

Metoda IMapper::getEntityClass má nyní k dispozici pouze název tabulky, pokud tomu dobře rozumím, přičemž mně by se spíše hodilo odvozovat název třídy entity od názvu třídy repozitáře. Každopádně mi nefunguje ani ona anotace @entity, nevím však, co děláš špatně.

před 5 lety

Tharos
Člen | 1039
+
0
-

@Michal III:

Střelím narychlo – tohle nefunguje?

/**
 * @table aa_authors
 * @entity Author
 */
class AuthorRepository extends BaseRepository
{
}

Metoda IMapper::getEntityClass má nyní k dispozici pouze název tabulky, pokud tomu dobře rozumím, přičemž mně by se spíše hodilo odvozovat název třídy entity od názvu třídy repozitáře.

K tomu se lze dopracovat následovně:

$mapper->getEntityClass($mapper->getTableByRepositoryClass('Model\Repository\SomeRepository'));

Možná to působí neohrabaně, ale u správně navrženého mapperu to spolehlivě funguje.

před 5 lety

Michal III
Člen | 84
+
0
-

@Tharos

Děkuji za odezvu. Bohužel ani přidáním @entity Author se to nevyřešilo. Přitom chyba nastane ve chvíli, kdy zavolám $author->books, kde bych si spíše myslel, že se název Entitní třídy vezme z anotace @property Book[] $books m:belongsToMany(:aa_books).


$mapper->getEntityClass($mapper->getTableByRepositoryClass('Model\Repository\SomeRepository'));

Možná to působí neohrabaně, ale u správně navrženého mapperu to spolehlivě funguje.

Tohle jsem bohužel nepochopil, jak bych mohl poděděním třídy DefaultMapper ovlivnit, jakým způsobem se budou volat metody rozhraní IMapper, uniká mi něco?

Myslel jsem, že akorát přepíšu metodu getEntityClass().

před 5 lety

Michal III
Člen | 84
+
0
-

fooBar → foo_bar a obráceně

<?php

class Mapper extends \LeanMapper\DefaultMapper
{

    public static function camel2Snake($str)
    {
        return strtolower(preg_replace('#(?<=[a-z])([A-Z])#', '_$1', $str));
    }

    public static function snake2Camel($str)
    {
        $words = explode('_', $str);
        $return = $words[0];
        unset($words[0]);
        foreach ($words as $word) {
            $return .= ucfirst($word);
        }
        return $return;
    }

    public function getColumn($entityClass, $field)
    {
        return self::camel2Snake($field);
    }

    public function getEntityField($table, $column)
    {
        return self::snake2Camel($column);
    }

}

?>

před 5 lety

Glottis
Člen | 129
+
0
-

jde to i takhle

class EntityHelpers {

    static public function decamelize($word) {
        return preg_replace(
                '/(^|[a-z])([A-Z])/e', 'strtolower(strlen("\\1") ? "\\1_\\2" : "\\2")', $word
        );
    }

    static public function camelize($word) {
        return lcfirst(preg_replace('/(^|_)([a-z])/e', 'strtoupper("\\2")', $word));
    }

}

před 5 lety

Tharos
Člen | 1039
+
0
-

@Michal III:

Bohužel ani přidáním @entity Author se to nevyřešilo. Přitom chyba nastane ve chvíli, kdy zavolám $author->books, kde bych si spíše myslel, že se název Entitní třídy vezme z anotace @property Book[] $books m:belongsToMany(:aa_books).

Super, už jsem asi doma. :) Souvisí to částečně s tím, co jsem psal zde.

Od chvíle, kdy byla zavedena lepší podpora single table inheritance vzoru, typ uvedený v anotacích trochu ztratil váhu. Stěžejní je nyní typ, který pro danou tabulku vrátí mapper. Ten pak pouze ověří, že je konzistentní s anotací.

Ty potřebuješ vlastní mapper, který bude obsahovat zhruba následující:

class Mapper extends LeanMapper\DefaultMapper
{

    public function getTable($entityClass)
    {
        if ($entityClass === 'Model\Entity\Author') {
            return 'aa_authors';
        }
        if ($entityClass === 'Model\Entity\Book') {
            return 'aa_books';
        }
        return parent::getTable($entityClass);
    }

    public function getEntityClass($table, Row $row = null)
    {
        if ($table === 'aa_authors') {
            return $this->defaultEntityNamespace . '\Author';
        }
        if ($table === 'aa_books') {
            return $this->defaultEntityNamespace . '\Book';
        }
        return parent::getEntityClass($table, $row);
    }

    public function getTableByRepositoryClass($repositoryClass)
    {
        if ($repositoryClass === 'Model\Repository\AuthorRepository') {
            return 'aa_authors';
        }
        if ($repositoryClass === 'Model\Repository\BookRepository') {
            return 'aa_books';
        }
        return parent::getTableByRepositoryClass($repositoryClass);
    }

}

Pak by ses měl obejít úplně bez těch anotací. Píšu ten mapper z hlavy, takže možná v něm bude zapotřebí ještě něco málo doladit, ale pevně věřím, že princip je z toho jasný. Také by dost možná šel ještě trochu uhladit (nevím, jestli třeba aa_ není nějaký prefix všech tabulek atp. – to by šlo určitě také zautomatizovat).


Ještě trochu jinými slovy… Ty anotace @table a @entity i v aktuální verzi fungují, ale Tobě aplikace padá při traverzování mezi entitami, kde se repositářů nevyužívá. Při tom traverzování se používá mapper, ale ten, který používáš, vrací špatné informace (tvrdí, že v tabulce aa_authors jsou uloženy data pro entitu Model\Entity\Aa_authors).

před 5 lety

Michal III
Člen | 84
+
0
-

@Tharos

Děkuji za odpověď, teď už tomu rozumím :-).

Stránky: Prev 1 … 6 7 8 9 10 … 23 Next RSS tématu