はじめに
Laravelによるポリモーフィックリレーションのサンプルです。
ポリモーフィックリレーションとは、親が複数ある親子関係を表すときに使います。
あまり日本語の文献がないので書き起こしてみました。
今回は投稿とコメントの両方とも画像を添付できる掲示板を作成します。画像のテーブルは投稿テーブルかコメントテーブルのどちらかが親になります。このような場合、ポリモーフィックリレーションを使うとシンプルに定義できます。
GitHub: https://github.com/naga3/laravel-sample-polymorphic
プロジェクト作成
Laravelのプロジェクトを適当な名前で新規作成してください。
その後、ファイルストレージをウェブ上から見るためにシンボリックリンクを張ります。
php artisan storage:link
モデル作成
投稿用のモデルPost
、コメント用のモデルComment
、添付画像用のモデルImage
を作成します。
php artisan make:model Post -m
php artisan make:model Comment -m
php artisan make:model Image -m
-m
オプションでマイグレーションファイルも同時に作成します。
テーブル作成
作成されたマイグレーションファイルを編集します。
xxxx_create_posts_table.php
投稿テーブルです。title
が題名、body
が本文になります。
public function up()
{
Schema::create('posts', function (Blueprint $table) {
$table->increments('id');
$table->string('title');
$table->text('body');
$table->timestamps();
});
}
xxxx_create_comments_table.php
コメントテーブルです。post_id
が投稿ID、body
がコメント本文となります。
public function up()
{
Schema::create('comments', function (Blueprint $table) {
$table->increments('id');
$table->integer('post_id');
$table->text('body');
$table->timestamps();
});
}
xxxx_create_images_table
添付画像テーブルです。$table->morphs('target');
によってtarget_id
とtarget_type
の2カラムを定義しています(Thx to @nunulk 様)。target_id
は画像が添付されている投稿IDまたはコメントID、target_type
は画像が添付されているモデルのクラス名(App\Post
またはApp\Comment
)となります。このような形式にすることでポリモーフィックリレーションを設定しやすくしています。filename
は画像のファイル名です。
public function up()
{
Schema::create('images', function (Blueprint $table) {
$table->increments('id');
$table->morphs('target');
$table->string('filename');
$table->timestamps();
});
}
リレーションの設定
モデルクラスにリレーションを設定します。
Post.php
class Post extends Model
{
public function images()
{
return $this->morphMany(Image::class, 'target');
}
public function comments()
{
return $this->hasMany(Comment::class);
}
}
images
メソッド内のmorphMany
メソッドでポリモーフィックリレーションを設定しています。2番目の引数のtarget
が重要で、これによってtarget_id
が投稿ID、target_type
の投稿モデルのクラス名となり、画像がどの投稿の添付であるかを判別します。
comments
メソッド内のhasMany
メソッドは1対多のリレーションです。投稿に紐付いているコメントを全て取得します。
Comment.php
class Comment extends Model
{
public function images()
{
return $this->morphMany(Image::class, 'target');
}
}
こちらも、morphMany
メソッドでポリモーフィックリレーションを設定しています。target_id
のコメントIDとtarget_type
のコメントモデルのクラス名により、画像がどのコメントの添付であるかを判別します。
Image.php
class Image extends Model
{
protected $guarded = ['id'];
}
Image
モデルのリレーションは設定する必要はありません。一括create
するのでMass assignmentの設定だけしています。
ルートの設定
web.php
を以下のようにします。
Route::get('/', function () {
return view('home', ['posts' => App\Post::latest('id')->get()]);
});
// Post
Route::post('/', function () {
$post = new App\Post();
$post->title = request('title');
$post->body = request('body');
$post->save();
$files = request('files');
if ($files) foreach ($files as $file) {
$file->store('public');
$post->images()->create(['filename' => $file->hashName()]);
}
return redirect('/');
});
// Comment
Route::post('/comments', function () {
$comment = new App\Comment();
$comment->post_id = request('post_id');
$comment->body = request('body');
$comment->save();
$files = request('files');
if ($files) foreach ($files as $file) {
$file->store('public');
$comment->images()->create(['filename' => $file->hashName()]);
}
return redirect('/');
});
// Post
のところが投稿部分です。
$file->store
メソッドでファイルをストレージに保存しています。
$post->images()->create
メソッドで、投稿に紐付いた画像レコードを生成しています。hashName
は上記store
メソッドで保存されたファイル名です。target_id
とtarget_type
は自動的に入ります。
// Comment
のところがコメント部分です。投稿とほぼ同じ処理です。
ビューの設定
ビューファイルhome.blade.php
を作成して以下のようにします。
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>BBS</title>
</head>
<body>
<form method="post" enctype="multipart/form-data">
{{ csrf_field() }}
<p>題名<br><input type="text" name="title" size="50"></p>
<p>本文<br><textarea name="body" cols="50" rows="8"></textarea></p>
<p>添付<br><input type="file" name="files[]" multiple></p>
<button>投稿</button>
</form>
@foreach ($posts as $post)
<hr>
<p>{{ $post->title }}</p>
<pre>{{ $post->body }}</pre>
<p>
@foreach ($post->images as $image)
<img src="{{ 'storage/' . $image->filename }}">
@endforeach
</p>
<blockquote>
@foreach ($post->comments as $comment)
<pre>{{ $comment->body }}</pre>
<p>
@foreach ($comment->images as $image)
<img src="{{ 'storage/' . $image->filename }}">
@endforeach
</p>
@endforeach
<form method="post" action="{{ url('/comments') }}" enctype="multipart/form-data">
{{ csrf_field() }}
<p>コメント<br><textarea name="body" cols="50" rows="3"></textarea></p>
<p>添付<br><input type="file" name="files[]" multiple></p>
<input type="hidden" name="post_id" value="{{ $post->id }}">
<button>コメント投稿</button>
</form>
</blockquote>
@endforeach
</body>
</html>
おわりに
ポリモーフィックリレーションを使うことによって、親が複数ある親子関係をスッキリと記述することができました。