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 | 1036
+
0
-

Ahoj,

naruším si svůj zdejší off-topic ještě větším off-topicem…

Pár lidí se mě prostřednictvím e-mailu ptalo, jestli je možné vývoj Lean Mapperu nějak podpořit. Ano, přiznám se, že doslova pár lidí. :)

Založil jsem proto na webu jednu stránku týkající se této záležitosti.


V každém případě velké díky za přízeň a za zdejší náměty, inspirace, zkušenosti… Zkrátka za to, že tu knihovnu používáte!

Editoval Tharos (11. 8. 2013 1:39)

před 5 lety

daniel.mejta
Člen | 21
+
0
-

Tharos napsal(a):
Ahoj,
naruším si svůj zdejší off-topic ještě větším off-topicem…

Tak jo, nastavil jsem trvalý příkaz, takže každý měsíc dorazí můj malý příspěvek na vývoj a podporu. Snad se přidá více lidí, LeanMapper si to zaslouží… :)

Edit: Stálo by za to, aby byl web a dokumentace i v angličtině, český píseček je dost malý…

Editoval daniel.mejta (11. 8. 2013 3:19)

před 5 lety

Casper
Člen | 253
+
0
-

@Tharos:

Tohle je pořád funkční, ale rád vysvětlím, na jaký jev jsi právě narazil.
Entita vytvořená úplně „ze vzduchoprázdna“ (new Book) v sobě nemá mapper, a proto pořádně neví, co kam patří. Ona sice ze svých anotací ví, že má řekněme 1:N vazbu na entitu Author, ale už neví, v jakém vazebním sloupci má mít uložen cizí klíč. Proto si entita až do té doby, než se dostane k mapperu, udržuje cizí klíče v jakýchsi virtuálních low-level položkách (to je například to column{hasOne:FozcflAR7K}).

Díky za vysvětlení, nicméně pokud někde nedělám nějakou hloupost, nefunguje to pro attached entity. Například pro editační formuláře je to tedy nepoužitelné. Šlo by nějak jednoduše doplnit tuto funkcionalitu i pro ně?

Editoval Casper (11. 8. 2013 22:15)

před 5 lety

Tharos
Člen | 1036
+
0
-

@Casper: Attached entita nemůže bez mapperu existovat – volání Entity::isDetached probublá až do Result::isDetached a tam se přítomnost mapperu vysloveně kontroluje.

Rád se na to podívám. Určitě je možné, že jsem nějaký scénář úplně nedomyslel. Mohl bys mi prosím poslat nějaký minimální kód, jak se k tomu problému dopracovat? Stačí jen nosné části – vytvoření entity, co se s ní poté dělá atp…

před 5 lety

Casper
Člen | 253
+
0
-

@Tharos:

Používám tento mapper a tvou BaseEntity a nejjedodušší ukázka je zde:

// tabulka user
| user_id | user_type_id
| 1       | 1

/**
 * @property int $userId
 * @property UserType|null $userType m:hasOne
 */
class User extends BaseEntity {}


// tabulka user_type
| user_type_id | user_type
| 1            | Foo
| 2            | Bar

/**
 * @property int $userTypeId
 * @property string $userType
 */
class UserType extends BaseEntity {}



// použití
$user = $userRepository->getSingle(1);
$user->assign(array(
    "userType" => 2
));
$userRepository->persist($user); // Unknown column 'usertype{hasOne:ID2Tj7oH5z}' in 'field list'

před 5 lety

Tharos
Člen | 1036
+
0
-

No jo, já jsem ale blbec. Podařilo se mi problém zreprodukovat a příčina je úplně jasná.

V té BaseEntity pouze nahraď řádek:

$property = $this->getReflection()->getEntityProperty($name);

za tento:

$property = $this->getCurrentReflection()->getEntityProperty($name);

A vše bude fungovat podle očekávání.

Důvod: Od té doby, co je v Lean Mapperu samostatný mapper, přijímá metoda Entity::getReflection jako volitelný parametr IMapper. Pokud jej nedostane, poradí si s tím, ale právě se vytváří ty virtuální vazební sloupce. U attached entity je možné ten mapper předat ručně ($this->getReflection($this->mapper)), ale mnohem výhodnější je zavolat $this->getCurrentReflection(), což je jednak výkonnější, ale také se nemusí řešit předávání mapperu (získá se na pozadí, pokud je k dispozici).

Editoval Tharos (12. 8. 2013 10:26)

před 5 lety

Casper
Člen | 253
+
0
-

@Tharos:

A vše bude fungovat podle očekávání.

Funguje, díky moc.

před 5 lety

Ripper
Člen | 56
+
0
-

Zdravím,

tohle je můj první ORM, který jsem začal používat. Nicméně asi to bude trapná otázka, ale – jak správně přidávat a odstraňovat z databáze? Zatím jsem to řešil tak, že jsem si přidal funkci do Repository.

Například:

/**
     * Remove function
     *
     * @param array $where
     *
     * @return \DibiResult|int
     */
    public function remove(array $where)
    {
        return $this->connection->delete($this->getTable())->where($where)->execute();
    }

Edit:

Tak už jsem na to přišel. Udělal jsem to následovně –

$user = new User; // Entity
$user->email = 'test@test.cz';

$this->userRepository->persist($user);

Edit #2:

Narazil jsem na menší problém při ukládání.

Mám následující –

/**
 * @property int $id
 * @property Role[] $role_id m:belongsToOne(id:role)
 * @property User[] $user_id m:belongsToOne(id:user)
 */
class UserRole extends Entity
{

}

Ale –

$addUserRole = new UserRole;
$addUserRole->role_id = $value;
$addUserRole->user_id = $this->getUser()->getId();

$this->userRoleRepository->persist($addUserRole);

Tohle vyhazuje hlášku „*Invalid property annotation given: property with ‚belongsToOne‘ relationship type must not contain collection.*“. Netuším vůbec proč, mohl by někdo poradit?

Editoval Ripper (13. 8. 2013 8:26)

před 5 lety

Tharos
Člen | 1036
+
0
-

@Ripper: Ahoj,

na přidávání do databáze už jdeš dobře:

$user = new User; // Entity
$user->email = 'test@test.cz';

$this->userRepository->persist($user);

Odstraňování je také jednoduché a potřebná metoda už v BaseRepository je:

$this->userRepository->delete($user);

$this->userRepository->delete(1); // funguje i výmaz podle ID

Anotace User[] $user_id m:belongsToOne(id:user) je skutečně logicky chybná, protože se jedná o vazbu 1:1 (…ToOne) a typ takové položky nesmí obsahovat kolekci (tj. ty hranaté závorky na konci typu).

Editoval Tharos (13. 8. 2013 9:54)

před 5 lety

Ripper
Člen | 56
+
0
-

@Tharos

Ahoj, děkuji za odpověď, pomohlo to.

Upravil jsem to na –

/**
 * @property int $id
 * @property Role $role_id m:belongsToOne(id:role)
 * @property User $user_id m:belongsToOne(id:user)
 */
class UserRole extends Entity
{

}

Ale to při ukládání –

$addUserRole = new UserRole;
$addUserRole->role_id = $value;
$addUserRole->user_id = $this->getUser()->getId();

$this->userRoleRepository->persist($addUserRole);

Vyhazovalo následující hlášku – „Only entities can be set via magic __set on field with relationships“.
Tak jsem to upravil na –

/**
 * @property int $id
 * @property Role $role_id m:belongsToOne(id:role)
 * @property User $user_id m:belongsToOne(id:user)
 * @property int $role_id
 * @property int $user_id
 */
class UserRole extends Entity
{

}

Je to takhle správně? Nevadí to ničemu? Vše funguje jak má. Díky! :)

před 5 lety

Tharos
Člen | 1036
+
0
-

@Ripper: Prozradil bys mi schéma databáze? Stačí těch pár tabulek. Pokud je to něco podobného:

user
 • id (primary key)

user_role
 • user_id (foreing key)
 • role_id (foreing key)

role
 • id (primary key)

navrhl bych entity a práci s nimi jinak.

Editoval Tharos (13. 8. 2013 12:53)

před 5 lety

Casper
Člen | 253
+
0
-

@Tharos:
Jenom malý dotaz: Plánuješ vylepšit podporu pro M:N vazby? Tedy jestli zmizí zmíněné omezení pro perzistenci vazeb u nových objektů a přibude metoda hasInXXX?

před 5 lety

Tharos
Člen | 1036
+
0
-

@Casper: V podstatě obojí zvažuji.

Přidat hasIn<name> nebude nic složitého, takže na to asi i v dohledné době dojde.

K tomu zmíněnému omezení – rozmyslím, co by to obnášelo a zda by to šlo vyřešit nějak elegantně. Ono to totiž celý mechanismus v pozadí dost zesložiťuje. Takže na to asi taky někdy dojde, ale má to spíš menší prioritu.

před 5 lety

Tharos
Člen | 1036
+
0
-

Ahoj,

Lean Mapper od včerejška disponuje užitečnou novou funkcionalitou, a tou je podpora událostí.

Jádrem API je možnost dynamické registrace v LeanMapper\Repository:

$authorRepository = new AuthorRepository($connection, $mapper);

$authorRepository->onBeforePersist[] = function (Author $author) {
    // do something usefull
}

$authorRepository->onAfterCreate[] = array($authorService, 'doSomething');
$authorRepository->onBeforeDelete[] = 'AuthorService::doSomethig'; // static call

Události se dají „štosovat“ stejným způsobem, jako události u Nette\Object. Ty v Lean Mapperu jsou API Nette\Object silně inspirované (byť implementace je trochu jiná).


Velmi užitečnou metodou je protected metoda LeanMapper\Repository::initEvents, která se volá při vytváření instance repositáře a její přetížení umožňuje nadefinovat události pro daný repositář:

protected function initEvents()
{
    $this->onBeforePersist[] = ...
    $this->onAfterPersist[] = ...
}

Nadefinovat události tímto způsobem lze považovat za best practice.


Lze pracovat s následujícími událostmi:

  • onBeforePersist
  • onBeforeCreate
  • onBeforeUpdate
  • onBeforeDelete
  • onAfterPersist
  • onAfterCreate
  • onAfterUpdate
  • onAfterDelete

Význam událostí je snad jasný z názvu. Každý zaregistrovaný callback dostane jako parametr entitu, se kterou se pracuje.

Věnujte pozornost tomu, kdy přesně se callbacky zaregistrované k jednotlivým událostem volají, hlavně pak v metodě Repository::persist. Za zmíňku stojí, že při persistenci se beforePersist a afterPersist zavolají vždy, zatímco beforeCreate, afterCreate, beforeUpdate a afterUpdate se volají podle situace a také podle toho, zda byla změněna nějaká data. Tohoto chování lze výhodně využít.


Události primárně slouží k vyvolání různých postraních efektů persistence. Typický příklad využití je v případě, kdy po vytvoření nějakého záznamu v databázi se má ještě někde na disku vytvořit nějaký soubor (například předgenerovaný avatar…) a tak podobně. Takovouto doménovou logiku lze řešit více způsoby a události jsou jedním z nich.

Události nevnášejí žádný BC break, takže můžete s klidným duchem updatovat na aktuální develop.


No a tím se mi víceméně vyprázdnil TODO pro verzi 1.5, zbývá už jen pár kosmetických drobností. V horizontu několika dní tedy vydám RC verze 1.5 a pak se začnu opět více věnovat dokumentaci.

Díky všem za podporu! Velmi si jí vážím.

Editoval Tharos (14. 8. 2013 12:58)

před 5 lety

Ripper
Člen | 56
+
0
-

@Tharos

Ahoj, schéma vypadá asi nějak takhle –

user
- id
- email
- password

role
- id
- name

user_role
- user_id (foreing key)
- role_id (foreing key)

Jak by jsi to navrhnul ty? Děkuji za odpověď.

Editoval Ripper (14. 8. 2013 13:52)

před 5 lety

Casper
Člen | 253
+
0
-

@Tharos:

Tak jsem chtěl ozkoušet ty M:N vazby, nicméně bez úspěchu. Vše proběhne bez jediné chyby, nicméně do databáze se nic neuloží. Použití mám podstatě totožné s ukázkou a testy M:N vazeb jsem na githubu nenašel. Používám stále stejný mapper, nadhodím použití:

/**
 * @property int $articleId
 * @property Tag[] $tag m:hasMany
 */
class Article extends BaseEntity {}

// použití
$article = $articleRepo->getSingle(1);
$article->addToTag(1); // zkoušel jsem dosadit i konkrétní entitu
$articleRepo->persist($article); // proběhne, ale tabulka article_tag je stále prázdná

Používám poslední dev verzi s eventy.

před 5 lety

Tharos
Člen | 1036
+
0
-

@Casper: Jsem na stopě, dej mi prosím tak 15 minut. Díky za upozornění.

před 5 lety

Tharos
Člen | 1036
+
0
-

@Casper: Tak, vyzkoušej prosím aktuální develop.

Btw. tohle mám za to, že jsem byl na tuhle část línej napsat testy. :–P

Editoval Tharos (14. 8. 2013 16:48)

před 5 lety

Casper
Člen | 253
+
0
-

@Tharos:

Tak, vyzkoušej prosím aktuální develop.

Zmíněný nejjednodušší scénář funguje, nicméně pokud pracuji s více M:N vazbami:

/**
 * @property int $articleId
 * @property Tag[] $tag m:hasMany
 * @property Category[] $category m:hasMany
 */
class Article extends BaseEntity {}

// použití
$article = $articleRepo->getSingle(12);
$article->addToTag(1);
$article->addToCategory(2);
$articleRepo->persist($article);

pokusí se LeanMapper při perzistenci použít tento dotaz:

INSERT INTO `article_category` (`article_id`, `tag_id`)
VALUES (12, 1) , (12, 2)

Což je samozřejmě špatně, spojil perzistenci dvou vazeb do jednoho SQL.

Editoval Casper (14. 8. 2013 17:02)

před 5 lety

Tharos
Člen | 1036
+
0
-

@Casper: Ok. Tak to mi dej víc než 15 minut :). Zreprodukuji si to u sebe a fixnu to. Příčinu tuším, ale chci si nechat projít hlavou optimální řešení.

před 5 lety

Tharos
Člen | 1036
+
0
-

@Casper: Takže… Mohl bys prosím ručně vyzkoušet tenhle build ?

Editoval Tharos (14. 8. 2013 17:41)

před 5 lety

paranoiq
Člen | 388
+
0
-

@Tharos řešil jsem teď modulární aplikaci nad jiným ORM a hlavní problém byl jak provazovat entity, které se nahrávají z pluginů do aplikace (a nemohou modifikovat původní kód aplikace)

jdou v Lean Mapperu vazby mezi entitami definovat dynamicky, tzn. jinak než anotacemi na entitách?

před 5 lety

Casper
Člen | 253
+
0
-

@Tharos:

Mohl bys prosím ručně vyzkoušet tenhle build?

Vypadá to naprosto v pořádku :) Díky moc za rychlost.

Nechce se ti doplnit tu metodu hasIn<name> když už seš v tom? ;)

před 5 lety

Šaman
Člen | 2240
+
0
-

@paranoiq: Lze – pomocí metod. Takže pomocí magických funkcí bys asi dokázal propojovat i neznámé entity. Ale podporu pro to si budeš muset dopsat sám.

před 5 lety

Tharos
Člen | 1036
+
0
-

@Casper:

Vypadá to naprosto v pořádku :) Díky moc za rychlost.

Tak super. Už je to i v develop větvi. Přihodím k tomu už jenom pár testů…

Nechce se ti doplnit tu metodu hasIn<name> když už seš v tom? ;)

Je v pořadníku vysoko :), ale dneska už to nestihnu. Mám rozpracovanou drobnou dekompozici Repository (která se mimochodem rýsuje podle mě moc hezky – třída se reálně zeštíhlí a překvapivě toho bude umožňovat víc) a nerad bych přepínal myšlenky.

před 5 lety

Tharos
Člen | 1036
+
0
-

@Ripper:

Zmíněné schéma bych osobně do entit převedl následovně:

/**
 * @property int $id
 * @property string $email
 * @property string $password    a hezky plain text! :)
 * @property Role[] $roles m:hasMany
 */
class User extends \LeanMapper\Entity
{
}

/**
 * @property int $id
 * @property string $name
 */
class Role extends \LeanMapper\Entity
{
}

A práce s tím by pak v náznaku vypadala následovně:

$user = $userRepository->find($id);

$user->addToRoles($role);

$user->removeFromRoles($role);

$userRepository->persist($user);

Nebylo by to lepší? Respektive řeší to to, co potřebuješ?

Edit: Doplnil jsem zapomenutý příznak m:hasMany.

Editoval Tharos (14. 8. 2013 22:24)

před 5 lety

Tharos
Člen | 1036
+
0
-

@paranoiq:

@Tharos řešil jsem teď modulární aplikaci nad jiným ORM a hlavní problém byl jak provazovat entity, které se nahrávají z pluginů do aplikace (a nemohou modifikovat původní kód aplikace)

jdou v Lean Mapperu vazby mezi entitami definovat dynamicky, tzn. jinak než anotacemi na entitách?

No… když se na tím tak zamyslím, vlastně mě nenapadá, proč by to nešlo. Jen jsem takový use-case, přiznám se, ještě nikdy neřešil. :) V zásadě mě napadají hned dvě možná řešení.

Prvním řešením je použití magických metod (tak, jak naznačil Šaman). To by měl být přístup v současné době plně podporovaný.

Druhým možným řešením je entitě nějak předat vlastní EntityReflection, což je třída, která vyčerpávajícím způsobem reprezentuje entitu včetně jejích položek.

Z principu je možné tu kolekci položek sestavit z čehokoliv – nemusí to být jen anotace, mohla by být klidně načtená z databáze.


Nastínil bys mi schéma databáze? Zkrátka to, jak ty dynamické položky ukládáš a jak ukládáš informaci o vazbách.

Já bych na základě toho nastínil nějaké řešení (prvně s pomocí těch metod, které by mělo být možné postavit nad stávajícím Lean Mapperem bez nějakých zásahů).


Edit: Tak jsem při troše experimentování zjistil, že i druhé nastíněné řešení je aktuálně proveditelné. Je svým způsobem elegantnější a principiálně nemá žádná omezení – i takhle dynamicky lze vykouzlit položky s vazbami, filtry, defaultními hodnotami… Pokud by Tě to tedy zajímalo, asi bych o tom mohl připravit nějaké povídání a třeba i ukázky.

Editoval Tharos (15. 8. 2013 0:20)

před 5 lety

tomas.lang
Člen | 54
+
0
-

Ahoj, chtěl bych se zeptat jak by bylo možné u Entity docílit následujícího chování?

V tabulce mám GPS souřadnici (sloupeček gps_position) reprezentovanou pomocí typu Point, ale v entitě bych měl rád dostupné dva vlastnosti latitude, longitude (typu float). Data se dají získat pomocí X(gps_position) a Y(gps_position) – ale nevidím žádnou pěknou cestu jak ten Point rozložit a při persistenci zase složit.

Měl by tu prosím někdo nápad?

před 5 lety

Ripper
Člen | 56
+
0
-

@Tharos:

Díky! Takhle to vypadá skvěle, ale – „Method ‚addToRoles‘ is not callable“. Nevím zda tu funkci tam mám dopsat nebo je to feature Lean Mapperu a nějak mi to nefunguje.

Editoval Ripper (15. 8. 2013 9:54)

před 5 lety

Filip Procházka
Moderator | 4692
+
0
-

<reklamní vsuvka>
Kdyby někdo chtěl používat Kdyby\Events zároveň s Lean Mapperem, mělo by to jít velice snadno zařídit
</reklamní vsuvka>

před 5 lety

Tharos
Člen | 1036
+
0
-

@tomas.lang: Tohle by mělo jít vyřešit docela snadno a stručně pomocí příznaku m:useMethods.

V jaké podobě Ti dibi vrací hodnot sloupce typu Point? Klidně Ti napíšu řešení.

V podstatě by mělo stačit v get<Name> metodě vytáhnout z LeanMapper\Row v entitě jen tu jednu potřebnou část a v set<Name> zase jen tu jednu část hodnoty zaktualizovat.

před 5 lety

Tharos
Člen | 1036
+
0
-

@Ripper: Je to featura Lean Mapperu. Zkontroluj prosím následující:

  1. Jestli používáš aktuální develop verzi Lean Mapperu. Tohle ve stable verzi 1.4 ještě nebylo.
  2. Jestli máš u té položky s vazbou příznak m:hasMany. Úplně mi při psaní toho příspěvku vypadl a dopsal jsem ho tam až po pár minutách. :)

Editoval Tharos (25. 8. 2013 20:21)

před 5 lety

Tharos
Člen | 1036
+
0
-

@Filip Procházka:

Super, tak to je výborná zpráva, že jsou tyhle knihovny kompatibilní a souběžně použitelné. :)

Kdyby\Events mohu maximálně doporučit a s úspěchem jej využívám jinde. Jediný důvod, proč jsem události v Lean Mapperu nezaložil na něm, je, že Lean Mapper nechci mít závislý na žádném full-stack PHP frameworku (a Kdyby\Events je závislé na Nette).

před 5 lety

Ripper
Člen | 56
+
0
-

@Tharos

To jsem z toho jelen. Updatnul jsem přes Composer na @dev verzi a stále to nejde. Stejná chyba.

Mám to následovně –

Entity\User:

/**
 * @property int $id
 * @property string $name
 * @property string $email
 * @property string $password
 * @property string $active
 * @property Profile $profile m:belongsToOne
 * @property Role[] $roles m:hasMany
 */
class User extends Entity
{

}

Entity\Role:

/**
 * @property int $id
 * @property Role|null $parent_id m:hasOne(parent_id)
 * @property string $key_name
 * @property string $name
 * @property string $description
 */
class Role extends Entity
{

}

A volám to tak jak jsi psal –

$user = $this->userRepository->find($this->getUser()->getId());
$user->removeFromRoles($role->id);
//...

před 5 lety

Tharos
Člen | 1036
+
0
-

@Ripper: Problém bude vážně někde jinde. Překopíroval jsem teď ty Tvé entity do jednoho svého dev skriptu a ty metody addTo<Name> a removeFrom<Name> mi fungují podle očekávání. Jakmile budu mít chvilku, připravím funkční balíček s ukázkou a pak s ním budeš moci svůj kód podrobně porovnat.

před 5 lety

Filip111
Člen | 244
+
0
-

Ahoj, mám pár otázek.

  1. jak smažu z entity s relací hasMany všechny prvky?

Zkoušel jsem

unset($e->roles);
$e->roles->remove();

ale nic z toho nezafungovalo.

/**
* @property int $id
* @property string|null $title
* @property Role[] $roles m:hasMany(user:accounts_role:role:cat_accounts_role)
*/
class User extends BaseEntity {

}
  1. Přiřazení mi funguje po jednom prvku. Jdou taky přiřadit nějak hromadně? (typicky hodnoty z multiselectu, kde jsou jen id).
foreach($val['roles'] as $role) {
    $e->addToRoles($role);
}
  1. Jak náročné by bylo kontrolovta validitu anotace na tuto chybu?

chybně

* @property int $lessonLength|null (lesson_length)

správně

* @property int|null $lessonLength (lesson_length)

Už jsem to takhle napsal několikrát a pokaždé mi trvalo docela dlouho než jsem zjistil v čem je problém. Já už si to snad zapamatuju, ale spíš aby s tím nebojoval i někdo další.

Díky.

Editoval Filip111 (15. 8. 2013 23:52)

před 5 lety

Tharos
Člen | 1036
+
0
-

@Ripper: Podívej se na tento minimální funkční příklad . Porovnej s ním kdyžtak svůj kód. Mám podezření, že reálně nemáš ve vendor adresáři aktuální develop verzi, protože jinak si tu natvrdo chybějící metodu nedokážu vysvětlit. :)

před 5 lety

echo
Člen | 134
+
0
-

@Ripper: V composer použij
"tharos/leanMapper": "dev-develop"

před 5 lety

daniel.mejta
Člen | 21
+
0
-

echo napsal(a):

@Ripper: V composer použij
"tharos/leanMapper": "dev-develop"

A pokud nebude fungovat tak "tharos/leanMapper": "dev-develop@dev"

před 5 lety

Tharos
Člen | 1036
+
0
-

@Filip111: Ahoj,

1) Aktuálně pouze tak, že nad nimi zaiteruješ:

foreach ($book->tags as $tag) {
    $book->removeFromTags($tag);
}

2) Opět se nevyhneš iterování… (Přesně takovému, jaké uvádíš.)

Při jakém use case potřebuješ odstranit všechny položky? Já při práci s formuláři běžně používám následující konstrukci:

foreach ($checkboxes as $checkbox) {
    $book->removeFromTags($checkbox->tagId);
    if ($checkbox->isChecked()) {
        $book->addToTags($checkbox->tagId);
    }
}

Možná vypadá divně vždy tag nejdřív odstranit a pak přidat, ale Lean Mapper má jistou vnitřní inteligenci, díky které vygeneruje úplně přesně ty dotazy, které jsou zapotřebí pro přechod „ze stavu A do stavu B“.

Jaké by sis představoval API pro to hromadné přidání či odebrání? Jinak už nyní by to mělo jít jednoduše přidat do nějaké abstraktní BaseEntity.

3) Rozumím. Já mám dva důvody, proč konkrétně tohle nekontrolovat:

  • Někdo jiný tíhne zase k jiným překlepům a pokud bych začal kontrolovat tohle, postupně by se našly další stejně významné překlepy ke kontrole…
  • Minimálně v PhpStormu mi při takovémto překlepu přestane IDE napovídat název položky. Myslím, že už to je okamžité vodítko, že něco není v pořádku. Jak jsou na tom jiná IDE?

před 5 lety

Filip111
Člen | 244
+
0
-

@Tharos:

  1. ok, budu zatím iterovat

ale dovedl bych si představit něco jako

$book->removeFromTags();    // bez uvedeni prvku mazat vse
$book->tags->remove();      // bez uvedeni prvku mazat vse
$book->tags->removeAll();
$book->tags->clean();
unset($book->tags);

Nevím co je nejvhodnější nebo zapadá do tvé koncepce, ale myslím, že to bude celkem užitečná věc.

  1. obvykle to dělám víc barbarsky – v prvním kroku vše smazat, ve druhém nastavím všechny klíče získané z formuláře. Když o tom uvažuji, u ORM to ale není to pravé ořechové.
  2. chápu tvoje důvody + čím více kontrol, tím větší dopad na výkonnost

Na druhou stranu php kód mi zkontroluje nějaký parser a v případě chyby mi to dá hned vědět, ale u anotací je to jinak a je tam víc prostoru pro chyby. Co nezkontroluješ ty, nezkontroluje nikdo jiný a pokud to vede k chybám, které mají na první pohled nepochopitelné příčiny je to pěkný WTF. Docela jsem si toho užil u Doctrine2, ale pokud u LeanMapperu s něčím bojuji, je to dost často chybně zapsaná anotace.
Pokud by mě parser anotací hned na začátku nakopnul a řekl, že mam něco blbě, opravím překlep a jedu dál. Takhle půl hodiny něco hledám a úplně mě to zabrzdí od práce.

Podle mě jsou anotace alfou a omegou definice entity a proto by měly být co nejvíce neprůstřelné a blbuvzdorné.
A troufnu si tvrdit (z pohledu uživatele :), kdyby to bylo moc pomalé, ať se předpřipravená entita/anotace/reflexe někde cachují, hlavně když mi to ušetří čas a nervy během výboje.

Příklad, který jsem uváděl výše.

* @property int $lessonLength|null (lesson_length)  // špatně
* @property int|null $lessonLength (lesson_length)  // správně

Schválně, jak se chyba projeví? :)

Všechno na první pohled fungovalo, mohl jsem s preperty pracovat, jen se lessonLength nenačítala z db. Při jejím ukládání, ale generovala SQL insert pro sloupec lessonLength namísto lesson_legth.
Chybu jsem tedy dlouho hledal v chybně uvedeném názvu db, nikoliv definici null.

před 5 lety

Šaman
Člen | 2240
+
0
-

Na druhou stranu ta definice je špatně i z hlediska PhpDoc. Mám obavu, že tohle snadno kontrolovat nepůjde, protože ten první zápis je vpodstatě validní, jen si LM myslí, že vše za |null (včetně) je komentář.
Jinak v tvém případě doporučuji použít konvence pro převod camelCase na underscore_divide konvence, ve většině případů pak odpadne potřeba explicitně definovat sloupce v db.

Za to rychlé odtagování se přimlouvám. Také používám odstranit vše a následně nasettit co uživatel vybral.

<?php
$book->tags->remove();      // bez uvedeni prvku mazat vse
$book->tags->removeAll();   // popisnější, asi lepší.
?>

před 5 lety

Tharos
Člen | 1036
+
0
-

@Filip111, @Šaman:

Je pravda, že chování Lean Mapperu v případě tohoto překlepu je zákeřné. Má sice svou logiku, kterou lze obhájit, ale to mu na zákeřnosti neubírá. :)

No, napadlo mě, že tohle by ještě mělo být řešitelné (rozumějte validovatelné) snadno. Prostě doplnit, aby za názvem položky musel následovat buďto konec řádku, anebo alespoň jeden bílý znak. To by obnášelo přidat jednu aserci do jednoho reguláru v parseru (tzn. žádné dopady na výkon) a mohlo by to být spolehlivě vyřešené. Takže to vyzkouším.


API v podobě:

$book->tags->remove();

není technicky možné, protože tagy jsou standardně uloženy v obyčejném array.

Jak by se vám ale líbilo následující API?

$book->removeAllTags();

Mně ze zmíněných zatím nejvíc.

před 5 lety

Filip111
Člen | 244
+
0
-

@Tharos:
Mě tahle magie moc nevoní, ale vzhledem k tomu, že už tam stejně je např.
$book->addToTags($checkbox->tagId);
tak varianta
$book->removeAllTags(); je přijatelná.

Škoda jen, že nejde $book->tags->removeAll();
To mi přijde nejpřehlednější.

@Šaman:
Taky mě napadlo tu konvenci mít někde v mapperu, pokud to jde, abych to nemusel pořád dokola přepisovat.
Poradíš mi jak by to mělo vypadat?
(jestli jsi myslel tuhle konvenci zavést v DB, tak to není reálné)

Dík.

před 5 lety

Casper
Člen | 253
+
0
-

@Filip111:

Mě tahle magie moc nevoní

Magie není magie, pokud je zdokumentovaná a to má Tharos určitě naplánované :)

@Tharos:

Pokud něco takového budeš implementovat, přimlouvám se za

$book->removeAllTags();

Editoval Casper (16. 8. 2013 21:54)

před 5 lety

Ripper
Člen | 56
+
0
-

@Tharos

Děkuji moc! Pomohlo to a již to funguje jak má, neměl jsem tam dev verzi. Ještě jednou díky.

před 5 lety

LuKo
Člen | 114
+
0
-

Ahoj, LeanMapper jsem zaznamenal v jeho začátcích, nyní jsem se k němu vrátil a rád bych ho zkusil použít. Aktuální debata ohledně persistování vazby hasMany mi docela přišla vhod. Avšak měl bych dotaz. Existuje možnost k vazbě přistoupit z druhé strany? Příklad: přidám úplně nový tag „prázdninová sleva“ a k tomuto tagu chci přiřadit seznam knih. Pokud to nyní chápu správně, musím nyní ke každé knize zvlášť přihodit tento tag a každou knihu zvlášť persistovat.

<?php
$tag = new Tag;
$tag->name = "prázdninová sleva";
$tagRepository->persist($tag);

$books = $bookRepository->findBy(array("id" => array(1, 5, ....)));
foreach($books as $book) {
    $book->addToTags($tag);
    $bookRepository->persist($book);
}
?>

Pokud mají knihy velké množství tagů, asi to nebude úplně efektivní. Raději bych tedy měl něco ve smyslu:

<?php
$tag = new Tag;
$tag->name = "prázdninový sleva";
$tag->addToBooks(1);
// ...
$tagRepository->persist($tag);
?>

Jde to nějak udělat, nebo je to nesmyslný požadavek?

před 5 lety

Šaman
Člen | 2240
+
0
-

Pokud entita Tag bude mít property Book[] $books (book_tag), tak by to mělo fungovat přesně jak píšeš.
Jenom (myslím) musíš explicitně vypsat jméno vazební tabulky, pokud přistupuješ „z druhé strany“ (jinak by to hledalo tabulku tag_book).

Editoval Šaman (18. 8. 2013 4:58)

před 5 lety

Tharos
Člen | 1036
+
0
-

@LuKo: Ahoj, jasně, že to lze. Pointu vystihl Šaman.

Jedinou nástrahou je, že pak skutečně musíš explicitně uvést název vazební tabulky, protože Lean Mapper by jinak pracoval s neexistující tabulkou tag_book. Toho lze dosáhnout buďto přímo v anotaci (viz Šamanův příspěvek), anebo například takto ve vlastními mapperu:

class Mapper extends \LeanMapper\DefaultMapper
{

    public function getRelationshipTable($sourceTable, $targetTable)
    {
        if ($sourceTable === 'tag' and $targetTable === 'book') {
            return parent::getRelationshipTable($targetTable, $sourceTable);
        }
        return parent::getRelationshipTable($sourceTable, $targetTable);
    }

}

S tím, že pokud bys měl podobných oboustranných vazeb v aplikaci více, v mapperu by to určitě šlo nějak hezky zobecnit.

Editoval Tharos (18. 8. 2013 12:03)

před 5 lety

LuKo
Člen | 114
+
0
-

@Šaman, @Tharos: Výborně, díky. Jak jsem již psal, teprve se rozkoukávám, ale na LeanMapperu mě baví, že na všechny otázky existuje jednoduché a logické řešení – geniální knihovna ;) BTW: Neuvažuješ o doplnění quick startu a nebo dokumentace o ukázku práce s vazbou hasMany při úpravě/smazání? Mohlo by to zajímat více lidí a ušetřilo by to spousty otázek.

Stránky: Prev 1 … 8 9 10 11 12 … 23 Next RSS tématu