tiny ‘n’ smart
database layer

Odkazy: dibi | API reference

Oznámení

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

Lean Mapper – tenké ORM nad dibi

před 5 lety

Tharos
Člen | 1039
+
0
-

Pro mě je tohle moc velká magie, kterou bych krajně nerad zaváděl…

Výsledkem by bylo, že před sebou budu mít v anotaci položku, u které na první pohled absolutně nebudu tušit, jestli má nebo nemá vazbu. Na první pohled nemám jak zjistit, jestli nějaká třída dědí z LeanMapper\Entity nebo ne.

Lean Mapper je myslím tak expresivní, že IMHO nemá smysl pro ušetření osmi znaků něco podobného zavádět.

před 5 lety

Etch
Člen | 404
+
0
-

Tohle mi přijde tak magický, že by na to čuměl i Harry Potter. Mě magie nevadí, pokud má nějaké relevantní opodstatnění, ale zavádět toto kvůli ušetření pár znaků by mi přišlo šílené.

před 5 lety

Šaman
Člen | 2248
+
0
-

To, že nejsem na první pohled schopen zjistit, jestli je daná třída entita, nebo mě nedošlo. Jasný důvod psát všechno explicitně (i když ER diagram myslím has a belong nezná, to zavádí až relační databáze pro potřeby tu vazbu nějak uložit).
Stejně mám poslední dobou sklony radši kód víc rozepsat, pokud to přinese lepší čitelnost. A když magii, tak jen bílou :)

před 5 lety

castamir
Člen | 631
+
0
-

Bylo to čistě z pragmatického důvodu, že zrovna u této vazby na to furt zapomínám :D

před 5 lety

castamir
Člen | 631
+
0
-

@Tharos a ty enumy na seznam by udělat šly? V několika entitách se mi ttiž hromadí docela nehezké a hlavně opakující transformace konstant na pole…

před 5 lety

Tharos
Člen | 1039
+
0
-

@castamir: Máš to mít. :)

před 5 lety

Casper
Člen | 253
+
0
-

Zdravím, tohle ORM vypadá super, zkusím jej nasadit na nějaké nové projekty. Zkoušel jsem však nově zavedenou metodu pro single-table-inheritance dle zmíněného testu, nicméně mi to funguje pouze při použití createEntities, nikoliv pro createEntity. Tehdy se do metody getEntityClass neposílá žádné Row a tím pádem je i v Mapperu druhý parametr Row vždy null. Něco mi uniklo nebo jde o chybku? Použití mám téměř totožné s ukázkou a používám poslední devel verzi.

Editoval Casper (16. 7. 2013 12:48)

před 5 lety

looky
Člen | 100
+
0
-

Návrh/dotaz, jestli je to vůbec možné: podpora pro vícesloupcové primární klíče.

před 5 lety

castamir
Člen | 631
+
0
-

@Tharos: pěkné, ale má to jeden háček: funguje to pouze v dev verzi :D

před 5 lety

Tharos
Člen | 1039
+
0
-

@Casper: Tohle je samozřejmě chyba (do té metody createEntity jsem tu podporu prostě přidat zapomněl :–P). Opraveno v develop větvi.

@looky: Aktuálně nejsou vícesloupcové primární klíče podporované. Provedu rešerši, co by její zavedení obnášelo.

@castamir: Já vím, já vím. :) Nicméně do verze 1.5 už mi chybí opravdu málo: upravit ten parser anotací (podle RFC tady na fóru) a taky bych do ní ještě chtěl zahrnout tu úpravu filtrů. Odhaduji, že tak do deseti dní bude venku.

Pak zase hurá na dokumentaci. ;)

Editoval Tharos (16. 7. 2013 19:41)

před 5 lety

besanek
Člen | 128
+
0
-

Také se přimlouvám za vícesloupcové primární klíče.

před 5 lety

Tharos
Člen | 1039
+
0
-

ad vícesloupcové PK) Tak jsem provedl bleskovou rešerši. Není to vůbec triviální záležitost (hlavně kvůli IN).

Koukal jsem, jak je na tom „konkurence“. Nette\Database ani NotORM plnokrevné vícesloupcové primární klíče AFAIK neumí. Cituji z NotORM dokumentace:

Does NotORM support multi-column primary keys?
Yes. What NotORM does not support is multi-column foreign keys. So it is valid to define a two-column primary key in M:N table but it is not possible to use a foreign key pointing to (type, id_type) – this is rarely used. Methods getPrimary, getReferencingColumn and getReferencedColumn must return a single column.

Lean Mapper je na tom nyní v podstatě stejně. Spojovací tabulky mohou mít pouze dva sloupce a nad nimi primární klíč.

Neslibuji, že podpora pro tohle bude lepší, protože bych si mohl pěkně naběhnout…

Editoval Tharos (16. 7. 2013 22:14)

před 5 lety

Casper
Člen | 253
+
0
-

Super, oprava funguje. Ještě bych měl pár poznámek, na které jsem narazil při prvotním používání:

  1. Zkoušel jsem využít metodu getPrimaryKey v mapperu, abych mohl PK pojmenovávat [table]_id. Nicméně při použití return $table."_id"; se pro vztahy (hasOne) předpokládá FK sloupec jako [table]_[table]_id. Je možné nějak pracovat s PK i FK ve tvaru [table]_id? Vím, že v dokumentaci je PK ve tvaru ‚id‘ psána jako esenciální, nicméně tahle metoda mi naznačovala, že už tomu tak není :)
  2. Míval jsem ve zvyku pojmenovávat tabulky s podtržítkem, nicméně s tímto ORM mi to dělalo docela trable. Například tabulka delivery_type na kterou poté odkazuji pomocí @property DeliveryType $deliveryType m:hasOne(delivery_type_id, delivery_type) stále předpokládá existenci entity Delivery_type. Mohu někde nastavit konverzi tabulky na entitu pro vztahy? U repozitářů toho lze docílit anotací @table, nicméně takto nevím.
  3. V dokumentaci bych zdůraznil význam nemezery za vztahovými anotacemi. Poprve mě trochu zmátlo, že nefunguje m:hasOne (...). Ikdyž možná je to jen má demence, že jsem to poprve vyzkoušel s ní.
  4. Velmi často využívám mapování z atributu entity na sloupec tabulky kvůli camelCase @property int $bankNumber (bank_number). V dokumentaci jsem to na první projetí neviděl nebo přehlédl, určitě bych to tam trochu vypíchnul, myslím, že to bude časté použití.

Jinak bych chtěl pochválit dokumentaci a tvé poctivé řešení všech návrhů zde na fóru, byl to jeden z hlavnch důvodů, proč jsem se rozhodl to vyzkoušet.

Editoval Casper (16. 7. 2013 22:33)

před 5 lety

Tharos
Člen | 1039
+
0
-

@Casper: Díky za kladný ohlas. :) Snad Tě potěší, že všechny problémy, o kterých píšeš, mají IMHO snadné řešení. :) Jen je znát, že ještě není dokumentace pro plánovanou verzi 1.5 (po jejím vydání si zase k dokumentaci sednu).

ad 1, 2, 4) Ty potřebuješ zhruba následující Mapper:

class Mapper extends LeanMapper\DefaultMapper
{

    public function getPrimaryKey($table)
    {
        return $table . '_id';
    }

    public function getRelationshipColumn($sourceTable, $targetTable)
    {
        return $this->getPrimaryKey($targetTable);
    }

    public function getTable($entityClass)
    {
        // dopsat automatickou konverzi ala Model\Entity\DeliveryType -> delivery_type
        // doporučuji inspirovat se zde: https://api.nette.org/2.0/source-Application.Routers.Route.php.html#688-715
    }

    public function getEntityClass($table, Row $row = null)
    {
        // dopsat automatickou konverzi ala delivery_type -> Model\Entity\DeliveryType
        // vhodná inspirace viz výše
    }

    public function getColumn($entityClass, $field)
    {
        // dopsat automatickou konverzi ala bankNumber -> bank_number
        // vhodná inspirace viz výše
    }

    public function getEntityField($table, $column)
    {
        // dopsat automatickou konverzi ala bank_number -> bankNumber
        // vhodná inspirace viz výše
    }

    public function getTableByRepositoryClass($repositoryClass)
    {
        // dopsat automatickou konverzi ala Model\Repository\DeliveryTypeRepository -> delivery_type
        // tohle není nezbytně nutné, ale pak se lze kompletně vyhnout používání @table nad repositáři
    }

}

Pevně věřím, že je z této kostry jasné, co mám na mysli. Pak se Ti s Lean Mapperem bude pracovat maximálně pohodlně. Explicitně uvádět sloupce budeš muset pouze u případů, které se vymykají uvedeným pravidlům. Nicméně i řada specifik se dá zanést do Mapperu. Fakticky je doupřesnění sloupce (vazebního) v entitě nutné pouze v případě, že pod pokličkou máš z nějaké tabulky více vazeb 1:N do jedné jiné tabulky (aplikace má autor a správce atp.).

Mapper a jeho možnosti ještě nejsou zdokumentované. Plánuji připravit i příklad, který by pokryl podobné konvence, jaké máš Ty.

ad 3) Ten parser budu upravovat a bude vůči tomuhle imunnější. Tebou uvedené mezery už mu nebudou vadit. Rozhodně nejde o Tvou demenci – nejsi totiž první, kdo s tímhle měl problém. :) Každopádně mezera mezi názvem položky a upřesněním sloupce být musí, protože jinak by IDE špatně napovídala.


Kdybys narazil na jakýkoliv problém/dotaz, neváhej se zeptat. Vítám, že s každým novým uživatelem přichází zase nový pohled na věc a nové požadavky.

Editoval Tharos (16. 7. 2013 23:02)

před 5 lety

Tharos
Člen | 1039
+
0
-

Ještě jedno takové doplnění… Určitě se podívej na rozhraní IMapper (metody jsou vzorně okomentované) a na defaultní implementaci.

Aktuálně veškerý vnitřní kód Lean Mapperu s předaným Mapperem pracuje (entity i repositáře). V kódu už by neměla být zadrátovaná absolutně žádná konvence.

Editoval Tharos (16. 7. 2013 23:13)

před 5 lety

Jan Tvrdík
Nette guru | 2541
+
0
-

Tharos wrote: Není to vůbec triviální záležitost (hlavně kvůli IN).

Víš, že pokud je např. složený PK přes sloupce foo a bar, tak lze napsat např.

WHERE (foo, bar) IN ((1, 2), (1, 3))

před 5 lety

Šaman
Člen | 2248
+
0
-

@Tharos: Ahoj, jak teď napsat tohle, pls?

<?php
/**
 * Umožňuje předat entitě místo navázaných entit jejich 'id'
 *
 * @param string $name
 * @param mixed  $value
 */
function __set($name, $value)
{
    $property = $this->getReflection()->getEntityProperty($name);
    if ($property->hasRelationship() && !($value instanceof Entity))
    {
        $relationship = $property->getRelationship();
        barDump($this->mapper); # NULL - mapper není připojen (nová entita)
        $this->row->{$property->getName().'_id'} = $value; # TODO: proč nefunguje $property->getColumn()?
        $this->row->cleanReferencedRowsCache($relationship->getTargetTable(), $relationship->getColumnReferencingTargetTable()); # je tohle ještě nutné?
    }
    else
    {
        parent::__set($name, $value);
    }
}
?>

Docela dlouho jsem lovil chybu, nakonec jsem zjistil, že $property->getColumn() mi vrací NULL, tak je to teď hacknutý přes $property->name, ale rád bych to přepsal správně. A jak mám dostat do entity mapper, když je čerstvě vytvořená? Resp. jak na dev verzi řešit optimálně tento problém? (Akceptovat místo entit jejich id)

před 5 lety

Tharos
Člen | 1039
+
0
-

@Šaman: V aktuální develop verzi to ten sloupec už zase vrací. :) Zkus prosím provést update (jsem skalně přesvědčen, že tohle chování už se nebude měnit). Ve verzi, kde to v takovém případě vracelo NULL, je nutné jít pro ten sloupec do $property->getRelationship()->getColumnReferencingTargetTable() (pokud bys nechtěl/nemohl aktualizovat na aktuální develop verzi).

Nad tou poslední verzí je optimální řešení Tvého problému následující kód:

class BaseEntity extends \LeanMapper\Entity
{

    function __set($name, $value)
    {
        $property = $this->getCurrentReflection()->getEntityProperty($name);
        $relationship = $property->getRelationship();
        if (($relationship instanceof Relationship\HasOne) and !($value instanceof Entity)) {
            $column = $property->getColumn();
            $this->row->$column = $value;
            $this->row->cleanReferencedRowsCache($relationship->getTargetTable(), $column);
        } else {
            parent::__set($name, $value);
        }
    }

}

Volání getCurrentReflection() namísto getReflection() je jen drobná výkonnostní optimalizace. Smysl podle mě dává kontrolovat, že se jedná o hasOne vazbu, protože pro jinou tohle celé nemá smysl.

Čistit tu cache ručně je pořád bohužel nutné. Lean Mapper už může mít načtenou sadu souvisejících entit, ve které ale může chybět entita s ID, které se přiřadilo. Důvod, proč tohle nelze jednoduše detekovat přímo v Row (posléze v Result) je ten, že tyhle třídy už nemají znalost nadefinovaných vazeb. Ony už přímo neví, že když se zapíše do author_id, tak že to je sloupec reprezentující vazbu… Ještě ale promyslím, jestli by to přece jenom nešlo nějak hezky řešit.

Editoval Tharos (17. 7. 2013 9:12)

před 5 lety

Tharos
Člen | 1039
+
0
-

@Jan Tvrdík: Jo, tohle jsem svým způsobem věděl. Nedávno jsem to někde použil, ale včera jsem to použití nemohl najít a z hlavy už jsem si nevzpomněl na přesnou syntaxi, takže díky za osvěžení. :)

Tohle funguje pod MySQL. Ještě bych vyzkoušel SQLite a PostgreSQL (u něj ale nepředpokládám problém). Minimálně tyhle databáze mi v PHP ještě přijdou užitečné a rád bych je slušně podporoval. Pokud by to uměly i ony, bylo by o jednu překážku méně.

před 5 lety

castamir
Člen | 631
+
0
-

@Tharos docela bych uvítal možnost provádět akce po insertu/create

Narážím opět na closure table a vložení prvku do closury viz

INSERT INTO category_closure (ancestor, descendant, depth) VALUES (11,11,0); -- 11 je id nového prvku

INSERT INTO category_closure (ancestor,descendant, depth)
SELECT ancestor, 11, depth+1 FROM category_closure
WHERE descendant = 1; -- 1 je id rodice

do tvé aktuální verze 1.4 jsem si přidal jednu metodu afterCreate(Entity $entity), kterou volám po nastavení ID entitě v metodě persist. V poděděné afterCreate pak u konkrétního repozitáře provádím ty další 2 inserty. Chtělo by to minimálně přidat tohle anebo nějaký event, který bych si tam mohl přidávat callbackem via Nette\Object a property $on*

před 5 lety

Tharos
Člen | 1039
+
0
-

@Castamir: Vyřešilo by to tohle? Projdi si druhou část toho postu.

Jaké parametry by ty eventy měly přijímat? Určitě asi entitu, se kterou se pracuje. A co repositář nebo takové connection? Předávat automaticky, anebo to nechat na programátorovi při definování toho callbacku? To by se mně osobně možná líbilo více…

před 5 lety

castamir
Člen | 631
+
0
-

@Tharos spíš bych to viděl na Nette-way (tak jako u Nette\Object potazmo formularu)

Měl bys tam několik defaultních „callback-properties“ např. $onCreate, $onPersist, $onUpdate či $onDelete případně s prefixy before a after a jen bys registroval callback

// inside custom repository
$this->onAfterCreate[] = $this->addClosure($parentID);
//$this->onAfterCreate[] = Callback('addClosure', $parentID);

//...

function addClosure(Entity $entity, $parentID) {...}

ale když píšu tento příspěvek, tak mě napadá… kde se ten první příkaz bude vůbec volat? Nebo ten tvůj addEvent()? Po každém vytvoření repozitáře explicitně volat registraci eventů mi přijde špatné. Spíš automaticky volat inicializační metodu repozitáře v konstruktoru, kde si všechny tyhle signály zaregistruje repozitář sám a to zcela automaticky. Na mě bude jen podědit tuhle metodu a patřičně upravit její tělo

Editoval castamir (17. 7. 2013 11:09)

před 5 lety

Tharos
Člen | 1039
+
0
-

@Castamir: Bezva. V podstatě jsi parafrázoval to mnou navržené řešení, což je dobré znamení. :)

Já jen v té své ukázce používám „konzervativnější“ syntaxi:

$repository->addEventCallback(Repository::EVENT_BEFORE_UPDATE, ...);

namísto

$repository->onBeforeUpdate[] = ...;

K té výchozí inicializaci (aby se eventy nemusely registrovat ručně po vytvořené instance repositáře) by sloužila zmíněná protected metoda initEventsCallbacks().

Má „dynamické“ přidávání eventů vůbec smysl? Anebo vše ponechat na metodě initEventsCallbacks()? A co pojmenovat ji initEvents()? :) Ono ale pokud by se eventy registrovaly přes magické metody, veřejné volání by stejně asi nešlo zakázat…


class CustomRepository extends Repository
{

    public function afterDelete($entity)
    {
        // do something
    }

    protected function initEvents()
    {
        $connection = $this->connection;
        $this->onBeforeUpdate[] = function ($entity) use ($connection) {
            // do something with $entity and $connection
        };
        $this->onAfterCreate[] = function () use ($connection) {
            // do something with $connection
        };
        $this->onAfterDelete[] = array($this, 'afterDelete');
    }

}

Tohle by mi možná přišlo jako nejhezčí řešení.

Editoval Tharos (17. 7. 2013 11:41)

před 5 lety

castamir
Člen | 631
+
0
-

@Tharos podobně bys to mohl řešit i třeba pro selekci dat u repozitáře (defacto filtr). Např u tabulky systémových zpráv defaultně řadím podle času odeslání, v jiné tabulce zase řadím podle něčeho jiného… a když už budou eventy, tak proč dělat ty base filtry jinak? Přes initEvents by toho mohlo jít udělat i víc právě ;-) ale ikdyby ne, tak i s tímhle se prozatím spokojím ;-)

Edit: myslel jsem něco jako $onSelect[]

Editoval castamir (17. 7. 2013 12:48)

před 5 lety

Šaman
Člen | 2248
+
0
-

@Tharos:

  1. Mohla by být ta funkce $entity->getEnumValues() statická? Přece defacto vrací seznam statických konstant a používat se bude hlavně v selectech, kde nechci vytvářet instanci jen pro to, abych z ní načetl třídní konstanty. Díky.
  2. Poprosím, jestli bys přidal odkaz na GitHub do download sekce, protože (nevím proč už po několikáté) hledám tento odkaz na webu LeanMapperu pokaždé, když chci stáhnout novou dev verzi.
  3. Díky, $entity->getColumn() už mi funguje.

Offotpic: Jestli do konce prázdnin bude stable verze s dokumentací, tak mám jasno, co příští rok vyučovat v rámci vývoje internetových aplikací a Nette. Doteď byl s modelem problém – přeci jen ty ukázky v quickstartu nováčkům podávají trochu zkreslený pohled na problematiku ER diagramu (pletou si ho s návrhem db), mapování (tam všechno řeší repozitář) a pochopení toho, co je vlastně entita.
Myslím, že tohle jako základ je ideální. Přechod třeba na Doctrine pak není tak bolestný, když už nějaký ORM budou znát. A přitom se LeanMapper nesnaží kecat do všeho a je snadno upravitelný :)

Editoval Šaman (17. 7. 2013 17:05)

před 5 lety

mkoubik
Člen | 732
+
0
-

Když už jsme u toho offtopic, tak „entita“ a „pytel dat vyblitých z databáze“ neznamená totéž (i když se to často nesprávně zaměňuje). Normálně bych do toho nerýpal, ale když jsi zmínil že to budeš učit, tak by bylo dobré na tohle studenty upozornit.

Editoval mkoubik (17. 7. 2013 17:19)

před 5 lety

Vojtěch Dobeš
Člen | 1317
+
0
-

Klasické události pomocí Nette\Object bych nerušil, dá se do nich dobře optimalizovaně hooknout třeba pomocí Kdyby\Events :).

před 5 lety

Šaman
Člen | 2248
+
0
-

@mkoubik: Vím, moje první setkání s modelem byl matematický model nějaké fyzikální soustavy, tedy cosi, co s ORM, pětivrstvým modelem pro práci s databází a vůbec s úložištěm nemělo žádnou souvislost.
Ale u studentů (máme spíš cvičení než přednášky) budu rád, když si zapamatují, že ten pytel dat si může některé své interní záležitosti obstarávat sám. A že je to plnohodnotná třída, ne jen obálka.
Druhý problém, když tohle pochopí, je přesvědčit je, aby nestrkali do entity externí funkcionalitu – typicky hned začnou vytvářet ActiveRow apod. :D

před 5 lety

Casper
Člen | 253
+
0
-

@Tharos: Že LeanMapper již nedrží konvence v sobě je super. Narazil jsem však na další problém se single-table-inheritance. Pokud mám entitu Company a dědím od abstraktní User a zavolám:

$userRepository->find(1); // již existuje a najde
$userRepository->persist(new Company($data));
$userRepository->find(1); // skončí chybou

Pak první volání getEntityClass na mapperu při find správně dostane $row argument, přičemž druhé volání při find již ne. U mne to tedy skončí chybou Cannot instantiate abstract class.

Editoval Casper (17. 7. 2013 17:38)

před 5 lety

Tharos
Člen | 1039
+
0
-

@Casper: Jsem na stopě. Single table inheritance bude potřebovat ještě kosmetický zásah do repositáře.

Měl bys kousek kódu, na kterém bych to mohl hned začít ladit? Pokud ne, nevadí, vyrobím si ho sám (už tuším, kde je problém).

Díky za report!

před 5 lety

Tharos
Člen | 1039
+
0
-

@Casper: Takže problém by měl být opraven v develop větvi. Prosím o vyzkoušení… Byl to takový cache-related bug, proto chyba navenek vypadala takhle zvláštně.

před 5 lety

Tharos
Člen | 1039
+
0
-

Šaman napsal(a):

@Tharos:

  1. Mohla by být ta funkce $entity->getEnumValues() statická? Přece defacto vrací seznam statických konstant a používat se bude hlavně v selectech, kde nechci vytvářet instanci jen pro to, abych z ní načetl třídní konstanty. Díky.

To není žádný problém, můžeš si ji napsat jako statickou:

public static function getEnumValues($propertyName, IMapper $mapper = null)
{
    $property = self::getReflection($mapper)->getEntityProperty($propertyName);
    if (!$property->containsEnumeration()) {
        throw new InvalidArgumentException;
    }
    return $property->getEnumValues();
}

Tahle metoda není přímo součástí LeanMapper\Entity. Nechce se mi něco podobného zadrátovat přímo do jádra, protože se snažím, aby základní rozhraní entity moc nenakynulo. A tohle není metoda, kterou by využil úplně každý…

  1. Poprosím, jestli bys přidal odkaz na GitHub do download sekce, protože (nevím proč už po několikáté) hledám tento odkaz na webu LeanMapperu pokaždé, když chci stáhnout novou dev verzi.

Máš ho tam. :)

Offotpic: Jestli do konce prázdnin bude stable verze s dokumentací…

No tak do konce prázdnin určitě bude. To je moře času. :) Vývoj i podpora mě hodně baví a to je myslím základ.

před 5 lety

Casper
Člen | 253
+
0
-

@Tharos: Všechno už běhá parádně, díky.

Jinak neplánuješ zavést něco jako kontrolu validity entit? Aby nešlo perzistovat neúplné entity (s nevyplněnými atributy, které nejsou nullable?). Momentálně mohu uložit entitu, která má definovaných 20 atributů s vyplněným pouze jedním.

před 5 lety

castamir
Člen | 631
+
0
-

@Tharos možná by bylo fajn udělat nějaký rozcestník na problémy a jejich řešení zde na foru (zda jsou stále aktuální)

před 5 lety

Šaman
Člen | 2248
+
0
-

Možná by nebylo od věci zprovoznit nějaké minifórum přímo na stránkách LeanMapperu. Ale počkal bych až bude dokumentace.
Vypadá to, že by se kolem tohoto ORMu mohla vytvořit komunita, určitě by bylo zajímavé sdílet Base* třídy a nadstavby nad základním LeanMapperem.

před 5 lety

Tharos
Člen | 1039
+
0
-

@Casper: Už mě to jednou taky napadlo… Mám s tím mírné dilema.

Ona se aktuálně ta entita neuloží, protože tomu zabrání databáze. :) Pokud někdo má v entitě sloupec, který není nullable, a persistuje jej do nullable sloupce v databázi, z mého pohledu má chybně navrženou databázi.

Jenomže je tu spousta ale… Sloupec v databázi může být nullable z nějakých zpětně kompatibilních důvodů, položka entity nemusí odpovídat nějakému jednomu sloupci… napadá mě toho řada.

Suma sumárum tu kontrolu možná zavedu. Přinese to i pár komplikací – já třeba s oblibou používám defaultní hodnoty v databázi pro sloupce s logickými hodnotami a to tedy budu muset ještě jednou dublovat… Ale výhody (větší robustnost) to asi převáží.

Díky za tip!

před 5 lety

Tharos
Člen | 1039
+
0
-

@castamir, @Šaman: Minifórum mě asi nemine, protože tohle jedno vlákno je už teď takové neohrabané. Nehledě na to, že zde mám rozjetý snad rekordní off-topic ;) (téma nesouvisející s Nette).

Od příštího týdne budu mít na Lean Mapper zase podstatně více čase. Odhaduji, že do konce příštího týdne vypustím verzi 1.5. Do ní zbývá dokončit:

  • Vylepšit parser anotací (aby byl více robustní, nezáleželo na pořadí definic, umožňoval vlastní definice atp.), viz RFC v tomto vlákně.
  • Zavést podporu pro defaultní hodnoty, viz RFC v tomto vlákně.
  • Redesignovat filtry, viz RFC v tomto vlákně.

Pak výrazně uberu na plynu u nových funkcionalit a začnu se zase věnovat dokumentaci. Jsem si jist, že do konce prázdnin bude vše zdokumentované, budou k dispozici ukázkové příklady, návody na konkrétní problémy (jakási best-practices) a také nějaké minifórum přímo na webu Lean Mapperu.

Eventy zatím směřuji do verze 1.6. Je pravděpodobné, že do konce léta vyjde ještě i ta (ono mě taky nebude bavit pořád jenom psát dokumentaci…).

Editoval Tharos (18. 7. 2013 17:43)

před 5 lety

peter.z
Člen | 37
+
0
-

Bolo by mozne do 1.5 vtesnat aj udalosti? :) Kludne aj klasickym Nette style (onSomething) s podporou zakladnych eventov – onInsert, onUpdate, onPersist, onDelete (prip. ich varianty s after a before)

před 5 lety

Tharos
Člen | 1039
+
0
-

@peter.z: Pokud nikdo nic nenamítne proti návrhu API a fungování v tomhle příspěvku (ke konci), tak by to asi šlo. :) Asi by to stálo za to i za cenu oddálení vydání třeba o den nebo dva…

Pokud by ale někdo měl k navrženému řešení nějaké věcné výhrady, ještě bych to asi nechal trochu uzrát (záleželo by na těch výhradách).

před 5 lety

Šaman
Člen | 2248
+
0
-

Opět jsem narazil na ten rozpor property v anotaci + set/get metody. Tentokrát jsem chtěl upravit jen setter (složitější kontrola dat), ale getter bych rád nechal. Mě připadá skutečně nejlepší řešení když metoda má přednost před defaultním chováním v anotaci. Nevím, jak ostatní, ale já jsem na toto chování docela navyklý a LM mě trochu mate. (Navíc se tím sjednotí volání $entity->foo a $entity->getFoo(), jejichž rozdílnost je občas wtf.)

A co se týče enumu – to zpracování konstant je originální, ale spíš bych ho viděl v nějaké Base třídě, než v jádru. Zato by se mi často hodilo, kdyby bylo možné předat enumům pole (jako property, i (hlavně) jako metodu).
Teď si LM vytváří to pole samo jediným možným způsobem (z konstant), což mi přijde těžkopádné a já si v některých případech musím kontrolu složitějších výčtů hlídat sám (proto chci přetížit setter).

Editoval Šaman (18. 7. 2013 18:04)

před 5 lety

Tharos
Člen | 1039
+
0
-

@Šaman: (Představme si nyní, že už je naimplementované toto RFC. a vydaná verze 1.5)

Pokud volání $entity->foo a $entity->getFoo() vede k získání hodnoty jiným způsobem, je to špatně. To je totálně matoucí. Ale to může nastat pouze tehdy, pokud je položka nadefinovaná zároveň pomocí anotace i metody, což je také špatně. Položka by měla být nadefinována buďto pomocí anotace, anebo pomocí metody – v tomto případě lze pouze přidat anotaci s příznakem m:hintOnly, aby IDE napovídalo.

Jak o položkách uvažuji já (a na žádné kolize či WTF nenarážím):

  • K položkám přistupuji zpravidla stylem $entity->foo, dokud nepotřebuji předat parametry nějakému filtru (ať už jsou nadefinované pomocí anotace, anebo metody – funguje to v obou případech). Pokud potřebuji předat parametr nějakému filtru, použiji zápis $entity->getFoo($parameter).
  • Položky definuji pomocí anotací a pouze tam, kde mi anotace nestačí, použiji definici pomocí metody. V takovém případě si ještě doplním anotaci m:hintOnly, aby mi IDE napovídalo. Důležité je, že ať už potom zavolám $entity->foo nebo $entity->getFoo(), vždy to proteče přes tu metodu. Prostě ta položka je vyjádřená pomocí metody.
  • Od verze 1.5 mi na mnohé stačí pouze anotace, protože díky příznaku m:passthru mohu například zkontrolovat validní e-mail atp. Pomocí tohoto příznaku mohu například i snadno zkontrolovat, zda se hodnota nachází ve výčtu nadefinovaném jako privátní pole dané entity. :)

Sečteno podtrženo, vážně nerad bych na té logice aktuálně něco měnil. Možná to má logiku trochu jinout než Nette\Object, ale myslím, že jasnou. Stačí si ji jen trochu zažít. Důležité je zažít si, že položka musí být nadefinována pomocí anotace, anebo pomocí metody (výlučně). Jakákoliv nejednoznačnost přichází až spolu s kouzlením a pokusy nadefinovat něco oběma způsoby zároveň. Něco takového by nemělo být vůbec zapotřebí.

před 5 lety

Šaman
Člen | 2248
+
0
-

No, ve chvíli, kdy se ti něco objeví i v anotaci i jako metoda, tak se LM bude chovat při volání ->foo a ->getFoo() odlišně. Pokud by vždy dával přednost metodě před anotací, tak to bude konzistentní.

Pokud budu důsledně dodržovat buď jednu, nebo druhou možnost, problém nenastane ať se to bude chovat postaru, nebo jak navrhuji.

A mít jednu věc zapsanou pomocí metod i anotací mi jako chyba nepřijde. Jen logicky očekávám, že metoda se vykoná a anotace je jen dokumentační (protože magie). A ručně napsaná metoda by měla mít před magií přednost.
HintOnly tento problém sice vyřeší, ale pořád je to break proti konvencím všech ostatních tříd. S Nette to souvisí jen málo, konkrétně tím překladem property na metodu. Ale pokud bych jinde definoval magické settery a gettery, tak jejich explicitním naprogramováním se ty magické ‚vypnou‘. A tady ne.

Doufám, že jsem se v tomto kostbatém textu vyjádřil srozumitelně :)


S tím enumem je sice fakt, že to půjde pořešit pomocí passthru, ale že by enum takto magicky zpracovával konstanty mi stále připadá divné. :

<?php

// intuitivně bych nejprve zkusil toto
/**
 * @property string $month m:enum(['leden', 'unor', 'brezen'])
 */

// nebo něco takového
/**
 * @property string $month m:enum(self::$months)
 * @property string $month m:enum($this->getMonths())
 */
?>

To vytváření pole z konstant se může hodit, ale považuji to za dost specifickou věc.


Někde jsem zahlédl anotaci @inheritdoc, ta ale má fungovat trochu jinak, než jak ji používáš ty. Nutno dodat, že v NetBeansech mi to nefunguje ani tak, jak je popsané.
Ale hlavní je, že:

<?php

// tohle nebude napovídat (NetBeans),
// resp. napovídá prázdný název i popis i parametry.
/**
 * @inheritdoc
 */
function Foo()
{...}

// tohle zdědí kompletní nápovědu od předka, je-li nějaká.
// To samé i kdyby tam nebyla vůbec žádná anotace.
// Já to tak píšu jen abych věděl, že sem už jiná dokumentace nepatří. Ale není to pravá anotace.
/*
 * @inheritdoc
 */
function Bar()
{...}
?>

Editoval Šaman (18. 7. 2013 21:56)

před 5 lety

Casper
Člen | 253
+
0
-

@Tharos:

  1. Co se týče rozdílného chování $entity->foo a $entity->getFoo(), taky jsem při přidávání specifických metod ponechal anotaci a divil se, že to neběhá – takže trochu wtf. Nicméně jsem v tomto fóru minul flag m:hintOnly a s ním už to imho smysl dává.
  2. S těmi neúplnými entitami nevím jestli jsme se pochopili. Pokud mám rozdílnou definici nullable atributů/sloupců v db a entitě, je to samozřejmě špatně. Šlo mi však především o to, že mohu jako programátor zapomenout vyplnit některé (ne-nullable) atributy entity a LM jí i přesto uloží do db, která tam vyplní defaultní hodnoty (pro VARCHAR například prázdný řetězec). A od téhle chyby by mě ORM mohlo (mělo?) odradit, pokud má být robustní. Aneb pokud mám v entitě atribut, který není nullable (alias NOT NULL sloupec v db), měl by být při perzistenci vyplněn.
  3. Taktéž zkus zvážit variabilnější metodu getData (popřípadě jinou, něco jako getPreviewData). Často (defaultní data formuláře, výpis do tabulky, uložení dat uživatele do session (Identity)) potřebuji získat „základní“ data entity bez vazeb, tedy bez (kolekcí) navázaných entit, kterých může být hodně. Aneb při těchto použitích, kdy potřebuji surová vlastní data entity, kdy mě nezajímají vazby na ostatní objekty, je zbytečné se na ně dotazovat do databáze. Je zřejmé, že by nebylo vhodné vytvořit tuto funkcionalitu natvrdo jak píšu, ale spíše nějakým způsobem mít možnost ovlivnit jaké hodnoty mi getData vrátí a jaké už ne. Implementací je mnoho (whitelist, blacklist, pomocná protected metoda pro rozhodnutí, zda atribut zahrnout či ne, …). Osobně jsem si již něco takového implementoval a využívám to poměrně často.

Editoval Casper (18. 7. 2013 21:46)

před 5 lety

peter.z
Člen | 37
+
0
-

Casper napsal(a):

@Tharos:

  1. Co se týče rozdílného chování $entity->foo a $entity->getFoo(), taky jsem při přidávání specifických metod ponechal anotaci a divil se, že to neběhá – takže trochu wtf. Nicméně jsem v tomto fóru minul flag m:hintOnly a s ním už to imho smysl dává.
  2. S těmi neúplnými entitami nevím jestli jsme se pochopili. Pokud mám rozdílnou definici nullable atributů/sloupců v db a entitě, je to samozřejmě špatně. Šlo mi však především o to, že mohu jako programátor zapomenout vyplnit některé (ne-nullable) atributy entity a LM jí i přesto uloží do db, která tam vyplní defaultní hodnoty (pro VARCHAR například prázdný řetězec). A od téhle chyby by mě ORM mohlo (mělo?) odradit, pokud má být robustní. Aneb pokud mám v entitě atribut, který není nullable (alias NOT NULL sloupec v db), měl by být při perzistenci vyplněn.
  3. Taktéž zkus zvážit variabilnější metodu getData (popřípadě jinou, něco jako getPreviewData). Často (defaultní data formuláře, výpis do tabulky, uložení dat uživatele do session (Identity)) potřebuji získat „základní“ data entity bez vazeb, tedy bez (kolekcí) navázaných entit, kterých může být hodně. Aneb při těchto použitích, kdy potřebuji surová vlastní data entity, kdy mě nezajímají vazby na ostatní objekty, je zbytečné se na ně dotazovat do databáze. Je zřejmé, že by nebylo vhodné vytvořit tuto funkcionalitu natvrdo jak píšu, ale spíše nějakým způsobem mít možnost ovlivnit jaké hodnoty mi getData vrátí a jaké už ne. Implementací je mnoho (whitelist, blacklist, pomocná protected metoda pro rozhodnutí, zda atribut zahrnout či ne, …). Osobně jsem si již něco takového implementoval a využívám to poměrně často.
  1. Nepostaci ti metoda getRowData()?

před 5 lety

Casper
Člen | 253
+
0
-

peter.z napsal(a):

  1. Nepostaci ti metoda getRowData()?

Nestačí, protože vrací atributy s jiným (nízkoúrovňovým) pojmenováním. Celá aplikace kromě repozitáře nebo mapperu pracuje s vysokoúrovňovými. Pokud bych taková data nalil jako výchozí hodnoty do formuláře a pak z nich chtěl zpátky vytvořit entitu, všechny atributy, které se budou lišit v názvu způsobí MemberAccessException (Undefined property), protože perzistence pracuje s vysokoúrovňovými atributy.

před 5 lety

Casper
Člen | 253
+
0
-

A ještě detail. Je nějaký zásadní důvod, proč metody typu beforeCreate dostávají jako argument pole modifiedData a nikoli celou entitu (krom toho, že repozitář v těchto nízkoúrovňových operacích většinou pracuje s nízkoúrovňovými daty)?

Pokud totiž například potřebuji při ukládání entity vygenerovat hodnotu nějakého sloupce na základě hodnot vazebních entity, musím poměrně složitě tyto entity hledat – musím mít závislost na cizích repozitářích a na základě vazebních atributů je manuálně dotahovat (případně se dotazovat přímo do db). Pokud bych měl přístup k celé entitě, bude to jedno prosté volání.

Aneb brání něco tomu, aby tyto metody přijímaly $entity a bylo na programátorovi, co si z nich vytáhne? Metodu getModifiedRowData() si můžu zavolat vždycky.


Edit: teď mi došlo, že na můj problém bylo asi bylo vhodnější využít plánovaných defaultních hodnot, nicméně odpověď na dotaz mě zajímá i tak :)

Editoval Casper (19. 7. 2013 12:06)

před 5 lety

Etch
Člen | 404
+
0
-

Opět jeden takovej filozofickej dotaz. Je nějaký důvod, proč se v \LeanMapper\Entity v getEntityClass nepředává do mapperu celé \LeanMapper\Reflection\Property

protected function getEntityClass(Property $property, Row $row = null)
{
    return $this->mapper->getEntityClass($property, $row);
}

a v mapperu pak není něco na způsob:

public function getEntityClass($table, Row $row = null)
{
    if($table instanceof Property){
        return $table->getType();
    }
    return $this->defaultEntityNamespace . '\\' . ucfirst($table);
}

při využití pouhého:

return $this->defaultEntityNamespace . '\\' . ucfirst($table);

totiž vzniká drobný problém pokud člověk pracuje s DB která nedodržuje téměř žádné konvence (ano jsem nucen s ní pracovat :) ), že dřív nebo později se člověk dostane do situace, že buď to hledá entitu v blbé tabulce, nebo to sice entitu hledá ve správné tabulce, ale zas to nedokáže detekovat správnou třídu entity.

Vím, že je to problém způsobený tím, že autor DB bylo „prase“, ale ptám se hlavně z toho důvodu, jestli využití \LeanMapper\Reflection\Property nebo přesněji LeanMapper\Reflection\PropertyType::$type nepřináší nějaký na první pohled ne dobře viditelný problém.

před 5 lety

Filip111
Člen | 244
+
0
-

Ahoj, narážím na nefunkční isset() nad jednotlivými property entity.
Příklad:

/**
* @property int $id
* @property string|null $title
* @property bool $status
*/
class Book extends BaseEntity {
}

a zkusím isset($entity->id), tak to taky nic nenajde.

Když si tuto entitu předám do šablony a používám makro {ifset $entity->id}, tak taky nic.
Samotný výpis $entity->id je ale v pořádku.

Konkrétně jsem na to narazil při spojení s NextrasGridem.
NextrasGrid kontroluje u entity řádku, zda obsahuje property potřebnou pro výpis daného sloupce https://github.com/…Datagrid.php#L328 a používá k tomu funkci isset. Getter jsem si pomocí callbacku přepsal ($row->getCurrentReflection()->getEntityProperty($column) přičemž getCurrentReflection jsem si prasácky podědil jako public fci ), ale vzápětí jsem na to narazil i v latte.

Dalo by se s tim něco dělat?

před 5 lety

Šaman
Člen | 2248
+
0
-

No, ony jsou ty property jen virtuální (magicky přes getId()), takže když si tu entitu dumpneš, tak vidíš, že má jen $row a nic víc. Takže isset na to asi stačit nebude, muselo by se to nějak detekovat přímo v entitě.

před 5 lety

castamir
Člen | 631
+
0
-

něco na tento způsob by nešlo?

// BaseEntity
function __isset($key) {
    return isset($this->__get(key));
}
Stránky: Prev 1 … 4 5 6 7 8 … 23 Next RSS tématu