DoctrineとEloquent比較大全7: テーブル間のリレーションを定義

day7の今日は、テーブル間に外部キーによるリレーションがある場合にDoctrine, Eloquentそれぞれでどうやって表すか見ていきます。

SQL的にはauthorsテーブルとbooksテーブルへのauthor_idカラム（authors.idに対する外部キーつき）を追加しています。 <https://github.com/77web/doctrine-vs-eloquent/blob/7f98ff21e16a1223e0ad5936e72e8be60c381ba0/sql/book.sql>

## Doctrine

author_idを追加したbooksテーブルを表すBookエンティティにはauthor_idプロパティでなくauthorプロパティを追加します。
```php
<?php

declare(strict_types=1);

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
#[ORM\Table(name: 'books')]
class Book
{
    #[ORM\Id]
    #[ORM\Column(type: 'integer')]
    #[ORM\GeneratedValue(strategy: 'AUTO')]
    private string $id;

    #[ORM\Column(type: 'string', length: 255, nullable: false)]
    private string $title;

    #[ORM\Column(type: 'integer')]
    private int $price;

    #[ORM\ManyToOne(targetEntity: Author::class)]
    #[ORM\JoinColumn(onDelete: 'CASCADE')]
    private Author $author;

    public function getId(): string
    {
        return $this->id;
    }

    public function setId(string $id): void
    {
        $this->id = $id;
    }

    public function getTitle(): string
    {
        return $this->title;
    }

    public function setTitle(string $title): void
    {
        $this->title = $title;
    }

    public function getPrice(): int
    {
        return $this->price;
    }

    public function setPrice(int $price): void
    {
        $this->price = $price;
    }

    public function getAuthor(): Author
    {
        return $this->author;
    }

    public function setAuthor(Author $author): void
    {
        $this->author = $author;
    }
}
```
<https://github.com/77web/doctrine-vs-eloquent/blob/7f98ff21e16a1223e0ad5936e72e8be60c381ba0/Doctrine/Entity/Book.php>

一方、所有側であるAuthorにもOneToManyリレーションを定義することで、author_idに自らのIDが設定されているBookエンティティのリストを取得することができます。
```php
<?php

declare(strict_types=1);

namespace App\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
#[ORM\Table(name: 'authors')]
class Author
{
    #[ORM\Id]
    #[ORM\Column(type: 'integer')]
    #[ORM\GeneratedValue(strategy: 'AUTO')]
    private string $id;

    #[ORM\Column(type: 'string', length: 255)]
    private string $name;

    #[ORM\OneToMany(targetEntity: Book::class)]
    private Collection $books;

    public function __construct()
    {
        $this->books = new ArrayCollection();
    }

    public function getId(): string
    {
        return $this->id;
    }

    public function setId(string $id): void
    {
        $this->id = $id;
    }

    public function getName(): string
    {
        return $this->name;
    }

    public function setName(string $name): void
    {
        $this->name = $name;
    }

    public function getBooks(): Collection
    {
        return $this->books;
    }

    public function addBook(Book $book): void
    {
        if (!$this->books->contains($book)) {
            $book->setAuthor($this);
            $this->books->add($book);
        }
    }
}
```
<https://github.com/77web/doctrine-vs-eloquent/blob/7f98ff21e16a1223e0ad5936e72e8be60c381ba0/Doctrine/Entity/Author.php>

- `Book::$author` の `ManyToOne` アトリビュートによりBookがAuthorに対するinverse-sideであることを示します。
- `Author::$books` の `OneToMany` アトリビュートによりAuthorがBookに対するowning-sideであることを示します。

## Eloquent

author_idを追加したbooksテーブルを表すBookモデルには `author()` メソッドを追加します。
```php
<?php

declare(strict_types=1);

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

/**
 * App\Models\Book
 *
 * @property int $id
 * @property string $title
 * @property int $price
 * @property Author $author
 */
class Book extends Model
{
    public $fillable = [
        'title',
        'price',
        'author_id',
    ];

    public function author()
    {
        return $this->belongsTo(Author::class);
    }
}
```
<https://github.com/77web/doctrine-vs-eloquent/blob/7f98ff21e16a1223e0ad5936e72e8be60c381ba0/Eloquent/Models/Book.php>

一方、所有側であるAuthorにも `books()` メソッドを定義することで、author_idに自らのIDが設定されているBookモデルのリストを取得することができます。
```php
<?php

declare(strict_types=1);

namespace App\Models;

use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;

/**
 * App\Models\Author
 *
 * @property int $id
 * @property string $name
 * @property Collection<Book> $books
 */
class Author extends Model
{
    public $fillable = [
        'name',
    ];

    public function books()
    {
        return $this->hasMany(Book::class);
    }
}
```
<https://github.com/77web/doctrine-vs-eloquent/blob/7f98ff21e16a1223e0ad5936e72e8be60c381ba0/Eloquent/Models/Author.php>