Oznámení
před 6 lety
- Tharos
- Člen | 1042
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 6 lety
- Šaman
- Člen | 2275
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 6 lety
- Casper
- Člen | 253
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 6 lety
- Tharos
- Člen | 1042
@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 6 lety
- Tharos
- Člen | 1042
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 6 lety
- Casper
- Člen | 253
Super, oprava funguje. Ještě bych měl pár poznámek, na které jsem narazil při prvotním používání:
- 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í :) - 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. - 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í. - 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 6 lety
- Tharos
- Člen | 1042
@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 6 lety
- Tharos
- Člen | 1042
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 6 lety
- Jan Tvrdík
- Nette guru | 2550
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 6 lety
- Šaman
- Člen | 2275
@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 6 lety
- Tharos
- Člen | 1042
@Š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 6 lety
- Tharos
- Člen | 1042
@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 6 lety
- castamir
- Člen | 631
@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 6 lety
- Tharos
- Člen | 1042
@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 6 lety
- castamir
- Člen | 631
@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 6 lety
- Tharos
- Člen | 1042
@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 6 lety
- castamir
- Člen | 631
@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 6 lety
- Šaman
- Člen | 2275
@Tharos:
- 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. - 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.
- 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 6 lety
- mkoubik
- Generous Backer | 734
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 6 lety
- Vojtěch Dobeš
- Člen | 1317
Klasické události pomocí Nette\Object
bych nerušil, dá se
do nich dobře optimalizovaně hooknout třeba pomocí
Kdyby\Events
:).
před 6 lety
- Šaman
- Člen | 2275
@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 6 lety
- Casper
- Člen | 253
@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 6 lety
- Tharos
- Člen | 1042
Šaman napsal(a):
@Tharos:
- 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ý…
- 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 6 lety
- Casper
- Člen | 253
@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 6 lety
- Tharos
- Člen | 1042
@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 6 lety
- Tharos
- Člen | 1042
@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 6 lety
- Tharos
- Člen | 1042
@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 6 lety
- Šaman
- Člen | 2275
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 6 lety
- Tharos
- Člen | 1042
@Š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 6 lety
- Šaman
- Člen | 2275
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 6 lety
- Casper
- Člen | 253
@Tharos:
- 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 flagm:hintOnly
a s ním už to imho smysl dává. - 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.
- 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 6 lety
- peter.z
- Člen | 37
Casper napsal(a):
@Tharos:
- 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 flagm:hintOnly
a s ním už to imho smysl dává.- 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.
- 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.
- Nepostaci ti metoda getRowData()?
před 6 lety
- Casper
- Člen | 253
peter.z napsal(a):
- 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 6 lety
- Casper
- Člen | 253
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 6 lety
- Etch
- Člen | 404
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 6 lety
- Filip111
- Člen | 244
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?