Doctrine 2 – OneToOne relace nebo postupné načítání entit
- Magnus
- Člen | 65
Ahoj,
řeším teď jeden problém v teoretické rovině – uvedu názorný
příklad.
Mám entitu User, která obsahuje atributy, jako je jméno, heslo, e-mail atd.
Jelikož v aplikaci přibývá funkčnost, vytváří se v databází čím
dál tím více tabulek, které souvisí s uživatelem (například nastavení,
statistiky, …). Všechny tyto vazby budou v entitě řešené pomocí
OneToOne relací.
Pokud ale vyhledám entitu User, Doctrine k tomu musí načíst všechny tyto
relace, protože je User vždy inverzní stranou.
To znamená, že při jakémkoliv vyhledání entity, se bude muset načíst
hromada dalších dat (a v mnoha případech i zbytečně, protože se
nevyužijí).
Teď tedy otázka – je lepší naházet entitě tyto atributy, nebo vytvořit
v nějaké fasádě metody, které daná data vyhledají jednotlivě?
Příklad:
- atributy v entitě
<?php
use Doctrine\ORM\Mapping as ORM;
class User
{
/**
* @ORM\Column(type="string")
*/
private $name;
/**
* @ORM\Column(type="string")
*/
private $email;
/**
* @ORM\OneToOne(targetEntity="Settings")
*/
private $settings;
/**
* @ORM\OneToOne(targetEntity="Statistics")
*/
private $statistics;
// další atributy s OneToOne relací
}
$user = $em->find(User::class, $id); // musí načíst i $settings a $statistics, i když nejsou potřeba
?>
- data ve fasádě
<?php
class User
{
/**
* @ORM\Column(type="string")
*/
private $name;
/**
* @ORM\Column(type="string")
*/
private $email;
}
class Settings
{
/**
* @ORM\Column(type="integer")
*/
private $userId;
// ostatní atributy
}
class UserFacade
{
/** @var EntityManager */
private $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
public function getSettings(User $user)
{
return $this->em->getRepository(Settings::class)->findOneBy(array("userId" => $user->getId()));
}
}
$user = $em->find(User::class, $id);
if ($user !== NULL) {
$settings = $userFacade->getSettings($user);
// předání proměnných šabloně apod.
}
?>
Pozn.: Snažil jsem se o co nejstručnější kód, vynechal jsem settery a gettery u entit. Předpokládejme, že $em a $userFacade jsou definované (získané např. z DI Containeru).
Chtěl bych se vás zeptat, jestli je v některých případech lepší použít druhou možnost? Případně jaké mají obě řešení výhody/nevýhody – podle čeho se v určité situaci rozhodnout, které řešení zvolit.
Nesouvisí to sice úplně s Nette, ale je tu sekce ORM a spousta chytrých
lidí, tak snad to nebude vadit.
Děkuji!
- Jan Endel
- Člen | 1016
Ahoj, v DameJidlo jsme řešili velice podobný problém – „vyřešili“ jsme to tak, že jsme všechny vazby oneToOne přepsali na oneToMany, je to více psaní, ale nemusí se donačítat věci co člověk vždy nepotřebuje.
B bych vůbec nepoužíval, zavlečuješ si do kódu zbytečné závislosti, když chceš z usera settings.
- Lukeluha
- Člen | 130
Azathoth napsal(a):
a nepomohlo by nastavit v anotaci té one to one asociace fetch=EXTRA_LAZY?
Řešil jsem úplně stejný problém, dle dokumentace doctrine jsem použil právě fetch=EXTRA_LAZY, což mi ale nepomoho, relace se stále načítaly. Tak nevím, jestli jsem dělal něco špatně, popř. jestli toto není bug @FilipProcházka ?
Editoval Lukeluha (26. 7. 2015 10:45)
- Jan Endel
- Člen | 1016
Podle Doctrine to není bug, ale feature, řešením je jenom jak jsem psal výše, to přepsat na oneToMany – tady si s tím pokecal Honza Tichý s vývojářema Doctrine
- Magnus
- Člen | 65
@JanEndel chápu tedy správně, že máte v podstatě kolekci s jediným prvkem a píšete něco v tomto smyslu?
<?php
public function getSettings()
{
return $this->settingsCollection[0];
}
?>
Protože záznam v tabulce settings
bude k jednomu uživateli
pochopitelně jen jeden. Doctrine ale umí pro kolekce lazy loading, a tak se
další databázový dotaz provede až v případě, kdy tu metodu
getSettings()
použiji.
Editoval Magnus (26. 7. 2015 13:13)
- Filip Procházka
- Moderator | 4668
@Magnus jen bych dodal, že přepsání oneToOne na oneToMany je sice funkční, ale nenazývejme to řešením – je to hack.
Jako opravdu správné řešení bych viděl otočit tu vazbu tak, aby ten owner (tedy user) měl skutečně v sobě idčko té entity, kterou „obsahuje“, nikoliv naopak.
- Magnus
- Člen | 65
@FilipProcházka mně to přišlo jako zvláštní řešení, i když
chytré.
Chceš tím říct, že bude např. settings_id
v tabulce
user
a ne naopak user_id
v tabulce
settings
?
S Doctrine začínám, tak si nejsem jistý, zda to nejde „obejít“ ještě
jiným způsobem. Omluv tedy, prosím, hloupý dotaz.
Děkuji
- Filip Procházka
- Moderator | 4668
@Magnus hloupý dotaz to rozhodně není.
Dává mi logicky smysl, aby uživatel když „vlastní“ settings, aby věděl které settings vlastně vlastní. Takhle settings ví, že je vlastněn uživatelem a to že se k němu dostaneš i z uživatele řeší Doctrine, což mi dává menší smysl, protože mi to nedává smysl z hlediska objektového návrhu.
Dokonce bych řekl, že je daleko lepší vůbec nedělat bi-directional vazbu, ale nechat jenom tu settings OneToOne z uživatele a v settings vubec nedělat property s uživatelem.
class User
{
/**
* @ORM\Column(type="string")
*/
private $name;
/**
* @ORM\OneToOne(targetEntity="Settings")
* @ORM\JoinColumn(nullable=FALSE)
*/
private $settings;
public function __construct()
{
$this->settings = new Settings();
}
}
class Settings
{
/**
* @ORM\Column(type="boolean", options={"default":FALSE})
*/
private $vip = FALSE;
// ostatní atributy
}
To je právě to přemýšlení v objektech, o kterém každý mluví když se snaží vysvětlit, že s ORM se pracuje maličko jinak než se samotnými tabulkami.
- hrach
- Člen | 1838
Takove malicke offtopic: toto v Nextras Orm funguje automaticky spravne, tj. lazy, ale vcetne zachovani efektivniho donacteni pro celou kolekci.