Doctrine 2 – OneToOne relace nebo postupné načítání entit

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

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:

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

?>
  1. 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
+
+2
-

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.

Azathoth
Člen | 495
+
0
-

a nepomohlo by nastavit v anotaci té one to one asociace fetch=EXTRA_LAZY?

Lukeluha
Člen | 130
+
0
-

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

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

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

Jan Endel
Člen | 1016
+
0
-

V podstatě ano.

Filip Procházka
Moderator | 4668
+
+1
-

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

@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
+
+2
-

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

Magnus
Člen | 65
+
0
-

@FilipProcházka máš pravdu, nějak jsem nad tímto způsobem ani nepřemýšlel – asi kvůli dlouhodobému zvyku cpát cizí klíč do druhé tabulky.
Děkuji za radu.

Zax
Člen | 370
+
0
-

@FilipProcházka To už může rovnou použít Embeddable ;-)

hrach
Člen | 1836
+
-6
-

Takove malicke offtopic: toto v Nextras Orm funguje automaticky spravne, tj. lazy, ale vcetne zachovani efektivniho donacteni pro celou kolekci.