Search code examples
doctrine-ormdoctrine

Doctrine One-to-Many Mapping with auto generated ids


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 Posts 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!


Solution

  • 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