'Symfony / Doctrine: OneToMany insert results in null id
I have two entities that I'm trying to apply a OneToMany / ManyToOne relationship to (one Game has many GameContent).
Game
/**
* @ORM\OneToMany(targetEntity="GameContent", mappedBy="game")
*/
private $contents;
public function __construct()
{
$this->contents = new ArrayCollection();
}
public function getContents()
{
return $this->contents;
}
GameContent
/**
* @ORM\ManyToOne(targetEntity="Game", inversedBy="contents")
*/
private $game;
And the following code inserts both records into their respective tables:
$game = $form->getData();
$content = new GameContent();
$content->setType('some type');
$game->getContents()->add($content);
$em = $this->getDoctrine()->getManager();
$em->persist($content);
$em->persist($game);
$em->flush();
However, the GameContent's game_id
is inserted as null
:
INSERT INTO game_content (type, game_id) VALUES (?, ?)
Parameters: { 1: 'some type', 2: null }
I've also tried:
- changing the order of
persist()
- replacing
$game->getContents()->add($content)
with$game->addContents($content)
by doing$this->contents[] = $content;
- removing
persist($content)
and havingcascade={"persist"}
on the Game entity.
Why is game_id
being inserted as null?
My current workaround is:
$em = $this->getDoctrine()->getManager();
$game = $form->getData();
$em->persist($game);
$content = new GameContent();
$content->setType('some type');
$content->setGame($game);
$em->persist($content);
$em->flush();
Solution 1:[1]
You have 2 solutions :
Persist children in controller
Without cascade={"persist"}
$em = $this->getDoctrine()->getManager();
// Get data
$game = $form->getData();
// Create new GameContent and hydrate
$content = new GameContent();
$content->setType('some type');
// Associate Game <> GameContent
$content->setGame($game);
// Persist GameContent
$em->persist($content);
// Persist Game and commit
$em->persist($game);
$em->flush();
Persist children in cascade
With cascade={"persist"}
in OneToMany relation.
Add in setGame()
function, to force association :
$game->addContent($this);
And remove persist :
$em = $this->getDoctrine()->getManager();
// Get data
$game = $form->getData();
// Create new GameContent and hydrate
$content = new GameContent();
$content->setType('some type');
// Associate Game <> GameContent
$content->setGame($game);
// Persist Game and commit
$em->persist($game);
$em->flush();
I think the error was also due to the positioning of the persist on game.
Solution 2:[2]
Add in setGame() function, to force association :
$game->addContent($this);
And remove persist :
$em = $this->getDoctrine()->getManager(); // Get data $game = $form->getData(); // Create new GameContent and hydrate $content = new GameContent(); $content->setType('some type'); // Associate Game <> GameContent $content->setGame($game); // Persist Game and commit $em->persist($game); $em->flush();
Note that today (Doctrine 2.7.1), the make:entity utility creates methods that does this stuff for you, in your case you would have had a method like this on your Game entity:
public function addContent(GameContent $content): self
{
$this->contents->add($content);
$content->setGame($this); // <-- IMPORTANT PART IS HERE
return $this;
}
Then, calling this on the game would have done the job:
this->addContent((new GameContent())->setType('some type'));
Solution 3:[3]
Further to the accepted answer, my next step was to create a form to handle the GameContent data, which led to further changes and some simplified logic.
I now setGame()
in Game::addContent()
, and so I've removed $game->addContent($this);
in GameContent::setGame()
.
Game
/**
* @var ArrayCollection
* @ORM\OneToMany(targetEntity="GameContent", mappedBy="game", cascade={"persist"})
*/
private $contents;
public function __construct()
{
$this->contents = new ArrayCollection();
}
public function getContents()
{
return $this->contents;
}
public function addContent(GameContent $content)
{
$this->contents->add($content);
$content->setGame($this);
return $this;
}
public function removeContent(GameContent $content)
{
$this->contents->removeElement($content);
return $this;
}
GameContent
/**
* @ORM\ManyToOne(targetEntity="Game", inversedBy="contents")
*/
private $game;
public function setGame(Game $game)
{
$this->game = $game;
return $this;
}
/**
* @return Game
*/
public function getGame()
{
return $this->game;
}
The real-world form-handling logic will look like this:
$game = $form->getData();
$em = $this->getDoctrine()->getManager();
$em->persist($game);
$em->flush();
More information at: http://symfony.com/doc/2.8/form/form_collections.html (see Doctrine: Cascading Relations and saving the "Inverse" side).
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Le Menach Florian |
Solution 2 | Ryierth |
Solution 3 | rybo111 |