tiny ‘n’ smart
database layer

Odkazy: dibi | API reference

Oznámení

Omlouváme se, provoz fóra byl ukončen

Lean Mapper – tenké ORM nad dibi

před 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. (řádek 202 v LeanMapper/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 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.

@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__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 a CachingBook s tím, že CachingBook umí načítat souvisejícíc data buďto z cache, anebo delegovat načítání na Book. Potřebnou kompozici si opět můžeš sestavit v EntityFactory.

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áš 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?

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“… :)

Stránky: Prev 1 … 16 17 18 19 20 … 23 Next