Oznámení
před 6 lety
- Šaman
- Člen | 2275
O více mapperech jsem už přemýšlel, ale počkám na dokumentaci –
možná už je lze použít.
Jde mi o to, aby se výjimky oproti konvenci psaly do samostatné třídy
rozšiřující hlavní mapper. V jednom adresáři modelu by pak byla typicky
třída entity (např. User), repozitář (UserRepository) a mapper
(UserMapper). Repositář i mapper však často bývají prázdné, takže je
otázka, zda by jejich existence byla nutná, nebo by se dala nějak na pozadí
nahradit defautními třidami App/../Repository
a
App/../Mapper
.
Výhoda by byla v tom, že při přidání nové entity s vlastními mapovacími pravidly by se přidal nový soubor místo zásahu do existujícího (hrozí rozkopání odladěného kódu a to, že jediný mapper přeroste pokud bude pravidel mnoho pro mnoho entit). Taky se pak budou pravidla ke konkrétní entoté lépe hledat, než v ifech velkého Mapperu.
před 6 lety
- Tharos
- Člen | 1042
@Šaman: To, co popisuješ, bych osobně řešil tak,
že bych si vytvořil nějaká MapperExtension
rozšíření. Ta
bych pak do hlavního Mapperu registroval obdobně, jako se registrují
například extenze do Nette konfigurátoru před sestavením DI kontejneru.
Určitě by šlo udělat, aby se to odehrálo úplně automaticky: jakmile bys
nějakou takovou extenzi zaregistroval jako službu do DI kontejneru, kontejner
by ve vhodný okamžik mohl projít všechny MapperExtension
,
které obsahuje, a zaregistrovat je do hlavního Mapperu.
Jak jsem psal, Mapper se předává při traverzování mezi entitami a byl
by neudržitelný hokej, kdyby jich bylo víc (těch Mapperů) a každý by
tvrdil třeba něco trochu jiného. Je to jako kdybys pracoval s více
DibiConnection
zároveň, každé jsi měl připojené do jiné
databáze a snažil se z toho celého uvařit nějaký konzistentní výsledek.
(Lepší analogie mě nenapadla :–P.)
Editoval Tharos (29. 1. 2014 16:57)
před 6 lety
- Filip111
- Člen | 244
@Tharos:
Dynamický mapper…že mě to jen nenapadlo :)
Díky.
před 6 lety
- peter.z
- Člen | 37
Zdravim :)
Uz par dni sa hram s generovanim tabuliek v databaze pomocou entit z LeanMapperu. Objavil som pomerne silnu funkciu Doctrine – schemy (http://docs.doctrine-project.org/…ntation.html). Pomocou tohto je generovanie tabuliek naozaj hrackou :)
Tu je moj nastrel – https://gist.github.com/…mous/8782935
Samozrejma je aj implementacia vazieb, cudzich klucov, vlastne flagy (m:size, m:autoincrement, m:unique, m:format(date|time) pri DateTime).
Daju sa s tym urobit hotove zazraky, mozete si ukladat stare schemy a pri zmene entity vam to moze vygenerovat potrebne SQL na zmenu databazy. Tu je priklad – https://gist.github.com/…mous/8782990 :)
Uvitam akekolvek pripomienky a napady :)
před 6 lety
- Tharos
- Člen | 1042
Kloubok dolů! :)
Hraji si tady s tím a funguje to jako víno. Díky!
Připomínky
Využít téhle funkcionality z Doktríní DBAL je výborný nápad. Tím je vyřešeno nespočet věcí, které by jinak bylo zapotřebí řešit (znovu-vynalézat). Těší mě, jak ty nástroje souzní.
Já to určitě budu používat a budu Ti poskytovat připomínky a zpětnou vazbu, pokud narazím na nějakou nesrovnalost. Určitě budu preferovat Tvůj nástroj před dopsáním svého generátoru, který jsem někdy na podzim začal psát úplně od nuly a je na něm ještě práce jako na kostele.
Ve svých úvahách jsem přemýšlel, zda metainformace potřebné pouze pro generátor schéma (auto-increment, délky sloupců…) by nebylo vhodné mít mimo anotace v entitách. To proto, aby v entitách zbytečně nevznikalo příliš balastu. Dopředně přemýšlím i nad tím, že já bych ve výsledku chtěl psát jenom entity a generovat nejen schéma databáze, ale i automaticky sestavovat formuláře. No a k formulářům budou zapotřebí další speciální metadata a v entitě by toho ve výsledku mohlo být poměrně hodně. Je to ale věc osobních preferencí.
před 6 lety
- peter.z
- Člen | 37
Dakujem :)
Tiez som to chcel pisat vsetko rucne a odznova… ale potom som na jednom projekte zacal pouzivat schemy, kde som si vsetko rucne definoval (kazdy stlpec, vazbu) a ked som mal to iste pisat aj do entity, tak som si povedal ze tu musi byt aj ina cesta.
Osobne sa mi paci mat zapisane metadata priamo v entitach, nejako mi to tam proste pasuje. Je sice pravda, ze po case to moze nabobtat, ale to by muselo ist o uber-entity :)
Nad generovanim formularov som premyslal, no narazil som na jeden zasadny problem – ak by som chcel vytvorit HasOne a HasMany vazby, musim mat dostupne repository k danej vazbe. Napr. mam entitu Product a ta ma HasOne na Category. Otazka ale znie, ako sa mozem vo formulari dostat ku CategoryRepository alebo k niecomu podobnemu? Musela by tam byt nejaka RepositoryMap, kde by bola trieda entity a k nej patricke repository, aby som sa mohol k tym entitam vo formulari dostat… Otazkou je, ze ci je to spravny pristup :)
před 6 lety
- Nobody.guy
- Člen | 22
Koukám, že mě někdo o pár dnů předběhl :-) dobrá práce!
před 6 lety
- batko
- Člen | 219
Ahoj,
právě jsem začal používat tento výtečný nástroj…Jen mi zaboha nefunguje ukládání datetime.
<?php
$x->inserted = new \DateTime();
?>
persistuji a:
Unexpected value type given in property ‚inserted‘ in entity Model\Entity\Phrase, Model\Entity\DateTime expected, instance of DateTime given.
děkuji za radu
před 6 lety
- Šaman
- Člen | 2275
Však ti to píše, v čem je problém. Pravděpodobně jsi v definici
sloupce v anotaci entity nedal před DateTime lomítko, nebo, lépe,
nevyčlenil DateTime do use sekce. Takže to teď očekává instanci
Model\Entity\DateTime
a odmítá to DateTime
.
před 6 lety
- danik
- Člen | 56
Ahoj vespolek,
tak nějak se rozkoukávám v Lean Mapperu a zatím se mi to moc líbí! Můj zatím jedinej „teologickej“ problém s LM je, že je to striktně databázová vrstva, což je podle mě podmnožina toho, co by měl model v tom nejširším pojetí umět.. nejsem nijak velkej odborník na ostatní ORM ani na návrhové vzory obecně, ale pocitově bych řekl, že má-li model zapouzdřovat obecně všechnu práci související s perzistentními daty a vztahy mezi nimi, patří mezi tyhle data třeba i některé obrázky (dokonce někdy zejména obrázky), nebo obecně vzato věci, které chceme spíš uložit do souboru. Pokud pomineme možnost ukládat obrázky do BLOB sloupců v databázi (uf, úplně mi vstávaj vlasy na zátylku), žádná striktně databázová modelová vrstva nám tuhle možnost nedá.
Takže se chci zeptat, jak tohle řešit konkrétně u Lean Mapperu? Rád
bych bral LM jako tu modelovou vrstvu, obalovat ho ještě něčím
zvenku mě neláká. Ale třeba konkrétní příklad obrázkové galerie:
jednoduchá tabulka
pictures (id INT, postedOn DATETIME, description TEXT)
a někde
odpovídající adresář pictures
, ve kterém čekáme JPEGy
pojmenované <id>.jpg
. Až na ten soubor je to celkem pěkná
entita.. ale jakmile začnu přemejšlet o tom, že ta entita vlastně popisuje
soubor a nějaká jeho metadata, začnu mít divný otázky a nemít na
ně odpověď:
- Jak taková entita má vzniknout? Řekněme, že uploaduju novej obrázek.
Vzniká entita. Moje představa je taková, že bych entitě asi měl předat
nějakou cestu (třeba
$upload->getTemporaryFile()
) a entita (respektive její repository) by si před perzistováním měla uvědomit, že má tenhle soubor odcestovat na patřičné místo a cestu si aktualizovat. Dobře, to by nebyl takovej problém (pomocí eventů atp). - Zároveň ale – nejsou binární data obrázku tak trochu „náplní“ (smyslem) téhle entity – a neměly by tudíž bejt skrze ni nějak dostupný? nebo třeba instance Nette\Image? Protože jinak práci s binárními daty vyčleňuji ven z modelu (pro ten bude property filename prostě jen string bez hlubšího významu).. kdo pak obrázky zmenší? ořízne? obrátí vzhůru nohama? atd.
Jak prostě zařídit, aby mi tahle část doménové logiky (ha, termitus technikus!) nezmizela z modelu a nezačala se, nikým nezvaná, objevovat někde, kde ji nechci?
před 6 lety
- Etch
- Člen | 404
danik napsal(a):
Můj zatím jedinej „teologickej“ problém s LM je, že je to striktně databázová vrstva, což je podle mě podmnožina toho, co by měl model v tom nejširším pojetí umět.
Já teda nevím „školy nemaje“, ale neslouží ORM už z podstaty k mapování dat z RDBMS??
Pokud pomineme možnost ukládat obrázky do BLOB sloupců v databázi (uf, úplně mi vstávaj vlasy na zátylku), žádná striktně databázová modelová vrstva nám tuhle možnost nedá.
Přijde mi „nesprávné“ chápat samotné ORM jako „modelovou vrstvu“.
Takže se chci zeptat, jak tohle řešit konkrétně u Lean Mapperu? Rád bych bral LM jako tu modelovou vrstvu, obalovat ho ještě něčím zvenku mě neláká.
Co znamená „obalovat ho ještě něčím z venku“? Je vytvoření třeba nějaké FACADE pro tebe obalování?
- Jak taková entita má vzniknout? Řekněme, že uploaduju novej obrázek. Vzniká entita. Moje představa je taková, že bych entitě asi měl předat nějakou cestu (třeba
$upload->getTemporaryFile()
) a entita (respektive její repository) by si před perzistováním měla uvědomit, že má tenhle soubor odcestovat na patřičné místo a cestu si aktualizovat. Dobře, to by nebyl takovej problém (pomocí eventů atp).- Zároveň ale – nejsou binární data obrázku tak trochu „náplní“ (smyslem) téhle entity – a neměly by tudíž bejt skrze ni nějak dostupný? nebo třeba instance Nette\Image? Protože jinak práci s binárními daty vyčleňuji ven z modelu (pro ten bude property filename prostě jen string bez hlubšího významu).. kdo pak obrázky zmenší? ořízne? obrátí vzhůru nohama? atd.
Jak prostě zařídit, aby mi tahle část doménové logiky (ha, termitus technikus!) nezmizela z modelu a nezačala se, nikým nezvaná, objevovat někde, kde ji nechci?
To zní, jako kdyby si naznačoval, že co ti „zmizí“ z ORM, ti zmizí z modelu. Možná že se mýlím, ale tento způsob chápání ORM jako „celé“ modelové vrstvy, mi přijde jako poněkud „divná“ myšlenka.
Editoval Etch (5. 2. 2014 23:27)
před 6 lety
- danik
- Člen | 56
Etch napsal(a):
Já teda nevím „školy nemaje“, ale neslouží ORM už z podstaty k mapování dat z RDBMS??
Já právě také školy nemaje čtu „Object Relational Mapping“ doslovně, tedy jako „vztahovou mapu objektů“ – v čemž mi „databáze“ nutně nerezonuje. Není snad obrázek ve fotogalerii objektem, který má nějaké vlastnosti a nějaké vazby na okolní svět?
Přijde mi „nesprávné“ chápat samotné ORM jako „modelovou vrstvu“.
K chápání ORM jako ucelené modelové vrstvy mě vede to, že už pracuje s těmi objekty, které mě zajímají – tj s entitami a jejich vztahy, nějak úplně objektově nevidím, kde by tam měla být další vrstva.
Co znamená „obalovat ho ještě něčím z venku“? Je vytvoření třeba nějaké FACADE pro tebe obalování?
Vytvoření fasády bezesporu obalováním je :-) minimálně ve smyslu
„zapouzdření“. Může to samozřejmě mít svůj smysl, ale ten podle mě
u facade patternu spočívá především v definování API nezávislého na
spodní vrstvě fasády, případně ke spojení API a odpovídající
funkcionality rozložené do více služeb / nástrojů v jeden, pokud to má
smysl. Ale pořád se mi vnitřně ta entita „obrázek“ nechce rozpadnout na
„metadata entitu“ a „binární data obrázku“ sjednocené pod jednou
fasádou.. představ si stejnou situaci u článku: „článek“ jako takovej
je nepochybně ten samotnej text, kterej je v databázi uloženej ve sloupci
typu TEXT
, všechno okolo (datum publikování, ID autora, …)
jsou jen metadata. Měl by „článek“ bejt vlastně jen fasáda nad entitou
„metadata článku“ a nějakou jinou položkou „článek“? To by
nedávalo po žádný stránce smysl. Tak proč by to u obrázku (nebo obecně
objektu, kterej nechceme ukládat do databáze) mělo bejt nutně jinak, když
problém je jenom se samotnou „storage“ vrstvou?
To zní, jako kdyby si naznačoval, že co ti „zmizí“ z ORM, ti zmizí z modelu. Možná že se mýlím, ale tento způsob chápání ORM jako „celé“ modelové vrstvy, mi přijde jako poněkud „divná“ myšlenka.
No to právě viz výše. Když už celej ORM pracuje s entitama, což je pro mě osobně tak nějak to, s čím chci pracovat vně modelu, přijde mi nepřehledný a z pohledu objemu kódu / práce zbytečný obalovat každou jednotlivou entitu ještě nějakou fasádou (dělat to jen u jedný by nebylo konzistentní), abych zajistil funkci, která podle mě už tak nějak uvnitř tý stávající struktury má bejt..
Ale ať zas tak moc neteologizujem, nehádám se, že by Lean Mapper měl umět ukládat data kamkoliv a jakkoliv nebo že bych na něm obecně něco měnil, spíš mě jen zajímalo, jak se tohle podle konvencí Lean Mapperu správně řeší – tj. je nějaká cesta, jak Lean Mapper používat přímo jako modelovou vrstvu a pokud ano, kam a jak tedy patří zpracovávání takovéhohle typu dat? Nebo to prostě takhle být nemá a Lean Mapper má sloužit jen jako jedna ze „spodních vrstev“ Modelu s velkým M, kterej to má celý obalit?
A tady je další otázka z trochu jiného soudku: lokalizace. Ne,
nekřičte, četl jsem tohle vlákno asi celé a vím, že se to řešilo. Mám
na to řešení ale trochu větší nároky. Východisko je stejné: pro
lokalizovanou entitu mám dvě tabulky, jednu s nepřekládaným obsahem a
jednu navázanou přes cizí klíč s lokalizovanými variantami
překládaného obsahu (tj. unique klíč nad entity_id
,
language_code
). Jako vstupní parametr při nasosávání entity
ale nemám jeden jazyk, nýbrž pole jazyků seřazených podle preferencí
uživatele; výstupem má být entita, která tohle kritérium splňuje nejlíp,
jak to jde.
Příklad: uživatel preferuje jazyky en, cs a de v tomhle pořadí. Článek má dostupné překlady pro cs, sk a ru. Vrácená entita tedy má být lokalizována do češtiny. Pokud by ovšem článek žádný z uživatelem preferovaných překladů neměl, měla by se vrátit entita lokalizovaná řekněme do první dostupné jazykové verze (takže třeba sk). Tohle umím jedním dotazem (je to trochu mysql magie, ale to mi pro můj use-case nevadí).
Jak mám ale s entitou pracovat v hypotetickém administračním rozhraní, kde admin v jednom formuláři může vytvářet překlady pro všechny webem podporované jazykové mutace? Formulář má část prvků pro nepřekládané property entity a pak má kontejnery s překladovými variantami. Najednou bych chtěl místo:
<?php
$entity->title = '..';
$entity->content = '..';
?>
použít něco jako:
<?php
foreach ($langs as $lang) {
$entity->$lang->title = '...';
$entity->$lang->content = '...';
}
?>
Ono by to teda asi nebylo takhle přímo na entitě, ale spíš v nějakém
poli $entity->contents[$lang]
, ale jde o jinou věc – při
čtení entity vůbec nemám žádné pole contents
a místo něj
mám property title
, content
…, naopak při zápisu
nemám tyhle property a mám pole contents
.. jak na to? :-)
Editoval danik (6. 2. 2014 3:14)
před 6 lety
- Šaman
- Člen | 2275
Jenomže tady dochází k drobnému zmatku v terminologii. To
R v ORM neznačí relaci objektů (jako např. ER diagram),
ale relační
databázi. Jedná se skutečně o mapování objektů do tabulek (relací)
a zpátky.
Když se v přednáškách o pětivrstvém modelu řeší mappery pro různá
úložiště, tak to vlastně znamená naučit model používat
společná rozhraní pro ORM a jiné mappery
pro specifická úložiště (memcache, filesystem). Taky už jsem narazil na
problémy, kdy mi v modelu existovaly entity které nepocházely z databáze,
ale pokud používáš jen ORM (což LM je), tak je logické, že se ti je
nepodaří podchytit.
Entita z ER diagramu totiž nutně neznamená databázová
tabulka a tedy dědit třídy entit od ORM entit je zjednodušení, které nám
sice často stačí, ale může nás i omezovat.
Ty chceš nějaký O-E Mapper, tedy mapování entit na objekty a zpátky. To je ale ještě o jednu abstrakci výše, než ORM. Pokud se ti ale všechny entity vejdou do databáze, tak se celý problém redukuje na ORM.
Editoval Šaman (6. 2. 2014 5:39)
před 6 lety
- batko
- Člen | 219
Pročítám toto forum a mám následující dotaz.
Jakým způsobem vyřešit následující problem:
Mám knihy a ty mají tagy. A rád bych dostal jen knihy, které tagy nemají.
Řeší se toto již v repository? Nebo to mohu „vyfiltrovat“ až po načtení entit? Čtu zde o filtrech, je to správné řešení?
Děkuji
před 6 lety
- duke
- Člen | 637
@batko Dle mého názoru by to mělo řešit repository, a sice buď speciální metodou nebo interpretací nějakého query objektu, tak aby se to filtrování řešilo už na úrovni databáze. Z hlediska databázového dotazu na to můžeš jít buď nějak takto:
SELECT * FROM book WHERE id IS NOT IN (SELECT book_id FROM book_tag)
… nebo takto (toto řešení bude nejspíš v MySql o něco rychlejší):
SELECT book.*, MIN(book_tag.tag_id) AS firstTag FROM book LEFT OUTER JOIN book_tag ON book.id=book_tag.book_id GROUP BY book.id HAVING firstTag IS NULL
Ty dotazy jsem netestoval, takže bez záruky…
před 6 lety
- Tharos
- Člen | 1042
@danik: ORM skutečně znamená objektově relační mapování.
Pokud bys chtěl mít data uložená nějak „hybridně“ (něco v relační databází, něco třeba v nějakém XML na disku, něco v nějakých souborech…), tak vše, co budeš potřebovat vyřešit, už za Tebe bohužel ORM nevyřeší. A nebude toho málo, hlavně bude pracné udržovat konzistenci dat (referenční integritu). Určitě se v takovém případě neobejdeš bez dalších vrstev nad nějakým ORM.
Nejen z výše uvedených důvodů se podobné problémy často řeší právě tak, že třeba i Tebou uvedené obrázky jsou „mapovány“ v relační databázi a na disku (nebo v nějaké CDN) se už jen udržují související binární data. Je to velmi výhodné a vcelku standardní řešení.
V Lean Mapperu není problém mít entitu Image
, která bude
mít například položku uri
. Jelikož entity v Lean Mapperu
mohou být „chytré“, není problém si do takové entity injektnout
službu, jejíž zdopovědností bude formulování správného uri.
S ukládáním a následným managementem těch dat mimo relační databázi Ti ale Lean Mapper příliš nepomůže, Lean Mapper je prostě ORM. Může jít o velmi pestrou činnost (ukládání někam na disk vs. upload do CDN…) a je právě na programátorovi, aby tohle v rámci modelu elegantně vyřešil. :) Lean Mapper Ti jen vychází vstříc vcelku flexibilními repositáři, do kterých se lze snadno „nahookovoat“, anebo systémem událostí.
před 6 lety
- batko
- Člen | 219
Začal jsem celkem úspěšně používat LM a jsem moc spokojen.
Nyní řeším následující.
Mám entitu Phrase a do property next a prev bych potřeboval dostat entitu Phrase nacházející se v DB před a za aktuální Phrase. Bohužel se mi do těchto property nedaří entity dostat.
<?php
/**
* @property int $id
* @property string $text
* @property DateTime $inserted
* @property User $user m:hasOne
* @property Tag[] $tags m:hasMany(:::tag)
* @property Phrase $next
* @property Phrase $prev
*/
?>
před 6 lety
- Tharos
- Člen | 1042
@batko: Luxusní požadavek, to tu ještě nebylo :).
Nad SQLite databází, která je součástí Lean Mapperových testů, bych to osobně řešil následovně:
/**
* @property int $id
* @property string $name
* @property string $pubdate
* @property-read Book|null $previous m:hasOne(previous)
* @property-read Book|null $next m:hasOne(next)
*/
class Book extends \LeanMapper\Entity
{
}
class BookRepository extends \LeanMapper\Repository
{
/**
* @return Book[]
*/
public function findAll()
{
return $this->createEntities(
$this->createFluent()->fetchAll()
);
}
}
class Mapper extends DefaultMapper
{
protected $defaultEntityNamespace = null;
/** @var Connection */
private $connection;
/**
* @param Connection $connection
*/
public function __construct(Connection $connection)
{
$this->connection = $connection;
}
/**
* @param string $entityClass
* @param Caller $caller
* @return mixed
*/
public function getImplicitFilters($entityClass, Caller $caller = null)
{
if ($entityClass === 'Book') {
return function (Fluent $fluent) {
$fluent->select(
$this->connection->select('prevbook.id')->from('[book] [prevbook]')
->where('[prevbook.id] < [book.id]')
->orderBy('id')->desc()->limit(1)
)->as('previous')
->select(
$this->connection->select('nextbook.id')->from('[book] [nextbook]')
->where('[nextbook.id] > [book.id]')
->orderBy('id')->limit(1)
)->as('next');
};
}
return parent::getImplicitFilters($entityClass, $caller);
}
}
Připravíme-li si poté následující testovací skript:
$mapper = new Mapper($connection);
$bookRepository = new BookRepository($connection, $mapper, $entityFactory);
foreach ($bookRepository->findAll() as $book) {
echo "[$book->id] $book->name\n";
if (isset($book->previous)) {
echo str_repeat(' ', 5) . "[{$book->previous->id}] {$book->previous->name}\n";
}
if (isset($book->next)) {
echo str_repeat(' ', 5) . "[{$book->next->id}] {$book->next->name}\n";
}
}
Dostaneme takovýto výstup:
[1] The Pragmatic Programmer
[2] The Art of Computer Programming
[2] The Art of Computer Programming
[1] The Pragmatic Programmer
[3] Refactoring: Improving the Design of Existing Code
[3] Refactoring: Improving the Design of Existing Code
[2] The Art of Computer Programming
[4] Introduction to Algorithms
[4] Introduction to Algorithms
[3] Refactoring: Improving the Design of Existing Code
[5] UML Distilled
[5] UML Distilled
[4] Introduction to Algorithms
S tím, že se vykonají následující dotazy:
SELECT [book].* , (SELECT [prevbook].[id] FROM [book] [prevbook] WHERE [prevbook].[id] < [book].[id] ORDER BY [id] DESC LIMIT 1) AS [previous] , (SELECT [nextbook].[id] FROM [book] [nextbook] WHERE [nextbook].[id] > [book].[id] ORDER BY [id] LIMIT 1) AS [next] FROM [book]
SELECT [book].* , (SELECT [prevbook].[id] FROM [book] [prevbook] WHERE [prevbook].[id] < [book].[id] ORDER BY [id] DESC LIMIT 1) AS [previous] , (SELECT [nextbook].[id] FROM [book] [nextbook] WHERE [nextbook].[id] > [book].[id] ORDER BY [id] LIMIT 1) AS [next] FROM [book] WHERE [book].[id] IN (1, 2, 3, 4)
SELECT [book].* , (SELECT [prevbook].[id] FROM [book] [prevbook] WHERE [prevbook].[id] < [book].[id] ORDER BY [id] DESC LIMIT 1) AS [previous] , (SELECT [nextbook].[id] FROM [book] [nextbook] WHERE [nextbook].[id] > [book].[id] ORDER BY [id] LIMIT 1) AS [next] FROM [book] WHERE [book].[id] IN (2, 3, 4, 5)
Věřím, že mé myšlenkové pochody jsou z té ukázky dobře patrné.
A teď ještě takové malé zamyšlení… Ty dotazy nejsou úplný skvost a také je nehezké, že to není lazy. ID předchozí a následující knihy se zjišťuje bez ohledu na to, zda bude zapotřebí. Jenomže udělat to úplně lazy asi není principiálně dost dobře možné… Nabízí se možnost ta ID dopředu nezjišťovat, ale pak by je bylo nutné nějak rekonstruovat při traverzování. To by ale asi nebylo vzhledem k „NotORM“ stylu dotazování možné…
Východiskem je samozřejmě udržování těch ID „předvypočtených“ přímo v databázi. Pokud by se výše uvedené řešení ukázalo jako problematické z výkonnostního hlediska, nebál bych se toho. Integritu lze s použitím Lean Mapperu zaručit (i když ony ty dotazy budou rychlé, ke všemu se použijí indexy).
Edit: Samozřejmě by také šlo mít položky
$previous
a $next
až v nějakém potomkovi
Book
(třeba TraversableBook
?) a v případech, kdy
se dopředu ví, že toto traverzování nebude zapotřebí, by se mohlo
pracovat s entitami Book
. Je to ale už trochu
překombinované…
Mimochodem netvrdím, že neexistuje nějaké celkově lepší řešení. :)
Editoval Tharos (7. 2. 2014 23:57)
před 6 lety
- Ripper
- Člen | 56
Zdravím,
mohl by mi někdo poradit, jak efektivně zaregistrovat filtr? Zkoušel jsem to v Repozitáři (hlavním), ale tam to hlásí chybu, že už je filtr jednou zaregistrovaný (což chápu). Kde ho tedy registrovat?
Díky.
před 6 lety
- Tharos
- Člen | 1042
@Ripper: Ahoj, já se s tím moc nepářu a filtry
registruji v bootstrap.php
… Netvrdím ale, že je to
ideální.
Lze je registrovat všude možně: v nějaké samostatné, k tomu určené
třídě, v nějakém LeanMapperExtension
kompileru… poslední
zmíněná varianta by se mi líbila asi nejvíce, ale sám takové
rozšíření napsané nemám. :–P
před 6 lety
- Tharos
- Člen | 1042
Tharos napsal(a):
Mimochodem netvrdím, že neexistuje nějaké celkově lepší řešení. :)
Tak nakonec existuje. :) Lze na to jít velmi výhodně „z druhé strany“:
/**
* @property int $id
* @property string $name
* @property string $pubdate
* @property-read Book|null $previous m:belongsToOne(*next) m:useMethods
* @property-read Book|null $next m:belongsToOne(*previous) m:useMethods
*/
class Book extends Entity
{
public function getPrevious()
{
return $this->getValueByPropertyWithRelationship('previous', new Filtering(function (Fluent $fluent) {
$fluent->select(
$fluent->createSelect('nextbook.id')->from('[book] [nextbook]')
->where('[nextbook.id] > [book.id]')
->orderBy('id')->limit(1)
)->as('next');
}));
}
public function getNext()
{
return $this->getValueByPropertyWithRelationship('next', new Filtering(function (Fluent $fluent) {
$fluent->select(
$fluent->createSelect('prevbook.id')->from('[book] [prevbook]')
->where('[prevbook.id] < [book.id]')
->orderBy('id')->desc()->limit(1)
)->as('previous');
}));
}
}
class BookRepository extends \LeanMapper\Repository
{
public function findAll()
{
return $this->createEntities(
$this->createFluent()->fetchAll()
);
}
}
No a to je vše. Při následujícím testu:
$bookRepository = new BookRepository($connection, $mapper, $entityFactory);
foreach ($bookRepository->findAll() as $book) {
echo "[$book->id] $book->name\n";
if (isset($book->previous)) {
echo str_repeat(' ', 5) . "[{$book->previous->id}] {$book->previous->name}\n";
}
if (isset($book->next)) {
echo str_repeat(' ', 5) . "[{$book->next->id}] {$book->next->name}\n";
}
}
Vypíše se:
[1] The Pragmatic Programmer
[2] The Art of Computer Programming
[2] The Art of Computer Programming
[1] The Pragmatic Programmer
[3] Refactoring: Improving the Design of Existing Code
[3] Refactoring: Improving the Design of Existing Code
[2] The Art of Computer Programming
[4] Introduction to Algorithms
[4] Introduction to Algorithms
[3] Refactoring: Improving the Design of Existing Code
[5] UML Distilled
[5] UML Distilled
[4] Introduction to Algorithms
A vykonají se následující dotazy:
SELECT [book].* FROM [book]
SELECT [book].* , (SELECT [nextbook].[id] FROM [book] [nextbook] WHERE [nextbook].[id] > [book].[id] ORDER BY [id] LIMIT 1) AS [next] FROM [book] WHERE [next] IN (1, 2, 3, 4, 5)
SELECT [book].* , (SELECT [prevbook].[id] FROM [book] [prevbook] WHERE [prevbook].[id] < [book].[id] ORDER BY [id] DESC LIMIT 1) AS [previous] FROM [book] WHERE [previous] IN (1, 2, 3, 4, 5)
S tím, že ten druhý a třetí je volán lazy až v okamžiku, kdy se k relevantním položkám přistupuje.
Funguje to ale zatím pouze v této branši. Musel jsem kvůli tomu dodělat podporu pro zápis aliasů ve vazebních sloupcích. Mergnu to, až k tomu ještě dopíšu pár testů…
Co mám těmi aliasy na mysli? Pokud v příznaku vyjadřujícím vazbu
zapíšu nějaký vazební sloupec
s hvězdičkou na začátku (například
m:belongsToOne(*previous)
), Lean Mapper nesestaví dotaz
... WHERE table.column IN ...
, nýbrž
... WHERE column IN
. Lze se takto výhodně odkázat na aliasy,
které vzniknou v SELECT klauzuli. Využití je hezky patrné z té mé
ukázky.
Editoval Tharos (10. 2. 2014 14:19)
před 5 lety
- batko
- Člen | 219
@Tharos
ta druhá varianta je o poznání přívětivější :-) děkuji moc, jdu to vyzkoušet
před 5 lety
- Petr Daňa
- Člen | 106
Ahoj, delší dobu jsem LeanMapper neviděl, teď jsem se k němu vrátil a nevím si rady s jednou situací. Mám tabulky Faultbook a User, k nim mám „vazební“ UserUnreadFaultbook. Vazební je v uvozovkách, protože se jedná spíše o příznakovou tabulku, která obsahuje pro konkrétního uživatele seznam Faultbook.id, které ještě neviděl. Ta vazební tabulka má dva sloupce „user_id“ a „faultbook_id“.
Snažil jsem se přidat do entity pro Faultbook vazbu „hasOne“, která by mi v podstatě řekla, jestli záznam existuje nebo ne, zkoušel jsem hasMany, ale bez výsledku. Vždycky vyhodil výjimku. Přikládám definice vazeb a k nim vždy výpis výjimek:
@property-read UserUnreadFaultbook $isUnread m:hasOne(id:user_unread_faultbook) m:filter(user)
Unknown column 'user_unread_faultbook.id' in 'where clause'
SELECT `user_unread_faultbook`.*
FROM `user_unread_faultbook`
WHERE `user_unread_faultbook`.`id` IN (4, 3, 2, 1) AND user_id = 1
@property-read UserUnreadFaultbook $isUnread m:hasOne(faultbook_id:user_unread_faultbook) m:filter(user)
Undefined index: faultbook_id
(v Result->extractIds())
Podobně na tom byly pokusy s hasMany vazbou.
Jak to teda udělat správně?
před 5 lety
- tomas.lang
- Člen | 54
@Tharos: chtěl jsem se zeptat jak to vypadá se samostatným LM fórem, případně dá se s tím nějak pomoct? Přeci jen dotazy na LM začínají dosti zasahovat do Nette fóra a asi to není úplně žádoucí…
před 5 lety
- Pavel Macháň
- Člen | 285
@Tharos poslal sem PR – https://github.com/…pper/pull/26 Narazil jsem na problém při registraci filtrů
Pokud není možné registrovat filtr při vytváření connection z důvodu kruhové závislosti je potřeba filter registrovat až za běhu (třeba v repository) a je nutné mít možnost zkontrolovat jeho existenci.
Příklad: potřebujeme zaregistrovat překladové filtry, které využívají LanguageFacade (má za úkol držet seznam jazyků, který načteme při inicializaci a zněhož dále získáváme pro překlady požadovaný jazyk). LanguageFacade (LanguageRepository) má závislost na Connection (kruhová závislost Filter > Connection > Filter). Překladové filtry zaregistruji v Abstract TranslationRepository, který využíváme pro překladové repositáře. TranslationRepository se může použít za 1 request vícekrát a connection by vyhazoval při registerFilter – InvalidArgumentException. Pomocí této vyjímky by se to dalo ošetřit, ale registrace filtru tuto vyjímku vyhazuje při více scénářích.
Editoval EIFEL (17. 2. 2014 12:04)
před 5 lety
- batko
- Člen | 219
Ahoj,
chtěl bych do selectu formuláře v nette přidat prvky. Jak z pole entit udělám pole $key ⇒ $val existuje na to nějaká metoda nebo je třeba pro iterovat pole entit? děkuji
před 5 lety
- Michal III
- Člen | 84
@batko:
Ahoj,
odkážu Tě sem, kde se i o pár příspěvků dál nachází rozřešení.
Jenom upozorňuji, že nyní se protected
metoda createCollection
nachází ve tříde DefaultEntityFactory
, kterou sis nejspíše
podědil.
před 5 lety
- batko
- Člen | 219
Michal III napsal(a):
@batko:
Ahoj,
odkážu Tě sem, kde se i o pár příspěvků dál nachází rozřešení.
Jenom upozorňuji, že nyní se
protected
metodacreateCollection
nachází ve třídeDefaultEntityFactory
, kterou sis nejspíše podědil.
Super dík moc.
Ještě malý dotaz.
Odešlu formulář a mám v něm multiselect který mi vrací pole hodnot. Ale já bych potřeboval, aby vracel pole objektů aby se to mohlo rovnou persistovat.
Mám entitu článek a ta má entity gallery[]. A ty gallery vyberu v multiselectu a odešlu.
Editoval batko (18. 2. 2014 21:50)
před 5 lety
- batko
- Člen | 219
Začínám být lehce nešťastný :-)
co je špatně na tomto kodu
<?php
$page = new \Model\Entity\Page();
$page->inserted = new \DateTime();
//chyba je někde zde
$gallery = $this->galleryRepository->find(1);
$page->gallery[] = $gallery;
$page->user = $this->userRepository->find(1);
$this->pageRepository->persist($page);
?>
vyhazuje
Missing entity factory in Model\Entity\Page.
před 5 lety
- Tharos
- Člen | 1042
Ahoj, omlouvám se za trochu zpožděnou reakci, mám teď trochu méně prostoru…
Protože už to tady nechci moc SPAMovat, chci všem nabídnout support na
Skype v.kohout
nebo na Jabberu tharos@jabber.cz
. Do
té doby, než bude mít Lean Mapper své vlastní fórum.
Díky!
před 5 lety
- Šaman
- Člen | 2275
Nebuď měkkej, tu tisícovku dáme :)
Myslím, že Davidovi spíš vadí, když se zakládají nová témata a LM se
pak rozlézá po fóru. Když bude všechno v tomhle jednom, tak je fajn, že
v případě hledání něčeho ohledně LM to asi bude někde tu. A je
šance, že někdo odpoví rychleji, než budeš mít čas ty.
A vím, že to musí byt děsná pruda, něco jako sepisování textu
k diplomce, ale – jak to vypadá s dokumentací?
Kdyz ji uděláš na způsob wiki, tak ti buď můžeme zkusit pomoct (i když
realita nikdy není tak růžová), nebo třeba hlasovat která kapitola je
aktuálně nejdůležitější. Trochu interagovat, ať máš větší motivaci
a nepřipadá ti, že si ji píšeš pro sebe…
před 5 lety
- castamir
- Člen | 631
Zveřejnil jsem rozšíření LM
Jde o přidání Query objectu, vlastního mapperu zohledňující namespace
Entit/Repozitářů v submodulech, repozitář pracující s Query objektem a
překládajícím názvy property na sloupce databáze.
Readme s popisem všech fičur přidám večer, nejpozději zítra…
Upozorňuji ale, že nepoužívám nejnovější (nezdokumentované) úpravy LM (používám verzi 2.0.1), takže negarantuju, že to pojede na novějších. Pokud budu vědět, jak to zprovoznit pro aktuální verzi LM, tak udělám druhou větev, kde ty změny zapracuju…
před 5 lety
- Tharos
- Člen | 1042
@castamir: Vypadá to moc hezky, díky za zveřejnění! Sešlo se mi teď nějak moc práce, ale jakmile budu mít trochu volněji, s Tvým rozšířením se rozhodně blíže seznámím.
@Šaman: Tak tisícovku asi jo :). Psaní docky je opravdu děsná pruda. Wiki na GitHubu je zapnutá. Právě už mě někdo prosil, jestli bych ji zapnul, a tak jsem ji zapnul. Čímž to samozřejmě skončilo. :)
Zajímalo by mě, jak by se nejlépe dala komunitně napsat dokumentace. Matně si vzpomínám, že třeba u Nette ta komunita v případě docky vcelku selhala, ale už nevím, co byla primární příčina. Řekněme, že bych nadhodil strukturu dokumentace a ke každé kapitole vypsal body, co by tak ideálně měla obsahovat, aby bylo vše pokryté. Najde se někdo, kdo by to pak pro Lean Mapper napsal? :) Já jsem skeptik, ale tak pro jistotu se ptám. :) Co mohu nabídnout je mentoring, přiznání autorství a taky to, že nebudu moc prudit (i kdyby to nebylo napsáno perfektně, jakékoliv zpracování je lepší než žádné).
před 5 lety
- Michal III
- Člen | 84
@Tharos: Já bych určitě rád něčím do dokumentace přispěl a doufám, že to není jenom planý slib. Mít k dispozici hlavní témata a nějaké body bude moc fajn → rád plním body, pokud zrovna ten bod není „napsat dokumentaci k LM“ :-).
před 5 lety
- castamir
- Člen | 631
@Tharos s tou wiki to byla narážka na mě, co?
:D
Demo apku, kde můžu ukázat spoustu (ale zdaleka ne vše), mám nachystanou.
Řeším tam v podstatě už jen kravinky. Ale nebude to tutoriál vyloženě
na LeanMapper, ale na Nette a různé doplňky a LM je tam jako podpora pro
práci s db.
před 5 lety
- tomas.lang
- Člen | 54
@Tharos: pokud by existoval nějaký seznam bodů, také rád k dokumentaci přispěji… :-)
před 5 lety
- castamir
- Člen | 631
pár věcí, které mě napadají a které stojí za zdokumentování
- celých changelog (probrat co je ještě aktální a co už ne)
- injektování služeb do entit (taková ta věc, které vůbec neholduju ale budiž)
- filtry (důkladně popsat)
- popsat jak na ‚ruční‘ vazby (např jak získat z vazební tabulky article_tag 10 nejčastějších tagů a jejich počet, aniž bych kvůli tomu musel speciálně vytvářet repozitář)
před 5 lety
- Pavel Macháň
- Člen | 285
@Tharos: Plánuješ do LeanMapperu přidat Extension
pro Nette? (dalo by se to tam jako bridge stylem co ma dibi). Zatím používám
svůj nedodělaný (pokud bych ho dodělal tak bych klidně poslal PR do LM
repa), který umožnuje více připojení a nastavení mapperu a filtrů pomocí
configu.
Momentálně zápis configu vypadá takto (zatím bez přidávání filtrů)
Pouze jedno připojení: @connection
leanmapper:
mapper: App\Mapper
entityFactory: App\EntityFactory
connection:
driver: mysql
host: localhost
username: USERNAME
password: PASSWORD
database: DATABASE
lazy: TRUE
Více připojení: @connection.forum, @connection.content
leanmapper:
mapper: App\Mapper
# pokud nenastavím mapper nebo entityFactory tak se pouzije defaultní z LM
connections:
forum:
driver: mysql
host: localhost
username: USERNAME
password: PASSWORD
database: DATABASE
lazy: TRUE
autowired: no
inject: no
# pokud nastavím mapper nebo entityFactory v určitém připojení, tak se použije místo globálního
entityFactory: App\EntityFactory
content:
driver: mysql
host: localhost
username: USERNAME
password: PASSWORD
database: DATABASE
lazy: TRUE
Editoval EIFEL (27. 2. 2014 14:38)
před 5 lety
- castamir
- Člen | 631
Nápad dobrej, akorát bych úplně škrtnul tu verzi s pouze jediným připojením. Jestli to odsadím o trochu víc nebo míň a přidám jeden řád je už jedno, ale hlavně to nikoho nebude plést kvůli jednomu „s“
před 5 lety
- Tharos
- Člen | 1042
Ad dokumentace) Tak super! Díky moc za podporu. Já se tedy ASAP pokusím připravit infrastrukturu. Mám vcelku pokročilou představu, jak by to celé mohlo fungovat, a vystačili bychom si jenom s Gitem (GitHubem). Mám teď opravdu hodně nabito, ale snad se k tomu přes víkend dostanu… Ty cca tři hoďky bych prostě měl najít.
Ad extenze) Já jsem samozřejmě rád za každou, jaká vznikne. :) Pokud by měla existovat nějaká „oficiální“, je k diskuzi, co by měla řešit. Jednou už jsem tu o tom někde psal. Diskuzi o tom už bych ale nechal na samostatné fórum… Tady by to bylo hezké s 1000. příspěvkem tak nějak opustit. ;)
před 5 lety
- duke
- Člen | 637
Používám následující entitu:
/**
* @property int $id
* @property \DateTime $start m:passThru(sanitizeDate)
* @property \DateTime $end m:passThru(sanitizeDate)
*/
class Exclusion extends \LeanMapper\Entity
{
protected function sanitizeDate($date)
{
if (!$date instanceof \DateTime) {
$date = new \DateTime('0000-01-01');
}
return $date;
}
}
Řeším tím problém, kdy mi Dibi v případě nulové hodnoty sloupce datetime v databázi vrací místo objektu typu \DateTime řetězec (což by se bez použití passThru projevilo chybou v očekávaném typu). Dá se toto řešit nějak globálněji, abych to nemusel psát ke všem entitám?
před 5 lety
- Šaman
- Člen | 2275
A zkoušel jsi tohle?
<?php
/**
* @property int $id
* @property \DateTime|string $start
* @property \DateTime|string $end
*/
?>
Editoval Šaman (4. 3. 2014 19:02)
před 5 lety
- duke
- Člen | 637
@Šaman Zkoušel. Způsobí to chybu:
Invalid property definition given: @property \DateTime|string $start
in entity …
Nicméně, i kdyby to fungovalo, tak by to ne zcela řešilo můj
problém.
PassThru řešení funguje; jen hledám jeho obecnou variantu, abych nemusel
psát passThru u všech sloupců typu \Datetime u všech entit.
před 5 lety
- Pavel Macháň
- Člen | 285
duke napsal(a):
@Šaman Zkoušel. Způsobí to chybu:
Invalid property definition given: @property \DateTime|string $start in entity …Nicméně, i kdyby to fungovalo, tak by to ne zcela řešilo můj problém.
PassThru řešení funguje; jen hledám jeho obecnou variantu, abych nemusel psát passThru u všech sloupců typu \Datetime u všech entit.
Musel by si upravit repository a tam kde se kontroluje typ mit vyjimku pro \DateTime aby když je null tak vrátit $date = new \DateTime(‚0000–01–01‘) (i když nechápu proč nevracíš NULL… přijde mě to víc logické než 0000–01–01 datum)
před 5 lety
- duke
- Člen | 637
@EIFEL Ten typ se kontroluje v
Entity::__get
a to je metoda, kterou není tak snadné přetížit
(volá private metody a vyhazuje poměrně obecné výjimky, takže by se musela
parsovat message výjimky, atp.). Toto by mělo jít řešit nějak
elegantněji.
NULL bych mohl vracet také, avšak někdy je rozdíl mezi nevyplněným
datumem a nulovým datumem (použitým např. jako minimální datum).
Striktně bych měl asi použít:
$date = new \DateTime($date);
… což by pro nulové datum vedlo na datum 30.11.-0001 (což odpovídá 0000–00–00, tj. 1 měsíc a 1 den před 1.1. roku 0).
Editoval duke (5. 3. 2014 13:18)
před 5 lety
- Tharos
- Člen | 1042
@duke: Vůbec jsem netušil, že Dibi v takové
situaci (datum „0000–00–00 00:00:00“) vrací string
. Nikdy
jsem na to nenarazil…
Tohle je souhra dvou „specialit“. Databáze umožní uložit uvedený datum, který je IMHO nesmyslný, a dibi z toho udělá string. Babo raď, co z toho udělat na úrovni entity…
Od stolu mě nenapadá žádné elegantní řešení aplikovatelné
v současné verzi. Popravdě řečeno já bych v tomhle případě měl
v databázi datum jako nullable
a zbytek se pokusil vyřešit
nějakou výchozí hodnotou a třeba příznakem, zda už datum byl nebo nebyl
nastaven…
Jak by sis představoval optimální řešení? Co třeba nějaký výchozí
passThru
podle typu položky?
Editoval Tharos (5. 3. 2014 14:52)
před 5 lety
- duke
- Člen | 637
@Tharos Problém jsi shrnul přesně. Nicméně mít nullable datetime jen kvůli tomu, že se tam může dostat tato speciální hodnota mi jako moc elegantní řešení nepřipadá.
Jinak já na tento problém narazil díky chybě ve validaci hodnot před uložením, takže se mi do databázové tabulky dostala tato speciální hodnota 0000–00–00 a to pak vedlo na chybu v LeanMapperu při následném načítání entit.
Implicitní passThru by toto mohl řešit a asi by našel i uplatnění jinde (když můžeme mít implicitní filtry, proč ne implicitní passThru…). Otázkou je, zda by neměl LeanMapper tento nesoulad dvou specialit řešit automaticky (ať už výchozí implementací tohoto implicitního passThru, či jinak).
před 5 lety
- jannek19
- Člen | 47
Ahoj, měl bych dotaz ohledně @property-read
, potřeboval bych
v entitě uchovávat položku created
, kterou nechci, aby kdokoli
z kódu měnil, ale zároveň tu hodnotu created
potřebuji sám
před uložením nějak nastavit – jak nejlíp na to? Nastavovat to nějak na
začátku persist()
metody v repozitáři (jak?), nebo přetížit
nějakou metodu v entitě a nastavovat to v ní? Jak něco takového obvykle
řešíte? Díky moc.
PS. pokud se to tu někde už řešilo, tak se omlouvám, prohledával jsem celé forum, ale odpověď na toto jsem nikde nenašel.
před 5 lety
- Pavel Macháň
- Člen | 285
jannek19 napsal(a):
Ahoj, měl bych dotaz ohledně
@property-read
, potřeboval bych v entitě uchovávat položkucreated
, kterou nechci, aby kdokoli z kódu měnil, ale zároveň tu hodnotucreated
potřebuji sám před uložením nějak nastavit – jak nejlíp na to? Nastavovat to nějak na začátkupersist()
metody v repozitáři (jak?), nebo přetížit nějakou metodu v entitě a nastavovat to v ní? Jak něco takového obvykle řešíte? Díky moc.PS. pokud se to tu někde už řešilo, tak se omlouvám, prohledával jsem celé forum, ale odpověď na toto jsem nikde nenašel.
Řeším to triggerem
CREATE TRIGGER `table_BINS` BEFORE INSERT ON `table` FOR EACH ROW
SET new.created = NOW();
před 5 lety
- jannek19
- Člen | 47
Vytvořit trigger by taky byla možnost, ale i když to má svou logiku (je to záležitost databáze a ta by se měla o správnost dat postarat), tak se mi vytváření triggeru kvůli nastavení jedné hodnoty příliš nelíbí, navíc tento sloupec mám skoro u všech tabulek, takže řešit to triggery se mi příliš nechce.
Aktuálně jsem to vyřešil přetížením metody
getModifiedRowData()
v entitě:
public function getModifiedRowData()
{
$data = parent::getModifiedRowData();
if ($this->isDetached()) {
$data['created'] = new \DateTime;
}
return $data;
}
Snad v tom není skrytý nějaký zádrhel.