DoctrineとEloquent比較大全18: 1つのテーブルに複数の種類のデータをもたせる

day18（から3日遅れた）今日はDoctrineとEloquentそれぞれに用意された1つのテーブルに複数の種類のデータをもたせる方法を見ていきます。

## Doctrine（SingleTableInheritance）

```php:Entity/Reaction.php
<?php

declare(strict_types=1);

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

#[ORM\Table(name: 'reactions')]
#[ORM\Entity]
#[ORM\InheritanceType('SINGLE_TABLE')]
#[ORM\DiscriminatorColumn(name: 'type', type: 'string')]
#[ORM\DiscriminatorMap([
    'like' => Like::class,
    'sad' => Sad::class,
])]
abstract class Reaction
{
    #[ORM\Id]
    #[ORM\GeneratedValue(strategy: 'AUTO')]
    #[ORM\Column(type: 'integer')]
    private int $id;

    #[ORM\ManyToOne(targetEntity: Book::class, inversedBy: 'reactions')]
    #[ORM\JoinColumn(onDelete: 'cascade')]
    private ?Book $book;

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

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

    public function getBook(): ?Book
    {
        return $this->book;
    }

    public function setBook(?Book $book): void
    {
        $this->book = $book;
    }

    abstract public function getType(): string;
}

```

```php:Entity/Like.php
<?php

declare(strict_types=1);

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
class Like extends Reaction
{
    public function getType(): string
    {
        return 'like';
    }
}

```

```php:Entity/Sad.php
<?php

declare(strict_types=1);

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
class Sad extends Reaction
{
    public function getType(): string
    {
        return 'sad';
    }
}
```

```php
<?php

declare(strict_types=1);

use App\Entity\Author;
use App\Entity\Book;
use App\Entity\Like;
use App\Entity\Sad;
use Doctrine\ORM\EntityManagerInterface;

require __DIR__.'/../vendor/autoload.php';

/** @var EntityManagerInterface $entityManager */
$entityManager = require __DIR__.'/bootstrap.php';
$author = new Author();
$author->setName('tanakahisateru');
$book = new Book();
$book->setTitle('ちょうぜつソフトウェア設計入門');
$book->setAuthor($author);
$book->setPrice(3080);
$book->setDescription('可愛い表紙で釣ってオブジェクト指向をガッツリ解説する本');
$book->addReaction(new Like());
$book->addReaction(new Sad());
$book->addReaction(new Like());

$entityManager->persist($author);
$entityManager->persist($book);
$entityManager->flush();

$entityManager->refresh($book);
echo $book->getReactions()->count().PHP_EOL; // 3件
echo $book->getReactions()[0]->getType().PHP_EOL; // Likeのインスタンス
echo $book->getReactions()[1]->getType().PHP_EOL; // Sadのインスタンス

```

- SingleTableInheritanceパターン（ https://www.martinfowler.com/eaaCatalog/singleTableInheritance.html ）が実装されています。
- Bookエンティティに対する2種類のリアクション（Like, Sad）をDB上は1つのreactionsテーブルに保存することができます。
- 呼び出し時はtype=likeのreactionsレコードは `Like` エンティティのインスタンスとして、type=sadのreactionsレコードは `Sad` エンティティのインスタンスとして取り出されます。

## Eloquent（PolymorphicRelationship）

```php:Models/Book.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
 * @property string|null $description
 */
class Book extends Model
{
    public $fillable = [
        'title',
        'price',
        'author_id',
        'description',
    ];

    public $timestamps = false;

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

    public function likes()
    {
        return $this->morphMany(Like::class, 'likable');
    }
}

```

```php:Models/Author.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);
    }

    public function likes()
    {
        return $this->morphMany(Like::class, 'likable');
    }
}

```

```php:Models/Like.php
<?php

declare(strict_types=1);

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Like extends Model
{
    public $timestamps = false;

    public function likable()
    {
        return $this->morphTo();
    }
}

```

```php
<?php

declare(strict_types=1);

use App\Models\Author;
use App\Models\Book;
use App\Models\Like;

require __DIR__.'/../vendor/autoload.php';
require __DIR__.'/bootstrap.php';

/** @var Book $book1 */
$book1 = Book::find(11);
$like1 = new Like();
$book1->likes()->save($like1);
/** @var Author $author1 */
$author1 = Author::find(1);
$like2 = new Like();
$author1->likes()->save($like2);

$book1->refresh();
echo count($book1->likes).PHP_EOL; // like1件

$author1->refresh();
echo count($author1->likes).PHP_EOL; // like1件

```

- EloquentにはSingleTableInheritanceは実装されていません。SingleTableInheritanceがやりたい場合はサードパーティのプラグインを使用する必要があります。代わりに、ポリモーフィックリレーションが実装されています。
- Bookモデルに対するLikeとAuthorモデルに対するLikeをDB上で1つの `likes` テーブルに保存することができます。
- likable_type, likeable_idというカラムを持ち、likable_typeにはLike対象のクラス名が保存されます（Like対象を表す識別子として任意の文字列を指定することもできる）