I have two classes Author
and Posts
. One author can have many posts and the posts
table has a reference to the author by author_id
. I would like to tell Doctrine to populate author_id
after it obtains the auto generated id from the database. Is that possible?
#[Entity]
#[Table(name: 'authors')]
class Author {
#[Id]
#[GeneratedValue]
#[Column]
private ?int $id = null;
#[OneToMany(mappedBy: 'authorId', targetEntity: Post::class, cascade: ['persist'])]
private ?Collection $posts;
public function __construct(Collection $posts) {
$this->posts = $posts;
}
}
#[Entity]
#[Table(name: 'posts')]
class Post {
#[Id]
#[GeneratedValue]
#[Column]
private ?int $id = null;
#[Column]
#[ManyToOne(targetEntity: Author::class, inversedBy: 'id')]
#[JoinColumn(name: 'author_id', referencedColumnName: 'id')]
private ?int $authorId = null,
}
To save the data, I created an ArrayCollection
of Post
s with nulls because the data needed should be handled by Doctrine.
$posts = new ArrayCollection([
new Post(null, null),
new Post(null, null)
]);
$author = new Author(posts: $posts);
$em->persist($author);
$em->flush();
Thanks!
As already discussed in the comments the basic problem was trying to use ids to associate entities and not having them cross linked properly. I did want to see if I could get passing an array of child elements into a constructor to work. I made a test example inside of the Symfony framework since it has been way too long since I used Doctrine standalone. Everything should still work one the ORM is removed from the mapping namespace.
I also added a couple of properties to author/post just to make things more realistic. So this works:
$author = new Author('David Weber',new ArrayCollection([
new Post('SafeHold'),
new Post('Honor'),
]));
$this->em->persist($author);
$this->em->flush();
The Post entity is pretty much unchanged except for making the $author property an Author instead of an id.
#[ORM\Entity(repositoryClass: PostRepository::class)]
#[ORM\Table(name: 'posts')]
class Post
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255)]
private ?string $title = null;
#[ORM\ManyToOne(inversedBy: 'posts')]
private ?Author $author = null;
The Author gets a few tweaks to ensure the Post entity is linked to the author:
#[ORM\Entity(repositoryClass: AuthorRepository::class)]
#[ORM\Table(name: 'authors')]
class Author
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255)]
private ?string $name = null;
#[ORM\OneToMany(mappedBy: 'author', targetEntity: Post::class, cascade: ['persist'])]
private Collection $posts;
public function __construct(string $name, ?ArrayCollection $posts = null)
{
$this->name = $name;
$this->posts = new ArrayCollection();
if ($posts !== null) {
foreach($posts as $post) {
$this->addPost($post);
}
}
}
public function addPost(Post $post): self
{
if (!$this->posts->contains($post)) {
$this->posts->add($post);
$post->setAuthor($this); // Notice this
}
return $this;
}
I should also perhaps point out that the entity skeletons we generated by using Symfony's make:entity
command. So methods like addPost are automatically added.
Enjoy