Kdyby\Doctrine: M:N relace
- HappyFace
- Člen | 162
Ahojte,
s doctrine som zacal cca 3 dni spat a narazil som na jedne „problem“ alebo skor len nedostatok poznatkov, tak neviem posudit ci je to korektne chovanie alebo nie
Mam 2 Tabulky prepojene N:M:
user---<user_has_file>---file
k nim entity:
User:
class User extends Doctrine\Entity
{
/**
* @var Collections\Collection
*
* @ORM\ManyToMany(targetEntity="File", inversedBy="users")
* @ORM\JoinTable(name="user_has_file",
* joinColumns={
* @ORM\JoinColumn(name="user_id", referencedColumnName="id")
* },
* inverseJoinColumns={
* @ORM\JoinColumn(name="file_id", referencedColumnName="id")
* }
* )
*/
private $files;
/**
* Add File
*
* @param File $file
* @return User
*/
public function addFile(File $file)
{
if(!$this->getFiles()->contains($file))
{
$this->files->add($file);
}
return $this;
}
}
File:
class File extends Doctrine\Entity
{
/**
* @var Collections\Collection
*
* @ORM\ManyToMany(targetEntity="User", mappedBy="files")
*/
private $users;
/**
* Add user
*
* @param User $user
* @return File
*/
public function addUser(User $user)
{
if(!$this->getUsers()->contains($user))
{
$user->addFile($this); #@todo Netusim ci je spravne tato implementacia
$this->users->add($user);
}
return $this;
}
}
ked pridam user-ovy file tak je vsetko vporiadku,objekty sa aktualizuju , dojde po flush-y k potrebnim insertom):
$user->addFile($file);
ale ak chcem k file-u pridat uzivatela, tak objekty sa aktualizuju ale na flush nic nestane :(
$file->addUser($user);
takze som doplnil @todo, co pomohlo, lenze neviem ci je to tak spravne.
Za reakcie vopred dakujem :)
PS: rozumiem ze mozem pridat file priamo cez uzivatela aj tak mam obe entity nacitane ale len ma to zo zvedavosti zaujima a nevedel som to najst na netu
Editoval HappyFace (19. 6. 2015 11:28)
- David Matějka
- Moderator | 6445
V pripade bidirectional association (tedy ze je na obou stranach, coz v tomto pripade je) kontroluje doctrine zmenu pouze na owning side (vlastnik asociace, tedy User), takze to chovani je ocekavany.
A to tvoje reseni je spravne :)
- Lukeluha
- Člen | 130
Mrkni se co znamená vlastnící a podřazená strana vazby do dokumentace.
V tvojem případě máš jako vlastnící stranu User, což je
v pořádku, ale protože je vlastnící, tak v metodě addFile
musíš mít ještě $file->addUser($this)
.
Naopak v metodě addUser
stačí mít
$this->users->add()
.
Jinak ještě poznámka: u každého vložení kontroluješ, jestli již
tato vazba ve spojovací tabulce není. Nejsem si úplně jist, jak funguje
$collection->contains
, ale určitě rychlejší bude to
neřešit na úrvoni PHP, tuto podmínku smazat, a kontrolovat to pomocí
odchytávání vyjímek.
- HappyFace
- Člen | 162
David Matějka napsal(a):
V pripade bidirectional association (tedy ze je na obou stranach, coz v tomto pripade je) kontroluje doctrine zmenu pouze na owning side (vlastnik asociace, tedy User), takze to chovani je ocekavany.
A to tvoje reseni je spravne :)
Velka vdaka za rychlu reakciu, uz mi je to jasne :)
ako som pisal nakonci, z 99% aj tak budem pouzivat owner stranu ale kedze doctrinu zatial osahavam tak ma trocha toto zaskocilo a nevedel som prist nato ci mam nieco zle napisane alebo je to vporiadku a ktovie ci nahodou to niekedy aj v buducnu nebudem potrebovat z nejakeho dovodu
- HappyFace
- Člen | 162
Lukeluha napsal(a):
Jinak ještě poznámka: u každého vložení kontroluješ, jestli již tato vazba ve spojovací tabulce není. Nejsem si úplně jist, jak funguje
$collection->contains
, ale určitě rychlejší bude to neřešit na úrvoni PHP, tuto podmínku smazat, a kontrolovat to pomocí odchytávání vyjímek.
Ahoj,
hmm ale ak pocas vykonu dojde k vynimke tak cela tranzakcia sa rollback-ne a
potom co dalej?
priklad:
$user = $this->em->find(Model\User::getClassName(), 6);
$user->addFile($this->em->find(Model\File::getClassName(), 66)); #nove priradenie
$user->addFile($this->em->find(Model\File::getClassName(), 43)); #uz existuje zaznam
$this->em->persist($user);
$this->em->flush();
Ked mas nejake brutalne dlhy script kde vsemozne pracujes s entitami a na konci das flush(tu sa vykona n-roznych operacii) a ono ti to padne koli vynimke, tak cela tvoja praca nevysla nazmar len koli tomu ze clovek chcel usetrit 1× select (FROM file t0 INNER JOIN user_has_file ON t0.id = user_has_file.file_id WHERE user_has_file.user_id = 6)
Mozno sa mylim ale rad si necham poradit :)
Editoval HappyFace (19. 6. 2015 13:34)
- Lukeluha
- Člen | 130
Všechno záleží na kontextu v aplikaci. Pokud umožníš uživateli, aby si přidal nějaký soubor, který již je přidaný a zároveň máš „brutal dlhý script“, tak je otázka, jestli je správně, aby byl někde tak brutal dlouhý skript :)
Zároveň nemusíš dělat pouze jeden flush, ale víc (uznávám, taky zpomalení).
Spíš to byla jen taková poznámka, jak říkám, nevím, jak pracuje ta
metoda contains
, pokud vyhledává přímo v db (jakože když nad
tím tak přemýšlím, tak asi bude :D), tak si myslím, že není
problém.
- David Matějka
- Moderator | 6445
Defaultne to nacte celou kolekci a vyhledava v ni. To se necha zmenit extra lazy kolekcema
- Lukeluha
- Člen | 130
David Matějka napsal(a):
Defaultne to nacte celou kolekci a vyhledava v ni. To se necha zmenit extra lazy kolekcema
Pokud je to tak, jak říkáš, tak je to dost neefektivní ne? Záleží
teda jak se v té kolekci vyhledává, ale pokud budu mít několik desítek
tisíc záznamů, tak asi bude mnohem rychlejší odchytnout si vyjímku
duplicate entry key než pomocí contains()
.
- David Matějka
- Moderator | 6445
@Lukeluha pokud mas desitky tisic zaznamu, tak pouzijes to extra lazy
- HappyFace
- Člen | 162
Lukeluha napsal(a):
Všechno záleží na kontextu v aplikaci. Pokud umožníš uživateli, aby si přidal nějaký soubor, který již je přidaný a zároveň máš „brutal dlhý script“, tak je otázka, jestli je správně, aby byl někde tak brutal dlouhý skript :)
jasne :D tak toto je len prtava vec takze sa neda nejako zmysluplne simulovat vsetky moznosti .. urcite nieco pravdy natom aj ty len ked som si to skusal bez toho tak som vykonal pre ten duplicitny insert v mojej podobe 6 dotazov na DB vratane stransaction (start/end) a skoncilo to vpohode .. cez vynimku to vykonalo 5 ale stym ze to padlo sice tento pristup je rychlejsie a nenacitavas si celu kolekciu
ale stym extra lazy je celkom zaujimava vec :)
No neviem ako som cital niekde na vebe je to o citu a zvazit co sa pouzije a kde aktualne sa len ucim co Doctrine dokaze a ake ma moznosti .. cim viac sposobov sa dozviem tym lepsie potom budem to moct navrhnut
- Lukeluha
- Člen | 130
HappyFace napsal(a):
Lukeluha napsal(a):
Všechno záleží na kontextu v aplikaci. Pokud umožníš uživateli, aby si přidal nějaký soubor, který již je přidaný a zároveň máš „brutal dlhý script“, tak je otázka, jestli je správně, aby byl někde tak brutal dlouhý skript :)
jasne :D tak toto je len prtava vec takze sa neda nejako zmysluplne simulovat vsetky moznosti .. urcite nieco pravdy natom aj ty len ked som si to skusal bez toho tak som vykonal pre ten duplicitny insert v mojej podobe 6 dotazov na DB vratane stransaction (start/end) a skoncilo to vpohode .. cez vynimku to vykonalo 5 ale stym ze to padlo sice tento pristup je rychlejsie a nenacitavas si celu kolekciu
ale stym extra lazy je celkom zaujimava vec :)
No neviem ako som cital niekde na vebe je to o citu a zvazit co sa pouzije a kde aktualne sa len ucim co Doctrine dokaze a ake ma moznosti .. cim viac sposobov sa dozviem tym lepsie potom budem to moct navrhnut
To máš naprostou pravdu. Lidi většinou používají jednu věc, na kterou jsou zvyknutí a neexistuje pro ně jiná alternativa, která by ovšem k danému problému vyhovovala mnohem víc. Ale i přešto budou tu svoji knihovničku obhajovat až do nebes :)
- HappyFace
- Člen | 162
@Lukeluha:
K tej vynimke som som sa docital zaujimavu vec a to ze ak nastane situacia pri ktorej doctrine vyvola „PDOException“ dojde k uzavretiu EntityManager. Co je dost nestastne ak by si chcel pokracovat dalej .. preto sa odporuca si vsetko zvalidovat na strane modelu a na DB posielat co mozno najspolahlivejsie data..
samozrejme vzdy si mozes vytvorit novy EM alebo robit clone stavajuceho a ten pouzivat ale to tiez nieje najlepsie
- Filip Procházka
- Moderator | 4668
Kdyby\Doctrine se snaží co nejméně měnit chování Doctrine a věci pouze přidávat. Tohle se tedy chová úplně stejně. Když ti vyletí DBAL výjimka behěm flushe, tak se EM zamkne. Byly tady tací, co to ohackovávali tak, že to přes reflexi zase odemkli, ale to zamykání je tam z drobrého důvodu – prostě se ti rozešel stav databáze se stavem v paměti a doctrine to neumí správně mergnout.
Jedno řešení je předcházet tomu, třeba pomocí této třídy s kouzelným názvem NonLockingUniqueInserter.
Další řešení (které se hodně používá v Symfony), je používat všude Registry, které instanci EntityManageru zabalí a stane se tedy jeho „obálkou“ a když se zamkne, jednoduše na Registry zavoláš metodu, která ho smaže z DI Containeru a vytvoří novou instanci. Znamená to ale taky, že nesmíš nikde používat EntityManager přímo, stejně jako nepoužíváš nikde v aplikaci Identitu uživatele přímo (ale přes User třídu, která ji obaluje).