LoginSignup
7
6

More than 3 years have passed since last update.

(Laravel5.8)ポリモーフィックを使った時の保存方法で詰んだのでメモしておく

Last updated at Posted at 2019-05-22

はじめに

マルチログインを実装した画像投稿アプリを作っている中で、ポリモーフィックリレーションについての記事があまりなくて悩んだので、忘備録として残しておきます。内容はモデルのリレーションの話と、実装方法のみ。Laravelのインストール〜モデルの作成、マルチログイン実装などは参考になる記事がたくさんあったのでそちらをご参照ください。

今回のユースケースの解説

マルチログインを実装した時に、usersテーブルとgroupsテーブルを使ってそれぞれ別のログインページからログインさせて、middlewareを使ってログイン後の画面も別々で管理していました。group側からは写真の投稿ができたりマイページで自分の投稿を一覧で見たりする機能がありますが、user側からは投稿の閲覧とコメントしかできないように設計しています。

今回ポリモーフィックを使った箇所はコメント機能の実装です。コメントはuser側からもgroup側からも可能で、誰がコメントしたか分かるようにします。この時コメント(comment)はuserもしくはgroupどちらかに所属することになります。これがポリモーフィック関係です。

テーブル構造

users //親その1
    id - integer
    name - string
    nickname - string
    email - string
    ///省略

groups //親その2
    id - integer
    name - string
    email - string
    establish - date
    ///省略

comments  //userかgroupいずれかにひも付く
    id - integer
   post_id - unsignedInteger   //外部キーでpostにひも付く
    body - text
    commentable_id - integer    //userかgroupのidが入る
    commentable_type - string   //モデル名:App\UserかApp\Groupが入る

commentsテーブルだけ書き方が特殊です。構造見れば分かりますが、commentsテーブルにuser_idgroup_idを外部キーとして持たせておけば、別にポリモーフィックを使わずともそれぞれ一対多のリレーションで処理できるのです。
ということでポリモーフィックは実際使う人が少なくて記事がなかったんだろうなと思っています。(私はせっかく機能としてあるなら使いたい!ということで使いました)

それと、ポリモーフィック自体がアンチパターンでもあるという記事も見かけましたので置いておきますね。

SQLアンチパターンを読んで (ポリモーフィック関連について)

実際に書いていく

前提はここまでにして、いざ書いていきます。まずは、各モデルにリレーションの定義をしていきます。

各モデルのリレーション定義

User.php
class User extends Authenticatable
{
    /** 
     * Commentに対してmorphMany
     */
    public function comments()
    {
        return $this->morphMany('App\Comment', 'commentable');
    }
}
Group.php
class Group extends Authenticatable
{
    /** 
     * Userと同じ
     */
    public function comments()
    {
        return $this->morphMany('App\Comment', 'commentable');
    }
}

Comment.php

class Comment extends Model
{
    /**
     * 忘れずに$fillableを定義する
     */
    protected $fillable = [
        'post_id',
        'body',
        'commentable_id',
        'commentable_type'
    ];

    /**
     * コメントはPost(投稿)にひも付く(今回は解説しません)
     */
    public function post()
    {
        return $this->belongsTo('App\post');
    }
    /**
     * 所有しているcommentableモデルの全取得
     */
    public function commentable()
    {
        return $this->morphTo();
    }
}

で、ここまではだいたい調べれば書いてあるので、大丈夫だと思います。
次は、リレーションが定義できているかを調べるため、まずはtinkerでダミーのデータを登録しておきます。

tinkerでダミーデータの登録

php artisan tinker
>>> $com = new App\Comment
=> App\Comment {#2972}
>>> $com->post_id = 1
=> 1
>>> $com->body = 'testtesttest'
=> "testtesttest"
>>> $com-> commentable_id = 1
=> 1
>>> $com->commentable_type = 'App\Group'
=> "App\Group"
>>> $com->save();
=> true
>>> exit

mysql> select * from comments;
+----+---------+--------------+----------------+------------------+---------------------+---------------------+
| id | post_id | body         | commentable_id | commentable_type | created_at          | updated_at          |
+----+---------+--------------+----------------+------------------+---------------------+---------------------+
|  1 |       1 | testtesttest |              1 | App\Group        | 2019-05-21 05:51:03 | 2019-05-21 05:51:03 |
+----+---------+--------------+----------------+------------------+---------------------+---------------------+
1 row in set (0.00 sec)

ちゃんと登録されました。

投稿者に関連するコメントを取り出す

先ほど登録した情報を取り出します。コメントの投稿者はGroupモデルのid=1としたので、Groupモデルからコメントのデータを取って来られれば成功です。

php artisan tinker
>>> $comment = App\Group::find(1)->comments->all();

[
     App\Comment {#2988
       id: 1,
       post_id: 1,
       body: "testtesttest",
       commentable_id: 1,
       commentable_type: "App\Group",
       created_at: "2019-05-21 05:51:03",
       updated_at: "2019-05-21 05:51:03",
     },
]

//ちなみに逆の時、つまりcommentからuserもしくはgroupを取得する時はこんな感じ

>>> $auth = App\Comment::find(1)->commentable->all();
=> App\Group {#2989
     id: 1,
     name: "グループname",
     cover_img: "sample.jpeg",
     icon_img: "sample.jpeg",
     //・・・省略
  }

よしっ!!!ページに実装だ!

・・・と思ってview側からコメントの表示はできたのですが、保存は???となりました。

コメントの保存

結論から言うと、めちゃくちゃ簡単でした。この辺が調べた時見たやつです。

commentsテーブルのcommentable_idcommentable_typeは定義せずともいい感じにやって頂けるようで。相変わらずかしこい。

CommentController.php
    public function store(Request $request)
    {
        /**ログインユーザーの情報取得*/
        $auth = Auth::user();

        /**ログインユーザーにひも付けてコメントを保存*/
        $auth->comments()->create(
            [
            'post_id' => $request->post_id,
            'body' => $request->body
            ]
        );
        //これで保存完了

        /**リダイレクト先の振り分け*/
        if (get_class($auth) == "App\Group") {
            return redirect('/group/posts');
        } elseif (get_class($auth) == "App\User") {
            return redirect('/');
        }
    }

これでOKです!最後に一応、groupのview側からのコメント投稿箇所のコードも一部載せておきます。ルートの定義は、こんな感じでいけるはず。

web.php
    Route::get('group/comments', 'CommentController@index')
    Route::post('group/comments', 'CommentController@store');
index.blade.php
   @forelse ($post->comments as $comment)
     <p>コメント</p>
     <pre>{{ $comment->body }}</pre>
   @empty
     <p>コメントがまだありません</p>
   @endforelse
     <form method="post" action="{{ url('group/comments') }}">
     @csrf
       <p>コメント<br><textarea name="body" cols="40" rows="3"></textarea></p>
       <input type="hidden" name="post_id" value="{{ $post->id }}">
       <button type="submit">コメント投稿</button>
     </form>

不備や、もっとこうしたほうがいい!等ご意見があればぜひコメントいただければと思います。

7
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
6