Witaj Gościu! ( Zaloguj | Rejestruj )

Forum PHP.pl

 
Reply to this topicStart new topic
> [Doctrine2] Klucz obcy w encji., Single id is not allowed on composite primary key in entity
ghost1511
post 5.11.2014, 14:14:54
Post #1





Grupa: Zarejestrowani
Postów: 186
Pomógł: 18
Dołączył: 2.09.2010

Ostrzeżenie: (0%)
-----


Mam dwie encje:
  1. <?php
  2.  
  3. namespace Album\Entity;
  4.  
  5. use Doctrine\ORM\Mapping as ORM;
  6. use Doctrine\Common\Collections\ArrayCollection;
  7. use Album\Entity\Song;
  8. use Doctrine\Common\Collections\Collection;
  9.  
  10. /**
  11.  * Description of Album
  12.  *
  13.  * @ORM\Entity
  14.  * @ORM\Table(name="Album")
  15.  */
  16. class Album {
  17.  
  18. /**
  19.   * @ORM\Id
  20.   * @ORM\Column(type="integer");
  21.   * @ORM\GeneratedValue(strategy="AUTO")
  22.   * @var integer
  23.   */
  24. protected $albumId;
  25.  
  26. /**
  27.   * @ORM\Column(type="string")
  28.   */
  29. protected $artist;
  30.  
  31. /**
  32.   * @ORM\Column(type="string")
  33.   */
  34. protected $title;
  35.  
  36. /**
  37.   *
  38.   * @var ArrayCollection
  39.   *
  40.   * @ORM\OneToMany(targetEntity="Song", mappedBy="album", cascade={"persist"})
  41.   */
  42. protected $songs;
  43.  
  44. function __construct() {
  45. $this->songs = new ArrayCollection();
  46. }
  47.  
  48. function getAlbumId() {
  49. return $this->albumId;
  50. }
  51.  
  52. function getArtist() {
  53. return $this->artist;
  54. }
  55.  
  56. function getTitle() {
  57. return $this->title;
  58. }
  59.  
  60. function setAlbumId($albumId) {
  61. $this->albumId = $albumId;
  62. }
  63.  
  64. function setArtist($artist) {
  65. $this->artist = $artist;
  66. }
  67.  
  68. function setTitle($title) {
  69. $this->title = $title;
  70. }
  71.  
  72. public function addSongs(Collection $songs) {
  73. foreach ($songs as $song) {
  74. $song->setAlbum($this);
  75. $this->songs->add($song);
  76. }
  77. }
  78.  
  79. public function removeSongs(Collection $songs) {
  80. foreach ($songs as $song) {
  81. $song->setAlbum(NULL);
  82. $this->songs->remove($song);
  83. }
  84. }
  85.  
  86. function getSongs() {
  87. return $this->songs;
  88. }
  89.  
  90. }

oraz:
  1. <?php
  2.  
  3. namespace Album\Entity;
  4.  
  5. use Doctrine\ORM\Mapping as ORM;
  6. use Album\Entity\Album;
  7.  
  8. /**
  9.  * Description of Song
  10.  *
  11.  *
  12.  * @ORM\Entity
  13.  * @ORM\Table(name="Song")
  14.  */
  15. class Song {
  16.  
  17. /**
  18.   * @var string
  19.   * @ORM\Id
  20.   * @ORM\GeneratedValue(strategy="AUTO")
  21.   * @ORM\Column(type="string");
  22.   */
  23. protected $title;
  24.  
  25. /**
  26.   *
  27.   * @var Album
  28.   *
  29.   * @ORM\Id
  30.   * @ORM\GeneratedValue(strategy="NONE")
  31.   * @ORM\ManyToOne(targetEntity="Album", inversedBy="songs")
  32.   * @ORM\JoinColumn(name="albumId", referencedColumnName="albumId")
  33.   */
  34. protected $album;
  35.  
  36. function getTitle() {
  37. return $this->title;
  38. }
  39.  
  40. function getAlbum() {
  41. return $this->album;
  42. }
  43.  
  44. function setTitle($title) {
  45. $this->title = $title;
  46. }
  47.  
  48. function setAlbum(Album $album) {
  49. $this->album = $album;
  50. }
  51.  
  52.  
  53.  
  54. }


Oraz kontroler:
  1. <?php
  2.  
  3. namespace Album\Controller;
  4.  
  5. use Zend\Mvc\Controller\AbstractActionController;
  6. use Zend\View\Model\ViewModel;
  7. use Doctrine\ORM\EntityManager;
  8. use DoctrineModule\Stdlib\Hydrator\DoctrineObject as DoctrineHydrator;
  9. use Album\Entity\Album;
  10.  
  11. class AlbumController extends AbstractActionController {
  12.  
  13. protected $doctrineHydrator;
  14.  
  15. public function indexAction() {
  16.  
  17. $killEmAllData = array(
  18. 'artist' => 'Metallica',
  19. 'title' => 'Kill em All',
  20. 'songs' => array(
  21. array('title' => 'Hit the Lights'),
  22. array('title' => 'The Four Horsemen'),
  23. array('title' => 'Motorbreath'),
  24. array('title' => 'Jump in the Fire'),
  25. array('title' => '(Anesthesia) Pulling Teeth'),
  26. array('title' => 'Whiplash'),
  27. array('title' => 'Phantom Lord'),
  28. array('title' => 'No Remorse'),
  29. array('title' => 'Seek & Destroy'),
  30. array('title' => 'Metal Militia'),
  31. array('title' => 'Am I Evil?'),
  32. array('title' => 'Blitzkrieg'),
  33. )
  34. );
  35.  
  36. var_dump($killEmAllData);
  37.  
  38. $killEmAll = new Album();
  39. $hydrator = $this->getDoctrineHydrator();
  40. $killEmAllHydrate = $hydrator->hydrate($killEmAllData, $killEmAll);
  41.  
  42. $songsCollection = new \Doctrine\Common\Collections\ArrayCollection();
  43. foreach ($killEmAllData['songs'] as $songArray) {
  44. $song = $hydrator->hydrate($songArray, new \Album\Entity\Song());
  45. $songsCollection->add($song);
  46. }
  47. $killEmAll->addSongs($songsCollection);
  48.  
  49. var_dump($killEmAllHydrate);
  50.  
  51. return new ViewModel();
  52. }
  53.  
  54. /**
  55.   * @var DoctrineORMEntityManager
  56.   */
  57. protected $entityManager;
  58.  
  59. public function getEntityManager() {
  60. if (null === $this->entityManager) {
  61. $this->entityManager = $this->getServiceLocator()->get('doctrine.entitymanager.orm_default');
  62. }
  63. return $this->entityManager;
  64. }
  65.  
  66. /**
  67.   * @return DoctrineHydrator
  68.   */
  69. private function getDoctrineHydrator() {
  70. if (!$this->doctrineHydrator instanceof DoctrineHydrator) {
  71. $this->doctrineHydrator = new DoctrineHydrator($this->getEntityManager());
  72. }
  73. return $this->doctrineHydrator;
  74. }
  75.  
  76. }


I otrzymuję błąd:
Kod
Fatal error: Uncaught exception 'Doctrine\ORM\Mapping\MappingException' with message 'Single id is not allowed on composite primary key in entity Album\Entity\Song' in D:\xampp1.8.2\htdocs\testy\DoctrineCompositeForeignKey\vendor\doctrine\orm\lib\Doctrine\ORM\Mapping\MappingException.php on line 414

Czy to ja robię coś źle? Czy to jakieś dziwne ograniczenie doctrine?

Ten post edytował ghost1511 5.11.2014, 14:15:44
Go to the top of the page
+Quote Post
mortus
post 5.11.2014, 19:23:17
Post #2





Grupa: Zarejestrowani
Postów: 2 178
Pomógł: 596
Dołączył: 25.09.2009
Skąd: Piwniczna-Zdrój

Ostrzeżenie: (0%)
-----


Encja Song ma złożony klucz główny (przyjrzyj się @ORM\Id) i jest identyfikowana przez title i album jednocześnie.
  1. /**
  2.   * @var string
  3.   * @ORM\Id
  4.   * @ORM\GeneratedValue(strategy="AUTO")
  5.   * @ORM\Column(type="string");
  6.   */
  7. protected $title;
  8.  
  9. /**
  10.   *
  11.   * @var Album
  12.   *
  13.   * @ORM\Id
  14.   * @ORM\GeneratedValue(strategy="NONE")
  15.   * @ORM\ManyToOne(targetEntity="Album", inversedBy="songs")
  16.   * @ORM\JoinColumn(name="albumId", referencedColumnName="albumId")
  17.   */
  18. protected $album;

Nie jestem pewien, czy to celowy zabieg, czy może pomyłka.
Go to the top of the page
+Quote Post
ghost1511
post 5.11.2014, 20:05:50
Post #3





Grupa: Zarejestrowani
Postów: 186
Pomógł: 18
Dołączył: 2.09.2010

Ostrzeżenie: (0%)
-----


Zabieg celowy. Wg dokumentacji jest to możliwe:
http://doctrine-orm.readthedocs.org/en/lat...imary-keys.html

Jednak wygląda na to, że robię coś źle. Nie potrafię jednak stwierdzić co.


Ten post edytował ghost1511 5.11.2014, 20:12:36
Go to the top of the page
+Quote Post
mortus
post 5.11.2014, 20:41:22
Post #4





Grupa: Zarejestrowani
Postów: 2 178
Pomógł: 596
Dołączył: 25.09.2009
Skąd: Piwniczna-Zdrój

Ostrzeżenie: (0%)
-----


Skoro jest to zabieg celowy, to konstruktor encji Song powinien wyglądać tak:
  1. public function __construction(string $title, Album $album) {
  2. $this->title = $title;
  3. $this->album = $album;
  4. }

Natomiast podczas tworzenia obiektu Song powinieneś podać zarówno jego title, jak i Album, do którego Song jest przypisany:
  1. $album = new Album();
  2. $song = new Song('Jakiś tytuł', $album);

Nie jestem pewien, czy hydrator Doctrine poradzi sobie z tym, gdyby jednak miał, to trzeba to uwzględnić w pętli foreach:
  1. $songsCollection = new \Doctrine\Common\Collections\ArrayCollection();
  2. foreach ($killEmAllData['songs'] as $songArray) {
  3. $songArray['album'] = $killEmAll;
  4. $song = $hydrator->hydrate($songArray, new \Album\Entity\Song());
  5. $songsCollection->add($song);
  6. }
  7. $killEmAll->addSongs($songsCollection);

Mamy tutaj takie małe "błędne" koło.

Ten post edytował mortus 5.11.2014, 20:42:05
Go to the top of the page
+Quote Post
ghost1511
post 6.11.2014, 08:18:46
Post #5





Grupa: Zarejestrowani
Postów: 186
Pomógł: 18
Dołączył: 2.09.2010

Ostrzeżenie: (0%)
-----


Wprowadziłem Twoje zmiany i jednak nie pomogło. Wygląda na to, że nie tutaj leży problem. Już na etapie uruchamiania Doctrine pojawia się ten problem:

Kod
Fatal error: Uncaught exception 'Doctrine\ORM\Mapping\MappingException' with message 'Single id is not allowed on composite primary key in entity Album\Entity\Song' in D:\xampp1.8.2\htdocs\testy\DoctrineCompositeForeignKey\vendor\doctrine\orm\lib\Doctrine\ORM\Mapping\MappingException.php on line 414
( ! ) Doctrine\ORM\Mapping\MappingException: Single id is not allowed on composite primary key in entity Album\Entity\Song in D:\xampp1.8.2\htdocs\testy\DoctrineCompositeForeignKey\vendor\doctrine\orm\lib\Doctrine\ORM\Mapping\MappingException.php on line 414
Call Stack
#    Time    Memory    Function    Location
1    0.0007    137544    {main}( )    ..\index.php:0
2    0.3081    5281208    Zend\Mvc\Application->run( )    ..\index.php:17
3    0.3206    5421056    Zend\Mvc\Application->completeRequest( )    ..\Application.php:327
4    0.3649    6199776    Zend\EventManager\EventManager->trigger( )    ..\Application.php:353
5    0.3649    6199904    Zend\EventManager\EventManager->triggerListeners( )    ..\EventManager.php:207
6    0.3656    6204744    call_user_func:{D:\xampp1.8.2\htdocs\testy\DoctrineCompositeForeignKey\vendor\zendframework\zendframework\library\Zend\EventManager\EventManager.php:468} ( )    ..\EventManager.php:468
7    0.3656    6204760    ZendDeveloperTools\Listener\ProfilerListener->onFinish( )    ..\EventManager.php:468
8    0.4202    8005080    ZendDeveloperTools\Profiler->collect( )    ..\ProfilerListener.php:107
9    0.4278    8103200    DoctrineORMModule\Collector\MappingCollector->collect( )    ..\Profiler.php:211
10    0.4279    8103256    Doctrine\Common\Persistence\Mapping\AbstractClassMetadataFactory->getAllMetadata( )    ..\MappingCollector.php:97
11    0.4776    8772984    Doctrine\Common\Persistence\Mapping\AbstractClassMetadataFactory->getMetadataFor( )    ..\AbstractClassMetadataFactory.php:114
12    0.4776    8773016    Doctrine\Common\Persistence\Mapping\AbstractClassMetadataFactory->loadMetadata( )    ..\AbstractClassMetadataFactory.php:211
13    0.4777    8775936    Doctrine\ORM\Mapping\ClassMetadataFactory->doLoadMetadata( )    ..\AbstractClassMetadataFactory.php:318
14    0.4825    8801744    Doctrine\ORM\Mapping\ClassMetadataFactory->completeIdGeneratorMapping( )    ..\ClassMetadataFactory.php:140
15    0.4825    8801808    Doctrine\ORM\Mapping\ClassMetadataInfo->getSingleIdentifierFieldName( )    ..\ClassMetadataFactory.php:451
Go to the top of the page
+Quote Post
mortus
post 6.11.2014, 12:33:24
Post #6





Grupa: Zarejestrowani
Postów: 2 178
Pomógł: 596
Dołączył: 25.09.2009
Skąd: Piwniczna-Zdrój

Ostrzeżenie: (0%)
-----


Problem zapewne pojawia się w tym miejscu:
  1. $killEmAllHydrate = $hydrator->hydrate($killEmAllData, $killEmAll);

Korzystamy tutaj z tablicy $killEmAllData, w której songs identyfikowane są jedynie przez title. Trzeba by było zatem zmienić strukturę danych:
  1. $killEmAll = new Album();
  2. $killEmAllData = array(
  3. 'artist' => 'Metallica',
  4. 'title' => 'Kill em All',
  5. 'songs' => array(
  6. array('title' => 'Hit the Lights', $killEmAll),
  7. array('title' => 'The Four Horsemen', $killEmAll),
  8. array('title' => 'Motorbreath', $killEmAll),
  9. array('title' => 'Jump in the Fire', $killEmAll),
  10. array('title' => '(Anesthesia) Pulling Teeth', $killEmAll),
  11. array('title' => 'Whiplash', $killEmAll),
  12. array('title' => 'Phantom Lord', $killEmAll),
  13. array('title' => 'No Remorse', $killEmAll),
  14. array('title' => 'Seek & Destroy', $killEmAll),
  15. array('title' => 'Metal Militia', $killEmAll),
  16. array('title' => 'Am I Evil?', $killEmAll),
  17. array('title' => 'Blitzkrieg', $killEmAll),
  18. )
  19. );
  20. $hydrator = $this->getDoctrineHydrator();
  21. $killEmAllHydrate = $hydrator->hydrate($killEmAllData, $killEmAll);
  22.  
  23. $songsCollection = new \Doctrine\Common\Collections\ArrayCollection();
  24. foreach ($killEmAllData['songs'] as $songArray) {
  25. $song = $hydrator->hydrate($songArray, new \Album\Entity\Song());
  26. $songsCollection->add($song);
  27. }
  28. $killEmAll->addSongs($songsCollection);

Nie jestem również pewien kolejności wykonywanych działań. Wg dokumentacji https://github.com/doctrine/DoctrineModule/...any-association, najpierw należałoby utworzyć tablicę obiektów Song (jeśli takowe encje w bazie danych nie istnieją), a dopiero później można użyć hydratora, przy czym kolekcja $songs zostanie wypełniona automatycznie. Wtedy kod wyglądałby tak:
  1. $killEmAll = new Album();
  2. $killEmAllSongsData = array(
  3. array('title' => 'Hit the Lights', $killEmAll),
  4. array('title' => 'The Four Horsemen', $killEmAll),
  5. array('title' => 'Motorbreath', $killEmAll),
  6. array('title' => 'Jump in the Fire', $killEmAll),
  7. array('title' => '(Anesthesia) Pulling Teeth', $killEmAll),
  8. array('title' => 'Whiplash', $killEmAll),
  9. array('title' => 'Phantom Lord', $killEmAll),
  10. array('title' => 'No Remorse', $killEmAll),
  11. array('title' => 'Seek & Destroy', $killEmAll),
  12. array('title' => 'Metal Militia', $killEmAll),
  13. array('title' => 'Am I Evil?', $killEmAll),
  14. array('title' => 'Blitzkrieg', $killEmAll),
  15. );
  16. $hydrator = $this->getDoctrineHydrator();
  17. $songs = array();
  18. foreach($killEmAllSongsData as $songArray) {
  19. $songs[] = $hydrator->hydrate($songArray, new \Album\Entity\Song())
  20. }
  21. $killEmAllData = array(
  22. 'artist' => 'Metallica',
  23. 'title' => 'Kill em All',
  24. 'songs' => $songs
  25. );
  26. $killEmAllHydrate = $hydrator->hydrate($killEmAllData, $killEmAll);

Przyjrzyj się dokładniej dokumentacji, którą możesz znaleźć pod odnośnikiem powyżej, bowiem Example 3 opisuje podobny przypadek, z tym, że Ty używasz klucza złożonego w połączeniu z kluczem obcym.
Go to the top of the page
+Quote Post
ghost1511
post 6.11.2014, 14:55:06
Post #7





Grupa: Zarejestrowani
Postów: 186
Pomógł: 18
Dołączył: 2.09.2010

Ostrzeżenie: (0%)
-----


Problemem nie są dane. Kontroler się nawet nie uruchamia. Doctrine podczas uruchamiania sprawdza powiązania encji i wtedy rzuca błędem, zresztą widać to na Call Stacku który wrzuciłem.

Przykład który podałeś jest bez @ID, które wszystko komplikuje. Spójrz jeszcze raz w dokumentację http://doctrine-orm.readthedocs.org/en/lat...oreign-entities.

Cytat
The semantics of mapping identity through foreign entities are easy:

Only allowed on Many-To-One or One-To-One associations.

To chyba odpowiedz na moje pytanie.

Jest sposób obejścia tego problemu. Nie jest on może tak elegancki jak hydrator. Ale też zgrabny:
  1. <?php
  2.  
  3. namespace Album\Entity;
  4.  
  5. use Doctrine\ORM\Mapping as ORM;
  6. use Album\Entity\Album;
  7.  
  8. /**
  9.  * Description of Song
  10.  *
  11.  * @author Bartosz Bartniczak <bbartniczak@zenex.pl>
  12.  *
  13.  * @ORM\Entity
  14.  * @ORM\Table(name="Song")
  15.  */
  16. class Song {
  17.  
  18. /**
  19.   * @var string
  20.   * @ORM\Id
  21.   * @ORM\GeneratedValue(strategy="AUTO")
  22.   * @ORM\Column(type="string");
  23.   */
  24. protected $title;
  25.  
  26. /**
  27.   *
  28.   * @var integer
  29.   *
  30.   * @ORM\Id
  31.   * @ORM\GeneratedValue(strategy="NONE")
  32.   * @ORM\Column(type="integer");
  33.   */
  34. protected $albumId;
  35.  
  36. /**
  37.   *
  38.   * @var Album
  39.   *
  40.   * @ORM\ManyToOne(targetEntity="Album", inversedBy="songs")
  41.   * @ORM\JoinColumn(name="albumId", referencedColumnName="albumId")
  42.   */
  43. protected $album;
  44.  
  45. function __construct($title = NULL, Album $album = NULL) {
  46. $this->setTitle($title);
  47. if (!is_null($album)) {
  48. $this->setAlbum($album);
  49. }
  50. }
  51.  
  52. function getTitle() {
  53. return $this->title;
  54. }
  55.  
  56. function getAlbum() {
  57. return $this->album;
  58. }
  59.  
  60. function setTitle($title) {
  61. $this->title = $title;
  62. }
  63.  
  64. function setAlbum(Album $album) {
  65. $this->album = $album;
  66. $this->albumId = $album->getAlbumId();
  67. }
  68.  
  69. }


  1. public function indexAction() {
  2.  
  3. $killEmAllData = array(
  4. 'artist' => 'Metallica',
  5. 'title' => 'Kill em All',
  6. 'songs' => array(
  7. array('title' => 'Hit the Lights'),
  8. array('title' => 'The Four Horsemen'),
  9. array('title' => 'Motorbreath'),
  10. array('title' => 'Jump in the Fire'),
  11. array('title' => '(Anesthesia) Pulling Teeth'),
  12. array('title' => 'Whiplash'),
  13. array('title' => 'Phantom Lord'),
  14. array('title' => 'No Remorse'),
  15. array('title' => 'Seek & Destroy'),
  16. array('title' => 'Metal Militia'),
  17. array('title' => 'Am I Evil?'),
  18. array('title' => 'Blitzkrieg'),
  19. )
  20. );
  21.  
  22. $songs = $killEmAllData['songs'];
  23. unset($killEmAllData['songs']);
  24.  
  25. // var_dump($killEmAllData);
  26. //
  27. $hydrator = $this->getDoctrineHydrator();
  28. $killEmAll = $hydrator->hydrate($killEmAllData, new Album());
  29. $this->getEntityManager()->persist($killEmAll);
  30. $this->getEntityManager()->flush();
  31.  
  32. $songsCollection = new \Doctrine\Common\Collections\ArrayCollection();
  33. foreach ($songs as $songArray) {
  34. $songArray['album'] = $killEmAll;
  35. $song = $hydrator->hydrate($songArray, new \Album\Entity\Song());
  36. $songsCollection->add($song);
  37. }
  38. $killEmAll->addSongs($songsCollection);
  39.  
  40. $this->getEntityManager()->persist($killEmAll);
  41. $this->getEntityManager()->flush();
  42.  
  43. return new ViewModel();
  44. }


Chyba, że ktoś jednak wie jak to ominąć. Wygląda na to, że Doctrine nie radzi sobie ze złożonymi kluczami obcymi.
Go to the top of the page
+Quote Post

Reply to this topicStart new topic
1 Użytkowników czyta ten temat (1 Gości i 0 Anonimowych użytkowników)
0 Zarejestrowanych:

 



RSS Wersja Lo-Fi Aktualny czas: 25.04.2024 - 10:57