Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: [SF][SF2] Relacja One-to-one - problem
Forum PHP.pl > Forum > PHP > Frameworki
r4nd4ll
Cześć.

Od niedawna bawię się Symfony2, do tej pory jakoś nie po drodze mi było z SF i pracowałem z innymi frameworkami (Zend, Yii).

Mam problem z odwzorowaniem relacji 1-1 w encjach Doctrine.

Mam 2 encje User i Profile.
Każdy użytkownik może, ale nie musi mieć profilu.
W tabeli Profile jako klucz główny i jednocześnie klucz obcy User zastosowałem pole 'uidx' (nie wydało mi się sensowne tworzenie osobnego klucza głównego AI w tabeli Profile, skoro i tak 1 User może mieć 1 Profile, gdzie id_usera=id_profilu).

Teraz w czym problem.
Główną tablicą w mojej bazie jest User. Chciałem to zrobić tak, że najpierw tworzę użytkownika, później mogę utworzyć mu profil na zasadzie:
- pobierz istniejącego użytkownika
- stwórz nowy obiekt Profile i wypełnij go danymi (np. z formularza ProfileType)
- przypisz profil do użytkownika, np. $user->setProfile($profile)
- zapisz całość do bazy.

Okazuje się, że chyba jednak nie mogę. Próby utworzenia relacji $profile w encji User, a następnie wykonanie 'doctrine:schema:update --force' kończą się tym, że na klucz główny w tabeli User zakładany jest CONSTRAINT do Profile - możecie sobie wyobrazić jakie jaja, nie mogę utworzyć wtedy ani jednego ani drugiego (bo w Profile też jest CONSTRAINT do User)...
Próbowałem kombinować z inversedBy oraz mappedBy, ale jak się pewnie domyślacie, bezskutecznie.

Tak wygląda odwołanie do User w Profile:

  1. /**
  2.  * Profile
  3.  *
  4.  * @ORM\Table(name="Profile")
  5.  * @ORM\Entity
  6.  */
  7. class Profile
  8. {
  9. /**
  10.   * @ORM\Column(name="uidx", type="integer", nullable=false)
  11.   * @ORM\Id
  12.   * @ORM\GeneratedValue(strategy="IDENTITY")
  13.   */
  14. private $uidx;
  15.  
  16. .... inne pola ...
  17.  
  18. /**
  19.   * @ORM\OneToOne(targetEntity="\SP\UserBundle\Entity\User", cascade={"persist"})
  20.   * @ORM\JoinColumn(name="uidx", referencedColumnName="uidx")
  21.   */
  22. private $user;
  23.  
  24. ... settery i gettery ...
  25.  
  26. /**
  27.   * Set user
  28.   *
  29.   * @param \SP\UserBundle\Entity\User $user
  30.   * @return Profile
  31.   */
  32. public function setUser(\SP\UserBundle\Entity\User $user)
  33. {
  34. $this->user = $user;
  35.  
  36. return $this;
  37. }
  38.  
  39. /**
  40.   * Get user
  41.   *
  42.   * @return \SP\UserBundle\Entity\User
  43.   */
  44. public function getUser()
  45. {
  46. return $this->user;
  47. }
  48. }


Tak wygląda User:
  1. /**
  2.  * User
  3.  *
  4.  * @ORM\Table(name="User")
  5.  * @ORM\Entity
  6.  * @UniqueEntity(fields="email", groups={"register"})
  7.  */
  8. class User implements AdvancedUserInterface, \Serializable
  9. {
  10. /**
  11.   * @var integer
  12.   *
  13.   * @ORM\Column(name="uidx", type="integer", nullable=false)
  14.   * @ORM\Id
  15.   * @ORM\GeneratedValue(strategy="IDENTITY")
  16.   */
  17. private $uidx;
  18.  
  19. ... inne pola ...
  20. ... gettery i settery ...
  21. }


O co mi chodzi?

Chciałbym móc odwołać się np. w kontrolerze do profilu z poziomu użytkownika... nie mając jednocześnie założonego CONSTRAINT na uidx w User, czyli np:

  1. $user = ...pobieram użytkownika;
  2. $profile = $user->getProfile();
  3.  
  4. echo $profile->getName();


Jest to w ogóle możliwe w SF?

A może źle kombinuje i powinienem to inaczej zaprojektować/odwzorować?
Czy dać sobie spokój i w UserRepository utworzyć zwykłego SQLa z JOIN, który pobierze mi profil np. getUserProfile() ?
destroyerr
Tylko czemu się dziwisz, że tworzy Tobie tabele z kluczami obcymi skoro tego chcesz? Wystarczy, żeby kolumna odpowiadająca za profil w tabeli użytkownika mogła przyjmować wartość null. W tym celu musisz w encji User dla właściwości profil dodać annotacje JoinColumn z kluczem nullable o wartości true. Szczególy w dokumentacji.
r4nd4ll
Właśnie o to chodzi, że w tabeli profil nie ma kolumny odpowiadajacej za profil.
Jest jeden klucz główny uidx w User i relacja 1-1 do Profile, gdzie ten klucz uidx jest jednoczesnie kluczem glownym i obcym.

User:
uidx
...

Profile:
uidx
...

I teraz chodzi mi o to, czy jest szansa, żebym mógł odwoływać się z poziomu User do Profile, np. tak $user->getProfile() ?

Mając analogiczną sytuację, w Yii wyglądało to tak:

  1. User:
  2. public function relations() {
  3. return array(
  4. ...
  5. 'settings' => array(self::HAS_ONE, 'Settings', 'uidx'),
  6. ...
  7. );
  8. }
  9.  
  10. Settings:
  11. public function relations() {
  12. return array(
  13. 'user' => array(self::BELONGS_TO, 'User', 'uidx'),
  14. );
  15. }


... tym sposobem mogłem zrobić zarówno:

  1. Z poziomu User: $user->settings - miałem ustawienia
  2. Z poziomu Settings: $settings->user - i miałem usera


Czy w Doctrine jestem w stanie uzyskać podobny efekt, czy powinienem dać sobie spokój i kombinować inaczej?
destroyerr
Cytat
Właśnie o to chodzi, że w tabeli profil nie ma kolumny odpowiadajacej za profil.

Pisałem o tabeli użytkowników.

Cytat
Czy w Doctrine jestem w stanie uzyskać podobny efekt, czy powinienem dać sobie spokój i kombinować inaczej?

Tak, jesteś w stanie.

Na przyszłość jak przedstawiasz relacje to najlepiej jak przedstawisz jej obie strony. Oprócz Profile przydałby się jeszcze User.
r4nd4ll
Wyedytowałem pierwszy post, jest i User.

A mógłbyś mnie trochę nakierować jak się do tego zabrać?
Zakładając, że uidx w Profile nie może być nullable - toż to klucz główny.
destroyerr
Cytat
Zakładając, że uidx w Profile nie może być nullable - toż to klucz główny.

Nie mam już pomysłu jak mam napisać żebyś zrozumiał, że chodzi nie o profil a o użytkownika

Po zamieszczeniu encji User od razu jest wiadome, że zrobiłeś relację jednokierunkową, a chcesz z niej skorzystać jak z dwukierunkowej. Jak już to zmienisz to w encji User dla pola profile ustaw nullable.

Inna sprawa, że chyba źle do tego podchodzisz. W encji Profile chcesz mieć klucz główny uidx z automatycznie generowaną wartością a dodatkowo chcesz, żeby jeszcze też było to kluczem obcym do innej tabeli. Najprostsze rozwiązanie to: klucz główny dla Profile to jedno pole (najlepiej standardowo id) a drugie pole user z relacją do opdowiedniej encji (Doctrine sam sobie utworzy kolumnę dla tej relacji). Przy takim rozwiązaniu zachowujesz konwencje a to jest po prostu wygodne. Możesz usunąć to uidx z Profile i dodać adnotacje @Id do pola user (szczegóły).
r4nd4ll
Cytat(destroyerr @ 16.05.2013, 12:00:45 ) *
Po zamieszczeniu encji User od razu jest wiadome, że zrobiłeś relację jednokierunkową, a chcesz z niej skorzystać jak z dwukierunkowej. Jak już to zmienisz to w encji User dla pola profile ustaw nullable.


Wkleiłem to co przywróciłem, gdy próby z dwukierunkową relacją mnie zawiodły (jedyne co "jakoś" działało)..

Cytat(destroyerr @ 16.05.2013, 12:00:45 ) *
W encji Profile chcesz mieć klucz główny uidx z automatycznie generowaną wartością

To Generated jakoś się tam zaplątało, oczywiście nie miał być automatycznie generowany, za to miał być odpowiednikiem uidx z User.
Mniejsza z tym, dodałem osobny klucz glowny $profidx do Profile.

Posiłkując się tym linkiem: http://docs.doctrine-project.org/projects/...e-bidirectional mam teraz tak:

  1. /**
  2.  * User
  3.  *
  4.  * @ORM\Table(name="User")
  5.  * @ORM\Entity
  6.  * @UniqueEntity(fields="email", groups={"register"})
  7.  */
  8. class User implements AdvancedUserInterface, \Serializable
  9. {
  10. /**
  11.   * @var integer
  12.   *
  13.   * @ORM\Column(name="uidx", type="integer", nullable=false)
  14.   * @ORM\Id
  15.   * @ORM\GeneratedValue(strategy="IDENTITY")
  16.   */
  17. private $uidx;
  18.  
  19. ...
  20.  
  21. /**
  22.   * @var \SP\CommunityBundle\Entity\Profile
  23.   *
  24.   * @ORM\OneToOne(targetEntity="\SP\CommunityBundle\Entity\Profile", mappedBy="user")
  25.   */
  26. private $profile;
  27.  
  28.  
  29.  
  30.  
  31. /**
  32.   * Get uidx
  33.   *
  34.   * @return integer
  35.   */
  36. public function getUidx()
  37. {
  38. return $this->uidx;
  39. }
  40.  
  41. ....
  42.  
  43. /**
  44.   * Set profile
  45.   *
  46.   * @param \SP\CommunityBundle\Entity\Profile $profile
  47.   * @return Profile
  48.   */
  49. public function setProfile(\SP\CommunityBundle\Entity\Profile $profile = null)
  50. {
  51. $this->profile = $profile;
  52.  
  53. return $this;
  54. }
  55.  
  56. /**
  57.   * Get profile
  58.   *
  59.   * @return \SP\CommunityBundle\Entity\Profile
  60.   */
  61. public function getProfile()
  62. {
  63. return $this->profile;
  64. }
  65. }
  66.  
  67. /**
  68.  * Profile
  69.  *
  70.  * @ORM\Table(name="Profile")
  71.  * @ORM\Entity
  72.  */
  73. class Profile
  74. {
  75. /**
  76.   * @var integer
  77.   *
  78.   * @ORM\Column(name="profidx", type="integer", nullable=false)
  79.   * @ORM\Id
  80.   * @ORM\GeneratedValue(strategy="IDENTITY")
  81.   */
  82. private $profidx;
  83.  
  84. /**
  85.   * @var \SP\UserBundle\Entity\User
  86.   *
  87.   * @ORM\OneToOne(targetEntity="\SP\UserBundle\Entity\User")
  88.   * @ORM\JoinColumn(name="uidx", referencedColumnName="uidx", inversedBy="profile")
  89.   */
  90. private $user;
  91.  
  92. /**
  93.   * Get profidx
  94.   *
  95.   * @return integer
  96.   */
  97. public function getProfidx()
  98. {
  99. return $this->profidx;
  100. }
  101.  
  102. ...
  103.  
  104. /**
  105.   * Set user
  106.   *
  107.   * @param \SP\UserBundle\Entity\User $user
  108.   * @return Profile
  109.   */
  110. public function setUser(\SP\UserBundle\Entity\User $user = null)
  111. {
  112. $this->user = $user;
  113.  
  114. return $this;
  115. }
  116.  
  117. /**
  118.   * Get user
  119.   *
  120.   * @return \SP\UserBundle\Entity\User
  121.   */
  122. public function getUser()
  123. {
  124. return $this->user;
  125. }
  126. }
  127.  



I teraz:

  1.  
  2. $user=new User;
  3. ... ustawiam pola
  4.  
  5. $profile=new Profile;
  6. ... ustawiam pola
  7.  
  8. // moge zrobić tak:
  9. $profile->setUser($user);
  10. // i to jest okey
  11.  
  12. // a kiedy próbuję tak:
  13. $user->setProfile($profile);
  14. // w przypadku kiedy dodawałem w User jak radziłeś @ORM\JoinColumn(name="uidx", referencedColumnName="uidx", nullable=true), zupdateowałem bazę, to wtedy działa, z tym, że ustawia uidx w Profile na NULL, więc nic mi po tym
  15. // no a kiedy nie updateowałem bazy z Encji dostaję SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'uidx' cannot be null
  16.  


Stwierdziłeś, że to co chcę osiągnąć jest możliwe, więc męczę się z tą przeklętą relacją już pół dnia i jestem niemalże tam gdzie byłem..
Będę wdzięczny jeśli chciałoby Ci się na to zerknąć jeszcze raz.



/// EDIT

Zmodyfikowałem metodę User setProfile:

  1. /**
  2.   * Set profile
  3.   *
  4.   * @param \SP\CommunityBundle\Entity\Profile $profile
  5.   * @return Profile
  6.   */
  7. public function setProfile(\SP\CommunityBundle\Entity\Profile $profile = null)
  8. {
  9. $profile->setUser($this); /// dodalem przypisanie uzytkownika do profilu
  10. $this->profile = $profile;
  11.  
  12. return $this;
  13. }


I wygląda na to, że działa, w obie strony smile.gif

Pytanie tylko, czy takie podejście jest prawidłowe i nie mieszam tym za bardzo??
To jest wersja lo-fi głównej zawartości. Aby zobaczyć pełną wersję z większą zawartością, obrazkami i formatowaniem proszę kliknij tutaj.
Invision Power Board © 2001-2025 Invision Power Services, Inc.