Kdyby\Doctrine: M:N relace

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
HappyFace
Člen | 162
+
0
-

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

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

vice na http://doctrine-orm.readthedocs.org/…iations.html

Lukeluha
Člen | 130
+
0
-

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

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

vice na http://doctrine-orm.readthedocs.org/…iations.html

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

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

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
+
+1
-

Defaultne to nacte celou kolekci a vyhledava v ni. To se necha zmenit extra lazy kolekcema

Lukeluha
Člen | 130
+
0
-

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

@Lukeluha pokud mas desitky tisic zaznamu, tak pouzijes to extra lazy

Lukeluha
Člen | 130
+
0
-

David Matějka napsal(a):

@Lukeluha pokud mas desitky tisic zaznamu, tak pouzijes to extra lazy

Jo, jasně, nebo tak :)

HappyFace
Člen | 162
+
0
-

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

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

@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

transactions-and-concurrency.html#exception-handling

Lukeluha
Člen | 130
+
0
-

Zajímavé, tak do hloubky jsem to nestudoval :) díky za info. Každopádně to může v Kdyby\Doctrine být řešené trochu jinak, ale to je otázka spíše na @FilipProcházka

Filip Procházka
Moderator | 4668
+
0
-

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).