Oznámení
před 6 lety
- Tharos
- Člen | 1042
@EIFEL:
Bude ještě nějaký zásadní BC break?
Nebude. :)
Právě jsem otevřel release větev 2.1.0
(dev-release-v2.1.0
v Composeru).
Těsně před tím jsem ještě lehce upravil Entity::__get
a
Entity::__set
tak, aby lépe pracovaly s položkami obsahujícími
kolekce (rozumějte pole), vylepšil jsem pár dalších chybových hlášek a
zrefaktoroval pár interních věcí…
Co se m:passThru
týče, nedávno představenou změnu
týkající se „nepoužití“ návratové hodnoty jsem revertoval a celé to
nechám ještě dozrát. Aktuálně platí, že se zaregistrované metody
volají před kontrolou hodnoty (to jsem z té novinky samozřejmě zachoval),
ale návratová hodnota té metody se pak použije. Takže i u jednoduchých
validačních funkcí je nutné předanou hodnotu zase vracet.
Co se BC breaků oproti poslední stable verzi týče, pár jich je, ale chybové hlášky v Lean Mapperu by teď měly být tak výřečné, že by vás knihovna měla úplně polopaticky navést k tomu, co je zapotřebí ve vašem kódu upravit. Myslím tedy, že se nikdo nemusí bát aktualizovat /a já budu rád, že se tak rychle odladí případné chyby ;)/.
Pokud nikdo nenarazí na žádný zásadní problém, sloučím to během pár dní do masteru.
Enjoy!
Edit: Pokud by se vám někomu hodila, zde je API dokumentace aktuální „RC“ verze.
Editoval Tharos (4. 12. 2013 11:10)
před 6 lety
- Michal III
- Člen | 84
@Tharos: Pokud se zavolá getData()
na
„detached“ entitě, výjimka je následující: Missing row with
id –1 or ‚id‘ column in that row. (řádek 202
v
LeanMapper/Result.php
).
před 6 lety
- Michal III
- Člen | 84
Nejspíše offtopic:
Jaký je best practise pro parametry metod „facade“? Mějme následující úkol: přidat autorovi knihu. Jak by měla vypadat taková metoda?
// Navázání na konkrétního autora vyřešit ve vyšší vrstvě.
public function addBook(Book $book)
{
return $this->bookRepository->persist($book);
}
// Navázání řešit oddělením parametrů.
public function addBookToAuthor(/*int|Author*/ $author, Book $book)
{
$book->author = $author;
return $this->bookRepository->persist($book);
}
// Pracovat nezávisle na entitách.
public function addBookToAuthor(/*int*/ $author, array $bookData)
{
$book = new Book($bookData);
// Buď využít upravené BaseEntity a předat jen (int)$id.
$book->author = $author;
// Nebo nejprve načíst entitu autora
$book->author = $this->authorRepository->get($author);
return $this->bookRepository->persist($book);
}
Používáte nějaké jiné řešení? Mně nejdříve dávalo největší smysl první zmíněné řešení, dokud jsem nenarazil na případ, kdy při vytvoření nové entity bylo třeba ještě spoustu dalších na ni navázat. V tu chvíli mi přišlo, že je to moc komplikované na to, aby to bylo řešeno v prezentéru. Děkuji za podněty.
před 6 lety
- Pavel Macháň
- Člen | 285
Michal III napsal(a):
Nejspíše offtopic:
Jaký je best practise pro parametry metod „facade“? Mějme následující úkol: přidat autorovi knihu. Jak by měla vypadat taková metoda?
// Navázání na konkrétního autora vyřešit ve vyšší vrstvě. public function addBook(Book $book) { return $this->bookRepository->persist($book); } // Navázání řešit oddělením parametrů. public function addBookToAuthor(/*int|Author*/ $author, Book $book) { $book->author = $author; return $this->bookRepository->persist($book); } // Pracovat nezávisle na entitách. public function addBookToAuthor(/*int*/ $author, array $bookData) { $book = new Book($bookData); // Buď využít upravené BaseEntity a předat jen (int)$id. $book->author = $author; // Nebo nejprve načíst entitu autora $book->author = $this->authorRepository->get($author); return $this->bookRepository->persist($book); }
Používáte nějaké jiné řešení? Mně nejdříve dávalo největší smysl první zmíněné řešení, dokud jsem nenarazil na případ, kdy při vytvoření nové entity bylo třeba ještě spoustu dalších na ni navázat. V tu chvíli mi přišlo, že je to moc komplikované na to, aby to bylo řešeno v prezentéru. Děkuji za podněty.
Prozatím sem v testovacím projektu (testování základních funkcí leanmapperu) používal tvojí první možnost
public function addBook(Book $book)
ale když to tak vidím, tak je ta „facade“ v tomto tvaru docela kničemu :) stejnak o většinu věcí by se musel starat pan prezentér.
Asi se nakonec přikláním k poslední možnosti, kde si složím data
třeba z odeslaného formuláře a dám nažrat fasádě ať se mě
o vytvoření entity a následné persistence postará ona.
Případně zkombinovat 1. a 3. možnost, že by bylo možné poslat pole nebo
rovnou entitu (kdyby se to prostě někde hodilo).
Editoval EIFEL (4. 12. 2013 22:22)
před 6 lety
- Jan Folwarczny
- Člen | 10
@Tharos:
Ahoj,
nejprve musím poděkovat Tharosovi za skvělou práci! Začínám u jednoho projektu LeanMapperem nahrazovat dosavadní ORM, se kterým jsem pořád nebyl spokojený a musím říct, že LeanMapper je přesně to, co jsem hledal nebo potřeboval. Takže ještě jednou díky! :)
Narazil jsem na jedno (pro mě) omezení v nastavování výchozích hodnot
pomocí anotací.
Chybí mi možnost nastavit výchozí hodnotu prázdný string a null.
/*
* @property string $text1 = ''
* @property string $text2 = ""
* @property DateTime|null $datetime = null
* @property int|null $count = NULL
*/
Procházel jsem zdrojáky aktuální 2.1 verze a tuhle podporu jsem tam nenašel. Dalo by se to přidat nebo to tam není z nějakého důvodu? :)
Editoval Folwik (5. 12. 2013 0:51)
před 6 lety
- Tharos
- Člen | 1042
@Michal III
Pokud se zavolá
getData()
na „detached“ entitě, výjimka je následující: Missing row with id –1 or ‚id‘ column in that row. (řádek202
vLeanMapper/Result.php
).
Díky moc za upozornění, chybovkou hlášku jsem vylepšil. (Proč ta chyba vzniká je, doufám, srozumitelné – čte se neinicializovaná položka.)
před 6 lety
- Tharos
- Člen | 1042
@Folwik:
nejprve musím poděkovat … LeanMapper je přesně to, co jsem hledal nebo potřeboval
Nemáš zač. Vážím si toho, že Ti Lean Mapper vyhovuje!
Narazil jsem na jedno (pro mě) omezení v nastavování výchozích hodnot pomocí anotací.
Chybí mi možnost nastavit výchozí hodnotu prázdný string a null.
Tohle je velmi dobrý postřeh. Není to záměr, prostě jsem na tyhle hodnoty nějak zapomněl. :–P
Hned jsem to opravil a pushnul na GitHub (je to v té release větvi). Díky za podnět!
před 6 lety
- Michal III
- Člen | 84
@Tharos: Jasné to je, děkuji :-).
před 6 lety
- Jan Folwarczny
- Člen | 10
@Tharos:
Tohle je velmi dobrý postřeh. Není to záměr, prostě jsem na tyhle hodnoty nějak zapomněl. :–P
Hned jsem to opravil a pushnul na GitHub (je to v té release větvi). Díky za podnět!
Paráda! Moc díky za rychlou reakci! :)
před 6 lety
- Jan Folwarczny
- Člen | 10
@Tharos:
Tharos napsal(a):
Narazil jsem na jedno (pro mě) omezení v nastavování výchozích hodnot pomocí anotací.
Chybí mi možnost nastavit výchozí hodnotu prázdný string a null.Tohle je velmi dobrý postřeh. Není to záměr, prostě jsem na tyhle hodnoty nějak zapomněl. :–P
Hned jsem to opravil a pushnul na GitHub (je to v té release větvi). Díky za podnět!
Po té úpravě jsem objevil další věc. :) Když se v anotacích nastaví výchozí hodnota null pro property, která není „basic type“, ale třeba DateTime, tak Lean Mapper to nedovolí a vyhodí vyjímku:
LeanMapper\Exception\InvalidAnnotationException: Invalid property definition given: @property DateTime|null $datetime = null in entity …Entity\MojeEntita, only properties of basic types may have default values specified. in …/libs/LeanMapper/Reflection/PropertyFactory.php:88
Nelze tedy nastavit defaultní null hodnotu v takové anotaci:
@property DateTime|null $datetime = null
Šlo by to upravit tak, že když je property „nullable“, tak by šla defaultní hodnota null nastavit i pro jiné než „basic“ typy?
Editoval Folwik (6. 12. 2013 9:58)
před 6 lety
- Michal III
- Člen | 84
@Tharos: „Typo“ na řádku 145
v
LeanMapper/Entity.php
– chybí mezera za
get_called_class()
.
před 6 lety
- Tharos
- Člen | 1042
@Folwik:
Šlo by to upravit tak, že když je property „nullable“, tak by šla defaultní hodnota null nastavit i pro jiné než „basic“ typy?
Samozřejmě, že šlo. Jde zase jenom o mojí lemplovitost, že jsem tohle nepodchytil hned. :) Opraveno na GitHubu.
@Michal III:
Super, díky, opraveno.
před 6 lety
- Ripper
- Člen | 56
Zdravím,
potřeboval bych menší radu, nevím jak skládat dotazy. Mám následující tabulky – warehouse(id, name), category(id, name), city(id,name), warehouse_city(id, city_id, warehouse_id), warehouse_category(id, category_id, warehouse_id). Ve spojovacích tabulkách spojuji kategorie a města s warehouse a já potřebuji například získat všechny warehouse které mají přiřazené city(id 1) a category(id 2), doufám že je to srozumitelné.
Díky.
před 6 lety
- elden46
- Člen | 37
Ahoj,
s LM zacinam, uz se pomalu dostavam se znalostmi jeho pouziti tak daleko, ze mi
zacina usnadnovat praci!:-)
Mam jeden dotaz (vlastne dva) a jednu prosbu. Takze dotaz:
V DB mam ulozeny datetime a chci ho vytahnout do aplikace jako Nette/Datetime, z LM dostavam DibiDateTime. Napsal jsem si tedy funkci toDateTime($value), ktera jen vrati new Nette\DateTime($value) a nadefinoval jsem si entitu nasledovne:
<?php
@property string $executed m:passThru(toDateTime)
?>
Tohle mi (dale v apúlikaci) funguje, ale vadi mi tam ta definice $executed jako string. Kdyz entitu zadefinuju jako
<?php
@property DateTime $executed m:passThru(toDateTime)
?>
tak mi pouziti vyhazuje LeanMapper\Exception\InvalidValueException – Property ‚executed‘ is expected to contain an instance of NewMS\Model\Nette\DateTime, instance of DibiDateTime
A ted prosba: dalo by se prosim zapracovat na nejake zakladni dokumentaci k novym vecem? Je super, ze se LM vyviji, ale dostat se k funkcnimu pouziti nejake funkcionality jen prochazenim tohoto olbrimiho vlakna zacina byt dost obtizne. Potreboval bych si nedefinovat nejake filtry (asi), ale od jejich oznameni nekde na treti strane tohoto vlakna se toho zmenilo tolik, ze to nejsem schopen rozchodit:)
Tedy predpokladam, ze jde o filtry – nasledujici situace: Mam entitu User, ktera k sobe muze mit vazbou 1:1 pripojeny nejake potvrzeni (dalsi entita obsahujici datum, status, typ potvrzeni). V DB je to reprezentovano tabulkou dt_user s vazbou na tabulku User pres user_id.
V entite User mam definovanu promennou $confirmed
<?php
* @property Confirmation $confirmed m:belongsToOne(user_id:dt_user)
?>
ale potreboval bych, aby LM do $confirmed vzdy dotahnul jen zaznam z dt_user s typem ‚CONFIRMED‘ (existuji jeste dalsi typy, treba ‚REGISTERED‘ a nektere muzou byt vazbou 1:N).
Jsem uplne mimo?:-)
před 6 lety
- Michal III
- Člen | 84
@elden46:
Ad dotaz: Takhle od oka se zdá, že by pomohlo, kdybys
před definicí entity měl use DateTime
, nebo u definic
property
měl \DateTime
. Bez toho se totiž třída
DateTime
hledá v aktuálním namespace, což je ve Tvém
případě nejspíše NewMS\Model\Nette
.
Ad filtry: Odkážu Tě například sem. Snad Ti to pomůže. Měla by tam být ukázka používání filtrů.
před 6 lety
- elden46
- Člen | 37
@Michal III:
dotaz: Ha, cist si vyhozene vyjimky by mi taky mohlo pomoct,
zejo? Zpetnelomitkova magie… spatne pouzity use, diky za
pohled zvenci
filtry: diky moc! Tenhle post jsem minul, nastuduju to.
Za dokumentaci s nejakymi bestpractises se ale stejne vsema deseti primlouvam:-)
před 6 lety
- Ripper
- Člen | 56
Ještě bych měl snad už poslední dotaz na který nevím odpověď. Jak zaregistruji filtr a nechci to dělat přes config.neon? Jak se dostanu k LeanMapper\Connection abych ho mohl správně zaregistrovat?
před 6 lety
- Pavel Macháň
- Člen | 285
Ripper napsal(a):
Ještě bych měl snad už poslední dotaz na který nevím odpověď. Jak zaregistruji filtr a nechci to dělat přes config.neon? Jak se dostanu k LeanMapper\Connection abych ho mohl správně zaregistrovat?
Já to registruju v repozitáři, protože zase nevím jak to registrovat přez config :)
class TranslationRepository extends Repository {
/**
* @param LM\Connection $connection
* @param ITranslatingMapper $mapper
* @param LM\IEntityFactory $entityFactory
*/
public function __construct(LM\Connection $connection, ITranslatingMapper $mapper, LM\IEntityFactory $entityFactory) {
$translator = new Filter\Translator($mapper, $languageFacade);
$connection->registerFilter('translate', array($translator, 'translate'));
$connection->registerFilter('translateFromEntity', array($translator, 'translateFromEntity'), LM\Connection::WIRE_ENTITY | LM\Connection::WIRE_PROPERTY);
parent::__construct($connection, $mapper, $entityFactory);
}
}
Editoval EIFEL (8. 12. 2013 12:29)
před 6 lety
- Tharos
- Člen | 1042
elden46 napsal(a):
Za dokumentaci s nejakymi bestpractises se ale stejne vsema deseti primlouvam:-)
Abych měl nad sebou větší bič a bylo vidět, jak dokumentace přibývá (nebo nepřibývá…), založil jsem dnes repositář, do kterého budu veřejně pushovat nejrůznější „útržky obsahu“ webu o Lean Mapperu 2.
Jedná se o útržky, které až v další etapě budu nějak strojově zpracovávat a které se ještě nějakou chvíli budou měnit pod rukama, ale pevně věřím, že už v neučesané podobě budou mít svou informační hodnotu a budou pro zájemce přínosné.
Aktuálně se v repositáři nachází kousek nového quick startu, na kterém právě pracuji. Mohlo by vás zajímat, že už nyní se v repositáři nachází de facto finální model, ke kterému se v quick startu iterativně dopracuji (spolu s ukázkami jeho použití).
S nadšením vítám jakoukoliv zpětnou vazbu: názory, korektury, tipy, konstruktivní kritiky… Díky!
Editoval Tharos (8. 12. 2013 23:02)
před 6 lety
- fast.solcik
- Člen | 3
Zdravím, toto je můj první post tu na fóru, tak snad s ním nic
nezkazím.
LeanMapper sleduji od samého začátku a tak přihazuji můj config.
parameters:
database:
driver:
host:
port:
username:
password:
database:
lazy: true
profiler: true
services:
defaultMapper: LeanMapper\DefaultMapper
entityFactory: LeanMapper\DefaultEntityFactory
filterClass: Model\Filter\Filter( defaultMapper )
connection:
class: LeanMapper\Connection( %database%, ... )
setup:
- registerFilter('filterOne', [@filterClass, 'filterOne'], ep)
- Model\Repository\Repository( connection, defaultMapper, entityFactory )
EIFEL napsal(a):
Ripper napsal(a):
Ještě bych měl snad už poslední dotaz na který nevím odpověď. Jak zaregistruji filtr a nechci to dělat přes config.neon? Jak se dostanu k LeanMapper\Connection abych ho mohl správně zaregistrovat?
Já to registruju v repozitáři, protože zase nevím jak to registrovat přez config :)
class TranslationRepository extends Repository { /** * @param LM\Connection $connection * @param ITranslatingMapper $mapper * @param LM\IEntityFactory $entityFactory */ public function __construct(LM\Connection $connection, ITranslatingMapper $mapper, LM\IEntityFactory $entityFactory) { $translator = new Filter\Translator($mapper, $languageFacade); $connection->registerFilter('translate', array($translator, 'translate')); $connection->registerFilter('translateFromEntity', array($translator, 'translateFromEntity'), LM\Connection::WIRE_ENTITY | LM\Connection::WIRE_PROPERTY); parent::__construct($connection, $mapper, $entityFactory); } }
Editoval fast.solcik (9. 12. 2013 23:15)
před 6 lety
- Pavel Macháň
- Člen | 285
fast.solcik napsal(a):
Zdravím, toto je můj první post tu na fóru, tak snad s ním nic nezkazím.
LeanMapper sleduji od samého začátku a tak přihazuji můj config.parameters: database: driver: host: port: username: password: database: lazy: true profiler: true services: defaultMapper: LeanMapper\DefaultMapper entityFactory: LeanMapper\DefaultEntityFactory filterClass: Model\Filter\Filter( defaultMapper ) connection: class: LeanMapper\Connection( %database%, ... ) setup: - registerFilter('filterOne', [@filterClass, 'filterOne'], ep) - Model\Repository\Repository( connection, defaultMapper, entityFactory )
Takže v mém případě bych si musel přepsat extension :/
$configurator->onCompile[] = function ($configurator, $compiler) {
$compiler->addExtension('leanmapper', new LeanMapper\Nette21Extension());
};
Config
leanmapper:
database:
driver:
host:
username:
password:
database:
lazy:
Editoval EIFEL (10. 12. 2013 1:01)
před 6 lety
- Tharos
- Člen | 1042
Ahoj,
nedá mi jedna taková věc. Od začátku, kdy jsem vypustil Lean Mapper ven, doslova prahnu po konstruktivní kritice, protože díky ní poznávám jiné pohledy na věc a knihovně to zásadním způsobem prospívá. Díky za každou takovou!
Když v dnešních dnech sleduji, co kdo na Lean Mapperu kritizuje (a sleduji to z výše uvedených důvodů se zájmem), bohužel mi přijde, že se úplně vytratila ta konstruktivita. Setkávám se s názory, které na mě působí jako výkřiky do tmy. Ale naděje umírá poslední a já se i v nich snažím najít něco relevantního, a proto bych se rád zeptal na váš názor na některé z takových kritik.
Anotace
Slýchávám (asi od dvou nebo třech lidí :–P), že je strašné, jak Lean Mapper pracuje s anotacemi. Že sice třeba v Doctrine 2 se také pracuje s anotacemi, ale docela jinak, jsou v nich jenom metadata týkající se mapování atp… Je to důsledně oddělené, takže díky tomu lze mít ta metadata třeba v YAMLu nebo v XML (sic!).
Řeknu něco, co kritiky tohohle možná trochu překvapí: stačila by
úplně triviální úprava Lean Mapperu a tahle metadata by mohla být také
v YAMLu, XML, neonu, databázi… whatever. Stačilo by jen vyčlenit nějakou
IEntityReflectionFactory
, kterou by si každý mohl naimplementovat
podle chuti. Entita jako taková v Lean Mapperu se o své anotace vůbec
nezajímá, ona pracuje čistě jen s relevantní reflexí. A je jí úplně
jedno, jak ta reflexe vznikla a kde byla uložená metadata.
Takže by nebyl problém mít de facto podobné entity jako v Doctrine 2 (jen bez těch kilometrových anotací, to po mně prosím nechtějte…) – private/protected a… něco málo magie k tomu (nikoliv ale nastavování private položek přes reflexi :–P). Já ale pořád marně bádám nad lepším způsobem, než současným. Nakopnete mě?
U současného řešení vidím hned řadu zásadních výhod. Zde jsou vystřižené z nově vznikající dokumentace:
- Využívá se standardní anotace `@property`, a proto vám většina hlavních IDE bude dobře rozumět a bude vám **napovídat**
- Je velmi expresivní. Stěží byste hledali způsob, jak stručněji položky včetně pravidel pro práci s nimi nadefinovat.
- Všechny položky mohou být viděny zároveň (rozumějte na jedné obrazovce) i včetně většiny detailů. Okamžitě si tak lze udělat o dané entitě představu: o jejích položkách, vazbách na jiné entity… Nemusíte se kvůli tomu zaobírat množstvím getterů a setterů a jako puzzle skládat celkový obraz entity.
- Asi není nutné dodávat, že takto nadefinované položky jsou nenarušitelné a naprosto bezpečné. Lean Mapper za vás následně kontroluje správnost typů, to, zda lze do té či oné položky zapisovat, zda může obsahovat `null`… Tím vám Lean Mapper ušetří mnoho psaní, protože pokud byste chtěli mít takto robustní položky nadefinované jen pomocí „nemagických“ getterů a setterů, museli byste napsat nemalé množství nezáživného rutinního kódu.
Jaké jsou jiné varianty?
Privátní členy
Řekněme, že položky budou nadefinované jako privátní proměnné, nad které se budou psát anotace. IDE mi tak ale přestane napovídat položky entity, což považuji za zásadní mínus. Nebo mi něco uniká? Dále ztratím přehled, co jsou proměnné reprezentující nějakou vlastnost doménového objektu, a co jsou třeba nějaké pomocné záležitosti či závislosti… další mínus. A těch mínus bych našel i víc. Prozradíte mi někdo nějaké plus?
XML, YAML, neon
Fakt byste někdo raději psali XML než anotace? Tohle je pro mě argument z podobné škatulky, jako argument pro ORM obecně: mohu kdykoliv přejít na jiný databázový systém…
Klasické třídy
Enita může být třída bez magie a její API může být „napsané“.
Jenomže s tím je šíleně psaní. Fakt byste preferovali ruční validaci
všech typů, null
hodnot atp.? Nicméně tohle je řešení,
které už je v Lean Mapperu možné. Lze se obejít bez jediné anotace. Jenom
mi prostě uniká, kdo a proč by to dobrovolně dělal…
Jak říkám, mile rád vyčlením IEntityReflectionFactory
(třeba ve verzi 2.2), ale nechce se mi to dělat jenom jako „věc pro
věc“. Jenom proto, aby někdo mohl mít nadefinované entity v YAMLu, ale
v reálu to nikdo mít nebude… Pak bych s takovou úpravou nerad
ztrácel čas.
Kód entity
Slyšel jsem, že entita je přeplácaná. Co si pod tím mám představit?
:) Co by šlo podle vás vypustit? Celý kód LeanMapper\Entity
(a
přiznávám, že to není úplně krátká třída) v podstatě jenom
realizuje API entity a deleguje práci. Dlouhý ten kód je proto, protože to
API je prostě bohaté – významná část kódu například řeší tu
správu jednoduchých M:N vazeb. Ano, tu třídu by šlo zkrátit, ale prostě
by se muselo něco odstranit z API a to by tam pak chybělo. :–P Anebo proč
konkrétně je entita přeplácaná?
Active record like
Slyšel jsem, že Lean Mapper je prý „active record like“. :) Zaznělo to jako kritika. Totálně tápu, co si pod tím mám představit. Active record se vyznačuje tím, že „entitní“ třídy odpovídají tabulkám v databázi a každá instance „entity“ je pak zodpovědná (mimo jiné) za ukládání dat do a načítání dat z databáze. S tímhle Lean Mapper koreluje asi tolik, jako s tím koreluje Doctrine 2. :–P Nebo mi něco uniká?
Nehezké modely
Slyšel jsem, že prý s Doctrine 2 lze přece jenom mít hezčí
objektový model než s Lean Mapperem. Ukázka bohužel zase chyběla. :)
Proto nevím, co si pod tím představit. Co ale zde na fóru sleduji útržky
modelů postavených nad Doctrine s voláními typu:
$this->entityManager->getRepository(...)->findAll(...)
,
při kterých by se Demeter obracela v hrobě (ok, přeháním), trochu mi ta
kritika připadá lichá. Třeba právě to, že Entity Manager v Doctrine
často vystupuje jako service locator, by mně osobně docela vadilo. Jak mám
vědět, co potřebuje třída, která dostává v konstruktoru Entity Manager?
Vlastně snadno – podívám se do kódu té třídy…
Tohle celé píšu jenom proto, protože se té kritice snažím porozumět, snažím se z ní „vydestilovat“ něco relevantního a Lean Mapper následně zlepšit. Jsou-li to ale fakt jenom výkřiky do tmy (a bez konkrétních argumentů asi jsou), asi mi fakt nezbyde než je ignorovat. Díky!
Editoval Tharos (12. 12. 2013 23:43)
před 6 lety
- Casper
- Člen | 253
@Tharos:
V krátkosti:
- Anotace – tohle je podle mě nejlepší možný způsob definování property vzhledem k napovídání IDE a expresivitě. Na IEntityReflectionFactory bych se zvysoka …, větší blbost, než si definovat entity někde v XML jsem snad neslyšel :)
- Kód entity – spíš je to pro laika trochu nepřehledné se v tom vyznat, když se chce podívat jak něco funguje (jaktože mi vlastně to traverzování přes vztahové entity funguje?). Myslím ale, že tvé poslední dekompozice toto trochu vylepšují.
Ke zbytku nemám co říct, páč s tebou souhlasím. Pokud chceš ale konstruktivní kritiku, tak dokumentace už velmi strádá – chápu, že vývoj a podpora je zajímavější, ale trochu si podkopáváš sám sebe, protože momentálně když někdo bude chtít vyzkoušet LM, tak se bude na každou blbost (rozuměj změnu oproti 1.3.1) ptát tady na fóru, popřípadě to vzdá.
před 6 lety
- Michal III
- Člen | 84
@Tharos: Mně se také zdá, že takovéto kritiky
jsou opravdu ignorování hodné. Jen se vyjádřím k repozitářům. Na
$this->entityManager->getRepository(...)->findAll(...)
jsem také při čtení kódu s Doctrine narazil a přemýšlel jsem, jestli je
tohle ten správný způsob.
Vůbec v poslední době přemýšlím, jak by mi vyhovovalo pracovat
s repozitáři. Už se mi stalo, že jsem potřeboval vytáhnout data
z nějaké vedlejší tabulky, ke které jsem zatím přistupoval jen přes
jiné entity. V tu chvíli mi ale chyběl nadefinovaný repozitář, takže ho
musím vytvořit a zaregistrovat v config.neon
a právě jsem
přemýšlel, že by mi pro tyhle účely asi usnadnil práci nějaký takový
„EntityManager“, nicméně je to asi skrytá závislost, takže se bez toho
obejdu.
Napadlo mě, jestli by nedávalo větší smysl sloupec odkazující na cílovou tabulku odvozoval spíše z názvu property, než z jejího typu. Co myslíš?
před 6 lety
- Casper
- Člen | 253
@Michal III:
Napadlo mě, jestli by nedávalo větší smysl sloupec odkazující na cílovou tabulku odvozoval spíše z názvu property, než z jejího typu. Co myslíš?
To je trochu nesmysl ne? Odkazuješ přece na nějakou tabulku (tedy v aplikaci Entitu), nikoli na něco, co si v aplikaci nějak pojmenuješ. Navíc by sis přitáhl spoustu problémů:
/*
* @property Address $billingAddress m:hasOne(billing_address_id)
* @property Address $shippingAddress m:hasOne(shipping_address_id)
*/
class User extends BaseEntity {}
EDIT: A v podstatě bys ztratil možnost si property pojmenovávat nějak stručeněji či lépe (množné číslo).
/*
* UserDocument[] $documents m:belongsToMany
*/
class User extends BaseEntity {}
Editoval Casper (12. 12. 2013 10:34)
před 6 lety
- Michal III
- Člen | 84
@Casper:
To je trochu nesmysl ne? Odkazuješ přece na nějakou tabulku (tedy v aplikaci Entitu), nikoli na něco, co si v aplikaci nějak pojmenuješ. Navíc by sis přitáhl spoustu problémů:
/* * @property Address $billingAddress m:hasOne(billing_address_id) * @property Address $shippingAddress m:hasOne(shipping_address_id) */ class User extends BaseEntity {}
Nerozumím nastíněnému problému.
/**
* @property int $id
* @property Author $author m:hasOne
* @property Author|null $reviewer m:hasOne(reviewer_id)
* @property Borrowing[] $borrowings m:belongsToMany
* @property Tag[] $tags m:hasMany
* @property string $pubdate
* @property string $name
* @property string|null $description
* @property string|null $website
* @property bool $available
*/
class Book extends \LeanMapper\Entity
{
}
Tohle je vzato z quickstartu LeanMapperu. Pokud by to fungovalo, jak jsem
navrhl já, nemuselo by se uvádět m:hasOne(reviewer_id)
. Vůbec
z mé praxe se právě většinou schoduje název cizího klíče v tabulce
s názvem property spíše než s jejím typem (tedy až na
_id
samozřejmě).
EDIT: A v podstatě bys ztratil možnost si property pojmenovávat nějak stručeněji či lépe (množné číslo).
/* * UserDocument[] $documents m:belongsToMany */ class User extends BaseEntity {}
Na speciální situace je tu právě to ruční dodefinování. Jde spíše o to, jaký případ nastane nejčastěji, a ten potom zvolit jako výchozí. U mě je to zrovna právě ta shoda názvu property s názvem sloupce v tabulce…
před 6 lety
- Casper
- Člen | 253
@Michal III:
Řekl bych že tím přenášíš zodpovědnost mapperu do nějakého
pojmenovávání property a to je už z koncepčního hlediska špatně.
před 6 lety
- Michal III
- Člen | 84
@Casper: Nepřijde mi to nějak špatně. Nevím, jak
moc to přenáší zodpovědnost mapperu do pojmenování property, ale
koneckonců, když nadefinuji @property string $name
, očekává
se, že bude ve sloupci name
a ne ve sloupci
string
.
Je to samozřejmě dost absurdní příklad, každopádně myslím, že
pojmenováváme cizí klíče tak, aby lépe určovali vztah k vazební
tabulce, nikoliv abychom určili samotnou vazební tabulku (můžeme mít
například tabulku user
, ve které se budou nacházet
všichni – Author
, Reviewer
,
Publisher
, … nebudeme pak ale mít všude user_id
).
Pak mi tedy dává smysl, aby se název ve výchozím stavu odvozoval z názvu
property. Nebo to alespoň stojí za úvahu, že by to mohlo být nastavitelné,
z čeho to odvozovat (například přepsáním nějaké
protected
metody?).
Rozhodně to není nějaký velký problém, vlastnost, bez které bych nemohl s LeanMapperem pracovat, ale nadhodil jsem to tu jako návrh.
před 6 lety
- Pavel Macháň
- Člen | 285
@Tharos: Ze začátku mě iritovala @property anotace a „nemožnost“ používat interface u entit, protože je strašně otravné ručně datlit get/set pro property z důvodu uložení dat v row ($this->row->azPotomMojeProperty). To vám IDE 1 klikem prostě podle property nedá (a kdo to má po vygenerování pak přepisovat… i replace je otrava), ale už sem to nějak překousnul.
Editoval EIFEL (12. 12. 2013 15:50)
před 6 lety
- Šaman
- Člen | 2275
@Tharos: U mě funguje nějaká asi čtvrt roku stará verze k moji spokojenosti, pak jsem s LM přestal aktivně pracovat a jen sleduji fórum. Potvrzuji, že nejdůležitější je teď dotáhnout dokumentaci, jinak už se s tím nováček nepopere. Na té ale děláš, takže tobě přeji pevné nervy a nám trpělivost, odměnou by nám měl být imho nejlepší ORM pod Dibi.
K nápadům, které tu zazněly – většina mi také připadá jako
výkřiky do tmy a já bych byl nerad, pokud by se některé z nich
implementovaly. Entity by podle mě měly zůstat v PHP (OOP, napovídání),
zvlášť když jsem po přednášce na PS pochopil, co myslíš chytrou
entitou.
Co se týče srovnání s Doctrine2 – jestli někomu D2 vyhovuje, tak ať ji
používá. Předpokládám, že kdo ji zná hodně dobře, napíše v ní
lepší model (sice nevím v čem konkrétně, ale z nějakého důvodu asi je
D2 tak rozsáhlá). D2 je prostě Ddooccttrriiiinneee, zatímco LM je Lean. Je
jasné, že výhodou D2 bude větší počet funkcí a možností, jak něco
zapsat – tedy vybrat pro každý, i extrémní případ ten nejlepší
způsob. LM má jiné výhody a těch bych se držel. Nehodlám se učit LQL
(Lean Query Language).
Mě se na LM nezdála jediná věc a to naprostá prkotina – typ enum. Podle mě má dostat pole, nebo výčet povolených hodnot a ta práce s konstantami je už určitá nadstavba, která by klidně mohla přijít do BaseEntity (prostě mi často konstanty nestačí a já si ten enum stejně simuluji přes funkci). Na druhou stranu na opravdu jednoduché enumy (muž, žena) to funguje s minimem kódu, takže občas se to hodí. Jak říkám, já bych si tuhle zkratku k vytvoření pole nechal v Base třídě mimo jádro LM, ale je to prkotina.
A druhou věc, o které jsem přemýšlel, jsem chtěl nadhodit až bude
dokumentace, ať tě nerozptyluju podružnostma – navíc to asi půjde
zařídit snadno. Jde o konkrétní mappery jednotlivých entit.
Cpát výjimečné případy do anotace entity se mi nelíbí, protože taková
věc patří do mapperu. Ale pokud budou nějaké výjimky ve více entitách,
mám v Mapperu nehezký if (a navíc kvůli každé nově přidané výjimce
edituji už odladěnou třídu).
Idea je taková, že pokud budu chtít popsat specifické mapování třeba
entity User, tak si vytvořím UserMapper a rozdíly oproti Mapperu popíšu
tam. Bude tak na jednou místě mapování celé entity a při přidání nové
entity s novými výjimkami budu přidávat jen nové soubory a nikoliv
překopávat stávající.
Takže buď by se takový mapper psal (generoval) vždy, stejně jako repository
(i prázdné má smysl). Nebo by se použil pouze, pokud je definovaný a pokud
ne, použil by se nějaký vyšší mapper. Anebo by se dal zadat ručně do
entity, která ho má použít…
⇒ Jestli mohu apelovat, soustřeď se teď na dokumentaci. Mám obavu, že teď LM zcela rozumíš jen ty a uživatelsky možná dva, tři lidi z fóra. My zkusíme odpovídat dotazům, na které stačíme, klidně tu odpovídej až s denním zpožděním a zkus dotáhnout dokumentaci aspoň na úrovni dobře popsaného api s ukázkami. Vím, že je to pruda, ale bez toho se prostě teď LM už nehne. GL!
před 6 lety
- Tharos
- Člen | 1042
Moc díky za odezvu.
Utvrdili jste mě v přesvědčení, že kritika může být i úplně lichá a je ztráta času snažit se v takové cokoliv nalézt.
Co se dokumentace týče, máte samozřejmě pravdu… Pár věcí v Lean Mapperu mi ještě „nedalo“, ale jakmile v následujících dnech vydám verzi 2.1, všechnu další energii už budu věnovat pouze dokumentaci. Na svou obhajobu bych chtěl říct, že v Lean Mapperu donedávna fakt ještě bylo pár věcí, se kterými jsem nebyl příliš spokojený, ale s verzí 2.1 už jich zbylo úplné minimum.
@Michal III: Mapper z podstaty věci musí umět zodpovědět, jak se jmenuje sloupec nesoucí cizí klíč při propojení dvou tabulek. Mimochodem to není záležitost pouze hasOne vazby, ale všech existujících vazeb (hasMany, belongsToOne, belongsToMany). Zkrátka následující metoda musí existovat minimálně s parametry, které aktuálně obsahuje:
public function getRelationshipColumn($sourceTable, $targetTable)
Problém typicky nastává v situaci, kdy z tabulky A
vede
více vazeb do tabulky B
najednou (takové to
author_id
, maintainer_id
). Aby se s tím mapper
plnohodnotně vypořádal, potřeboval by ještě vědět, z jaké entity a
přes jakou položku se mezi tabulkami traverzuje. Ta metoda by pak musela
vypadat zhruba následovně:
public function getRelationshipColumn($sourceTable, $targetTable, $entityClass = null, $property = null)
Jenomže přidané parametry by byly takové… divné. Mapper by to IMHO dost znepřehlednělo. A už teď to s ním není žádná sláva… Takže v těchto situacích mně osobně prostě přijde lepší doladit vazební sloupec v anotaci, byť samozřejmě vyřešit to kompletně v mapperu by bylo čistější.
Nedávno mě ale napadl ještě jiný způsob, jak by tohle šlo čistě vyřešit:
public function getRelationshipColumn($sourceTable, $targetTable, $relationshipName = null)
@property Author $maintainer m:hasOne(@maintainer)
Ten zavináč je jenom nástřel, jak by syntaxe mohla vypadat. Význam by byl takový, že u doupřesnění vazby by se namísto natvrdo vepsaného názvu vazebního sloupce mohlo vložit jakési pojmenování té vazby a mapper by s tím mohl pracovat. Je to srozumitelné? Výhoda je jasná – konkrétní název sloupce by zůstal v režii mapperu. Takto pojmenovat by šlo vazební sloupce, tabulky takto pojmenovávat IMHO nemá smysl.
@EIFEL: Dokázal bys rozvést, jak s těmi rozhraními v praxi pracuješ (bys pracoval)? Pro mě je to pořád moc abstraktní. :) Ty bys chtěl, aby existovalo rozhraní, které bude říkal, že například autor má mít ty a ty položky, a pak bys chtěl mít v aplikaci více implementací takového rozhraní? Jaké to má praktické využití? :)
před 6 lety
- Pavel Macháň
- Člen | 285
Tharos napsal(a):
@EIFEL: Dokázal bys rozvést, jak s těmi rozhraními v praxi pracuješ (bys pracoval)? Pro mě je to pořád moc abstraktní. :) Ty bys chtěl, aby existovalo rozhraní, které bude říkal, že například autor má mít ty a ty položky, a pak bys chtěl mít v aplikaci více implementací takového rozhraní? Jaké to má praktické využití? :)
@Tharos:
interface IGoods {
public function getDimensions();
public function getWeight();
public function getWarehouse();
}
interface IVehicle {
public function getEnginePower();
}
interface IComputer {
public function getProcessor();
}
class Car implements IGoods, IVehicle {
// IGoods, IVehicle metody + specifické pro auta
public function getHorsepower(){}
}
class Notebook implements IGoods, IComputer {
// IGoods, IComputer metody + specifické pro Notebook
public function getBatteryCapacity(){}
}
class ShippingCost {
public function getPplCost(IGoods $goods){}
}
Máme třeba obchod na různej sortiment zboží. IGoods nám udává rozhraní, které bude vyžadováno pro zpracování zboží třeba pro cenu poštovného pro PPL. Pro výpočet poštovného nás nezajímají žádná dodatečná data od dalších tříd či rozhraní. Budeme moc využít jen nutné metody pro použití pro výpočet ceny (nebo druhu dopravy… preci jen tank nepošlete letadlem ale lodí). Dále máme třeba IVehicle kde nás jako uživatele zajímá třeba výkon motoru, ale data pro výpočet poštovného nás absolutně nezajímají (to je interní věc obchodu). Dále třeba pro výpis dopravního prostředku absolutně nepotřebujeme vědět (a ani by nám to nemělo jít získat… nemá to tam co dělat) v jakém skladu se nachází (berme to, že obchod má 1sklad v Brně a duhej v Oslu… pokud to kupujete v brne asi bude jina cena dopravy). To by mělo jít zjist jen tam kde je to vyžadováno a to ve výpočtu poštovného.
Samozřejmě by šlo třídě Vehicle podědit metody třídu Goods a při výpočtu poštovného mít vstupní parametr Goods a vesele vypočítat poštovné, jenže jak to uděláte naopak? Chcete zobrazit data ze třídy Vehicle ale máte možnost zavolat i metodu kterou ste podědili z Goods.
Ono by se to dalo prostě ignorovat a zděděných tříd, které v danné části nemají co dělat si nevšímat, ale jsou tam. Funkčně to nebude vadit, protože data tam budou (pokud by ta entita opravdu ty data z db dostat měla a nebude to jen prazdna metoda třeba s vyjímkou nebo null hodnotou, protože to vyžaduje zděděná třída… což by problém už byl) tak by se nestalo, že by nastal nějaký problém s její manipulací (ale furt je tam ta možnost).
Doufám, že sem to podal nějak srozumitelně. Na vysvětlování nejsem moc dobrej :/
Editoval EIFEL (12. 12. 2013 22:23)
před 6 lety
- Tharos
- Člen | 1042
@EIFEL: Super, rozumím. :)
No hele, já bych to řešil zhruba následovně:
abstract class Entity extends LeanMapper\Entity
{
protected function getPropertyColumn($method)
{
return $this->getCurrentReflection()->getEntityProperty(lcfirst(substr($method, 3)))->getColumn();
}
}
interface IGoods
{
/**
* @return string
*/
public function getDimensions();
/**
* @return float
*/
public function getWeight();
/**
* @return Warehouse
*/
public function getWarehouse();
}
/**
* @property int $id
* @property string $dimensions m:useMethods
* @property float $weight m:useMethods
* @property Warehouse $warehouse m:hasOne m:useMethods
*/
class Goods extends Entity implements IGoods
{
public function getDimensions()
{
return $this->row->{$this->getPropertyColumn(__FUNCTION__)};
}
public function getWeight()
{
return $this->row->{$this->getPropertyColumn(__FUNCTION__)};
}
public function getWarehouse()
{
return $this->getValueByPropertyWithRelationship('warehouse');
}
}
Čtení low-level hodnot přímo z row je u jednoduchých typů
triviální. Aby nebyly názvy sloupců hard-coded, je ideální jít na to
přes reflexi (která je dostupná přes getCurrentReflection
),
jenž v pozadí používá mapper. Pro to získání názvu sloupce bych si
z lenosti napsal tu zkratku getPropertyColumn
. Tenhle styl psaní
vlastního getteru velmi důmyslně kombinuje sílu anotací (hned je
k dispozici mapování, případné custom příznaky atp.) a vlastních
get/set metod. Jestli je srozumitelné, co tím chci říct. :)
No a pokud jde o získávání souvisejících entit, přesně pro tohle
existuje v LeanMapper\Entity
relativně nová metoda
getValueByPropertyWithRelationship
, na jejíž kód doporučuji se
podívat. Ta za Tebe přesně řeší tu prudu, jakou je získání potřebné
hodnoty, má-li být zohledněno vše relevantní.
V současné verzi Lean Mapperu je to psaní vlastních getterů a setterů o mnoho snazší než bylo kdy dříve. Akorát to ještě není zdokumentované no. :(
Doplnění: Příznak m:useMethods
je
nepovinný, jeho vynechání vůbec nevadí. Jen já ho třeba ve svých
entitách uvádím, protože pak při „skenování“ anotací hned vím, že
se ta hodnota získává/zapisuje přes getter/setter a že se na něj mám
podívat.
Editoval Tharos (12. 12. 2013 23:13)
před 6 lety
- Pavel Macháň
- Člen | 285
Tharos napsal(a):
@EIFEL: Super, rozumím. :)
No hele, já bych to řešil zhruba následovně:
----- PHP ------
Čtení low-level hodnot přímo z row je u jednoduchých typů triviální. Aby nebyly názvy sloupců hard-coded, je ideální jít na to přes reflexi (která je dostupná přes
getCurrentReflection
), jenž v pozadí používá mapper. Pro to získání názvu sloupce bych si z lenosti napsal tu zkratkugetPropertyColumn
. Tenhle styl psaní vlastního getteru velmi důmyslně kombinuje sílu anotací (hned je k dispozici mapování, případné custom příznaky atp.) a vlastních get/set metod. Jestli je srozumitelné, co tím chci říct. :)No a pokud jde o získávání souvisejících entit, přesně pro tohle existuje v
LeanMapper\Entity
relativně nová metodagetValueByPropertyWithRelationship
, na jejíž kód doporučuji se podívat. Ta za Tebe přesně řeší tu prudu, jakou je získání potřebné hodnoty, má-li být zohledněno vše relevantní.V současné verzi Lean Mapperu je to psaní vlastních getterů a setterů o mnoho snazší než bylo kdy dříve. Akorát to ještě není zdokumentované no. :(
Doplnění: Příznak
m:useMethods
je nepovinný, jeho vynechání vůbec nevadí. Jen já ho třeba ve svých entitách uvádím, protože pak při „skenování“ anotací hned vím, že se ta hodnota získává/zapisuje přes getter/setter a že se na něj mám podívat.
@Tharos:
Na getValueByPropertyWithRelationship se každopádně
podívám :)
Bohužel toto řešení i když funkční se mě teda moc nezamlouvá, ale bohužel pokud je to řešené přez row tak stím nic jiného nezmůžeme. I když než
$this->row->{$this->getPropertyColumn(__FUNCTION__)};
bych si nechal pomocí IDE vygenerovat klasický getter a provedl bych replace $this->row->property -> $this->property u základních typů… u souvisejících dat bych to už musel přepsat ručně.
Kvůli tomuto „přelamovování kostí a následného sádrování“ raději interface u entit nepoužívám a ani nebudu. Hodně práce za málo srandy.
Editoval EIFEL (13. 12. 2013 0:34)
před 6 lety
- Tharos
- Člen | 1042
@EIFEL: No hele, a následující řešení by bylo lepší?
interface IGoods
{
/**
* @return string
*/
public function getDimensions();
/**
* @return float
*/
public function getWeight();
/**
* @return Warehouse
*/
public function getWarehouse();
}
/**
* @property int $id
* @property string $dimensions m:useMethods
* @property float $weight m:useMethods
* @property Warehouse $warehouse m:hasOne m:useMethods
*/
class Goods extends LeanMapper\Entity implements IGoods
{
public function getDimensions()
{
return $this->__get('dimensions');
}
public function getWeight()
{
return $this->__get('weight');
}
public function getWarehouse()
{
return $this->__get('warehouse');
}
}
Tohle nad aktuální verzí nefunguje, protože to logicky spadne do
nekonečného cyklu (v __get
se zase zavolá
get<Name>
). Nicméně bylo by triviální zařídit, aby to
fungovalo. Stačilo by, aby si Lean Mapper pamatoval, že vstoupil u dané
položky do přístupové metody a že rekurzivně do ní (v případě
dalšího volání __get
uvnitř ní) znovu vstupovat nemá.
Mimochodem takhle přesně to funguje v PHP s magickým
__get/__set
– pokud uvnitř __get/__set
přistoupíte k té samé neexistující třídní proměnné,
__get/__set
už se znovu nevolá.
Že bych tohle umožnil jsem už jednou ze svého vlastního popudu přemýšlel…
Editoval Tharos (13. 12. 2013 12:21)
před 6 lety
- Pavel Macháň
- Člen | 285
Tharos napsal(a):
@EIFEL: No hele, a následující řešení by bylo lepší?
interface IGoods { /** * @return string */ public function getDimensions(); /** * @return float */ public function getWeight(); /** * @return Warehouse */ public function getWarehouse(); }
/** * @property int $id * @property string $dimensions m:useMethods * @property float $weight m:useMethods * @property Warehouse $warehouse m:hasOne m:useMethods */ class Goods extends LeanMapper\Entity implements IGoods { public function getDimensions() { return $this->__get('dimensions'); } public function getWeight() { return $this->__get('weight'); } public function getWarehouse() { return $this->__get('warehouse'); } }
Tohle nad aktuální verzí nefunguje, protože to logicky spadne do nekonečného cyklu (v
__get
se zase zavoláget<Name>
). Nicméně bylo by triviální zařídit, aby to fungovalo. Stačilo by, aby si Lean Mapper pamatoval, že vstoupil u dané položky do přístupové metody a že rekurzivně do ní (v případě dalšího volání__get
uvnitř ní) znovu vstupovat nemá. Mimochodem takhle přesně to funguje v PHP s magickým__get/__set
– pokud uvnitř__get/__set
přistoupíte k neexistující třídní proměnné,__get/__set
už se znovu nevolá.Že bych tohle umožnil jsem už jednou ze svého vlastního popudu přemýšlel…
Určitě by to bylo lepší než to předešlé řešení.
před 6 lety
- Tharos
- Člen | 1042
@EIFEL: Když jsem nad tím ještě tak přemýšlel,
tak to, že volání __get/__set
z vlastního getteru/setteru
skončí zacyklením, je víceméně chyba.
Jelikož úprava byla skutečně triviální, zahrnul jsem to ještě do release větve pro verzi 2.1. Nyní tedy můj výše uvedený kód funguje.
No a celé to přináší zase další možnosti vlastním
getterům/setterům. Nyní se z nich lze kdekoliv odkázat na výchozí
__get/__set
a vytěžit tak maximum z toho, co za vás může Lean
Mapper udělat (kontrola typu, null
atp).
Editoval Tharos (13. 12. 2013 10:26)
před 6 lety
- Tharos
- Člen | 1042
Ahoj,
tak jsem právě vydal verzi
2.1 (Composer "tharos/leanmapper": "v2.1.0"
).
Hlavní novinky jsou:
- vyčlenění IEntityFactory včetně defaultní implementace DefaultEntityFactory (BC break)
- zásadní zlepšení chybových hlášek
- implicitní filtry
- anonymní filtry
- Repository::createFluent
- dekompozice Entity::__get a Entity::__set
- Entity::createCollection a Repository::createCollection přesunuto do IEntityFactory::createCollection (BC break)
- podpora pro výchozí hodnoty (v anotaci) null a prázdný řetězec
- z vlastních getterů a setterů se lze nově odkazovat na
__get
a__set
Dále bylo v Lean Mapperu refaktorováno a „vyšperkováno“ velké množství interních záležitostí.
Všechny změny lze vidět v tomto diffu.
Takže enjoy a já si jdu teda užít tu dokumentaci. :)
před 6 lety
- Jan Suchánek
- Backer | 403
@Tharos: Poslal jsem ti drobné na vývoj dokumentace, v rámci možností, na tvůj účet, aby se ti dokumentace ve tvém volném čase dělala líp. Docela se na ní těším a asi nejsem sám, z nás začátečníků, co Lean Mapper zatím neumějí moc kvalitně používat a rádi by. Až bude hotová a použitelná pošlu další …
před 6 lety
- Tharos
- Člen | 1042
@jenicek: Moc díky!
Když už jsme u toho sponzoringu… Je toho opravdu hodně, co by bylo záhodno, aby dokumentace obsahovala. Jelikož vše zdokumentovat vyloženě ve svém volném čase pro mě není reálné, rozhodl jsem se k následujícímu řešení:
Bude existovat jakási standardní dokumentace všech funkconalit Lean Mapperu včetně základních ukázek, quick startu atp. Ta bude volně dostupná bez jakýchkoliv omezení.
Paralelně bude ale existovat i jakási rozšířená dokumentace, ve které
bych se chtěl postupně věnovat množství neméně zajímavých témat:
ověřenými architekturami modelů nad Lean Mapperem a kdy je vhodné jakou
použít, Query objekty, pokročilými ukázkami a „návrhovými vzory“ při
práci s filtry, generováním schéma databáze z entit, sestavováním
formulářů z IEntityReflection
, možnosti využití
IEntityReflection
obecně… a dalo by se hodně dlouze
pokračovat.
Jelikož v oné rozšířené dokumentaci už bude nemalé know-how a i množství „proprietárního“ kódu, přístup k ní bude zpoplatněn. Symbolicky – rozhodně nepůjde o cenu, jakou byste zaplatili třeba za nějaké školení o PHP nebo tak.
Samozřejmě ale všichni, kteří už teď jakýmkoliv způsobem na vývoj knihovny přispěli, budou mít automaticky přístup i k té rozšířené dokumentaci.
před 6 lety
- Jan Suchánek
- Backer | 403
@Tharos: Nezvažoval si to vlastní fórum? Dalo by se na něm pěkně jednotlivé sekce rozdělit a nebylo by pak vše na 18cti stránkách.
před 6 lety
- fast.solcik
- Člen | 3
Taktéž bych se přimlouval za vlastní fórum. Rozhodně by to vedle dokumentace přispělo k větší transparentnosti.
před 6 lety
- m@te
- Člen | 7
@Tharos: +1 za platenu rozsirenu dokumentaciu. Akurat by bolo dobre umoznit platit aj cez PayPal, zo SR je totiz prevod na cesky ucet so zbytocnymi poplatkami :(
před 6 lety
- Mesiah
- Člen | 242
Tharos napsal(a):
Takže, jenom bleskově jsem tak po diskuzích koukal, jak tohle řeší Doktrína, a narazil jsem na tohle a tohle …
S tím do jisté míry souhlasím, vlastně i v současném Lean Mapper quickstartu upozorňuji na to, že pro takové „vazební tabulky“ (které už nejsou tak úplně vazebními tabulkami) je vhodné vytvořit samostatnou entitu.
Bez diskuze to je třeba v případě, kdy „vazební tabulka“ udržuje doplňujících informací ještě kdo ví kolik…
Nicméně v případě, že tam je třeba jeden sloupec navíc (zda je vazba aktivní, výše výplaty zaměstnance v oddělení atp.), přijde mi příjemné, že Lean Mapper nabízí jako alternativu k vytváření samostatné entity i řešení popsané v mých předchozích dvou příspěvcích.
Vlastně mi přijde jako dost nedořešené, že v takovém případě musím mít tři plnokrevné entity… V reálném světě mám prostě zaměstnance, který pracuje v nějakém oddělení. A to, co za to má, je přece vlastnost toho vztahu mezi jím a oddělením, nikoliv další objekt… Nějak se mi tam tu třetí entitu nedaří najít. :–P
V reálnémm světě, ve kterém zaměstnanci zapomínají, kde a odkdy do
kdy pracovali by to fungovalo.
Ale v našem světě bych spíš měl říct: Zaměstnanec,
který v současnosti pracuje v nějakém
oddělení. :)
- v současnosti řeším vazby, které mohou expirovat, takže hledám jak na ně…
PS: fórum pomalu přelouskávám, ale někde jsem zachytil možnost generování db z entit, někde nějaké informace o použití? Jak to vidíš s dokumentací?
před 6 lety
- llook
- Člen | 412
@Casper
Anotace – tohle je podle mě nejlepší možný způsob definování property vzhledem k napovídání IDE a expresivitě. Na IEntityReflectionFactory bych se zvysoka …, větší blbost, než si definovat entity někde v XML jsem snad neslyšel :)
Entity vždy definuješ v PHP, ale nastavení jejich mapování je něco, co tu entitu vůbec nezajímá. Proto je v Doctrine možné toto nastavení udržovat zvlášť, v YAMLu nebo XML (XML má výhodu napovídání v IDE, YAML je lépe lidsky čitelný). Můžeš tak teoreticky psát pěkný DDD kód s plain-old objekty bez toho ORM informačního šumu. V praxi ale i tak většina vývojářů volí tu cestu, že se entita a mapování míchá na jednom místě a používají anotace.
Je to podobné, jako třeba u routování pomocí anotací. Někomu to přijde děsně kůl a někdo to považuje za anti-pattern.
Možnost držet mapování někde vedle není úplná blbost, ale dokud po tom není poptávka, tak je to zbytečné. Předpokládám, že ti, kdo absenci této funkcionality Lean Mapperu vytýkají, by ho nepoužívali ani potom kvůli jiným odlišnostem od Doctrine.
před 6 lety
- kudlajz
- Člen | 70
Muze mi nekdo poradit, jak muzu cachovat asociovane entity?
Dokazu nacachovat jednu entitu pri volani napr. find(), ale dalsi uz ne.
Dekuji.
před 6 lety
- Mesiah
- Člen | 242
Ahoj,
narazil jsem na jedno omezení a možná uznáte, že je smysluplné a bylo
by fajn LM rozšířit.
Řeším situaci, mám komentáře, komentáře můžu psát ke článkům a
fotkám.
Na straně databáze to mám řešeno zhruba takhle.
A teď mám use-case: vypsat všechny komentáře k obrázku.
Easy. $image->comments
.
/**
* Reprezentuje obrázek
*
* @property int $id
* @property string $source
* @property string $alternativeText
* @property User $user m:hasOne
* @property Comment[] $comments m:hasMany(id:commentable:id:comment)
*/
class Image extends BusinessEntity
{
}
Jenže tohle bude fungovat jen dokud Comment.id == Comment.commentable_id.
Celé by to šlo vyřešit, kdyby existoval 5. parametr pro hasMany, kde by se
určilo vůči jakému atribudu data „joinovat“. V mém případě by to
bylo
@property Comment[] $comments m:hasMany(id:commentable:id:comment:
commentable_id)
.
Nebo, jak byste takovou situaci řešili vy?
Editoval Mesiah (21. 12. 2013 19:50)
před 6 lety
- Tharos
- Člen | 1042
@Mesiah:
V reálnémm světě, ve kterém zaměstnanci zapomínají, kde a odkdy do kdy pracovali by to fungovalo. Ale v našem světě bych spíš měl říct: Zaměstnanec, který v současnosti pracuje v nějakém oddělení. :)
Samozřejmě. :) To, jestli tu „plnokrevnou“ entitu má smysl mít nebo ne je tenký led a velmi záleží na kontextu.
generování db z entit, někde nějaké informace o použití? Jak to vidíš s dokumentací?
Ten skript jsem začal psát v rámci jednoho projektu a je ještě mu pár věcí k plnohodnotné použitelnosti chybí. Plánuji doladit jej zase na nějakém dalším projektu. Preferuji nepsat takové věci oproti smyšelným zadání…
Co se dokumentace týče, měl bych na ni mít po večerech relativně dost času přes svátky, takže s ní během následujících dnech, pevně věřím, singifikantně pohnu.
@llook:
Možnost držet mapování někde vedle není úplná blbost, ale dokud po tom není poptávka, tak je to zbytečné. Předpokládám, že ti, kdo absenci této funkcionality Lean Mapperu vytýkají, by ho nepoužívali ani potom kvůli jiným odlišnostem od Doctrine.
Tohle je krásně shrnuté, jak to vidím i já. Mimochodem, psát plain-old objekty je sice hezké, ale v praxi je to často nezáživná otročina. Co položka, to minimálně jedna validační podmínka a případně i výřečná chybová zpráva… Já rozhodně tíhnu k plain-old objektům, ale prostě u entit mi určitá zdokumentovaná magie přijde jako obzvláště přínosná. Běžně vídám (i tady na fóru) útržky plain-old objektů entit z Doctrine 2, které nejsou vůbec robustní (umožňují přiřazení nevalidní hodnoty atp.). A já to chápu – kdo by se s těmi validacemi psal…
@kudlajz:
Muze mi nekdo poradit, jak muzu cachovat asociovane entity?
Abych pravdu řekl, nemám hotové žádné ukázkové řešení, protože jsem tohle nikdy nepotřeboval. Nicméně, principiálně by to mělo jít například takto:
- Mít entity závislé na nějaké službě, která entity umí (ukládat
do|načítat z) cache, a tuto službu v
EntityFactory
entitám injektovat. Entita se pak při traverzování může nejdříve pokusit načíst potřebné entity z cache. - Lze to rozšířit o použití návrhového vzoru dekorátor, který se na
cache hezky hodí. Můžeš pak mít například entity
Book
aCachingBook
s tím, žeCachingBook
umí načítat souvisejícíc data buďto z cache, anebo delegovat načítání naBook
. Potřebnou kompozici si opět můžeš sestavit vEntityFactory
.
Je to srozumitelné? Snažím se odpovědět co nejstručněji, ale aby to mělo hlavu a patu…
před 6 lety
- Tharos
- Člen | 1042
@Mesiah:
narazil jsem na jedno omezení a možná uznáte, že je smysluplné a bylo by fajn LM rozšířit.
Řeším situaci, mám komentáře, komentáře můžu psát ke článkům a fotkám.
Na straně databáze to mám řešeno zhruba takhle.
A teď mám use-case: vypsat všechny komentáře k obrázku.
Easy.$image->comments
./** * Reprezentuje obrázek * * @property int $id * @property string $source * @property string $alternativeText * @property User $user m:hasOne * @property Comment[] $comments m:hasMany(id:commentable:id:comment) */ class Image extends BusinessEntity { }
Jenže tohle bude fungovat jen dokud Comment.id == Comment.commentable_id.
Celé by to šlo vyřešit, kdyby existoval 5. parametr pro hasMany, kde by se určilo vůči jakému atribudu data „joinovat“. V mém případě by to bylo@property Comment[] $comments m:hasMany(id:commentable:id:comment:
commentable_id)
.Nebo, jak byste takovou situaci řešili vy?
Pozor na to, že ty parametry v hasMany
znamenají ve
skutečnosti něco trochu jiného:
columnReferencingSourceTable,relationshipTable,columnReferencingTargetTable,targetTable
.
A dále pozor na to, že Ty nemáš v databázi klasickou M:N
(hasMany
) vazbu. :) Ty máš kombinaci hasOne
vazby
(např. photo.cid → commentable.id) a belongsToMany
vazby
(commentable.id ← comment.ref_id).
Ty k tomu podle toho musíš přistoupit. Nejsnáze přes vlastní getter:
public function getComments() {
$comments = array();
foreach ($this->row->referenced('commentable', 'cid')->referencing('comment', 'ref_id') as $comment) {
$comments[] = $this->entityFactory->createEntity('Model\Entity\Comment', $comment);
}
return $this->entityFactory->createCollection($comments);
}
Je to psané z hlavy, možná v tom bude nějaký drobný překlep…
Nevylučuji, že existuje i nějaké jiné řešení (třeba s použitím filtrů).
Řeší to Tvůj problém?
Editoval Tharos (22. 12. 2013 22:50)
před 6 lety
- Mesiah
- Člen | 242
Tharos napsal(a):
Pozor na to, že ty parametry v
hasMany
znamenají ve skutečnosti něco trochu jiného:columnReferencingSourceTable,relationshipTable,columnReferencingTargetTable,targetTable
.A dále pozor na to, že Ty nemáš v databázi klasickou M:N (
hasMany
) vazbu. :) Ty máš kombinacihasOne
vazby (např. photo.cid → commentable.id) abelongsToMany
vazby (commentable.id ← comment.ref_id).Ty k tomu podle toho musíš přistoupit. Nejsnáze přes vlastní getter:
public function getComments() { $comments = array(); foreach ($this->row->referenced('commentable', 'cid')->referencing('comment', 'ref_id') as $comment) { $comments[] = $this->entityFactory->createEntity('Model\Entity\Comment', $comment); } return $this->entityFactory->createCollection($comments); }
Je to psané z hlavy, možná v tom bude nějaký drobný překlep…
Nevylučuji, že existuje i nějaké jiné řešení (třeba s použitím filtrů).
Řeší to Tvůj problém?
Vyzkouším a dám vědět; díky! Řešení s filtrem mě napadlo, ale spíš jsem chtěl nadhodit feature request – ale teď si uvědomuji, že mi nedocvakly souvislosti a není dobrý nápad dělat z hasMany ultimátní „operátor“… :)