day18(から3日遅れた)今日はDoctrineとEloquentそれぞれに用意された1つのテーブルに複数の種類のデータをもたせる方法を見ていきます。
Doctrine(SingleTableInheritance)
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; }
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'; } }
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 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)
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'); } }
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'); } }
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 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対象を表す識別子として任意の文字列を指定することもできる)