58
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

posted at

updated at

【Laravel入門】リレーションとユーザーの一覧表示

リレーションとは

例えば、投稿記事articleが表示された時に「投稿者の名前も知りたい!」と思うのは当然だろう。そのためには何をするのか?articlesテーブルにuser_nameの様なカラムを追加するのか?

しかし、投稿者情報を引き出すために既存のusersテーブルを活用できれば簡単だ。それを可能にしてくれるのが、リレーションという仕組みだ。

公式ドキュメントより(一部抜粋)

データベーステーブルは大抵の場合他のものと関連しています。たとえばブログ投稿(ポスト)は多くのコメントを持つか、それを投稿したユーザーと関連しています。Eloquentはそうしたリレーションを簡単に管理し操作できるようにするとともに、様々なタイプのリレーションをサポートしています。

Eloquentのリレーション(関係)とはEloquentモデルクラスのメソッドとして定義します。

今回はそのための設定を行っていく。

主キーと外部キー

今まで2つのテーブルusersとarticlesを作成した。この2つのテーブルで共通の値が入るカラムを設定し、これを元に結びつければ良いのだ。これをidカラムとuser_idカラムにする。

この場合、usersテーブルのid主キーと呼ばれ、articlesテーブルのuser_id外部キーと呼ばれる。articlesテーブルは、user_idを元にusersテーブルから必要なuser情報を引き出すことができるのだ。

実はLaravelでは、既にuser_idにはusersテーブルのidが入る仕組みになっているのだ。

公式ドキュメントより(一部抜粋)

Eloquentは、Commentモデルに対する外部キーを自動的に決めることを心に留めてください。規約によりEloquentは、自分自身のモデル名の「スネークケース」に_idのサフィックスをつけた名前と想定します。ですから今回の例でEloquentは、Commentモデルの外部キーをpost_idであると想定します。

モデル名が異なるので、CommentモデルをArticleモデル、PostモデルをUserモデルとして読んで欲しい。

モデル編集

ここで注意するのはモデル同士の結びつき方だ。UserとArticleの関係は1対多と呼ばれるものだ。

  • ユーザー(投稿者)は複数の記事を投稿できる。よって、Userは複数のArticleを持つことができる
  • 1つの記事の投稿者は1人しか存在しない。よって、Articleは1つのUserしか持つことができない

上記の関係に基づき、各モデルにはこのように関数を追記する。

User.php
public function articles()
{
    return $this->hasMany('App\Article');
}
Article.php
public function user()
{
    return $this->belongsTo('App\User');
}

コントローラ編集

Articleコントローラの全体像はこうなる。

ArticleController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Article;

class ArticleController extends Controller
{
    public function __construct()
    {
        $this->middleware('auth')->except(['index']);
    }

    public function index()
    {
        $articles = Article::All();
        return view('article.index', ['articles' => $articles]);
    }

    public function add()
    {
        return view('article.add');
    }

    public function create(Request $request)
    {
        $article = new Article;
        $article->user_id = $request->user()->id;
        $article->title = $request->title;
        $article->content = $request->content;
        $article->save();
        return redirect('/');
    }

    public function edit(Request $request)
    {
        $article = Article::find($request->id);
        return view('article.edit', ['article' => $article]); 
    }

    public function update(Request $request)
    {
        $article = Article::find($request->id);
        $article->title = $request->title;
        $article->content = $request->content;
        $article->save();
        return redirect('/');
    }

    public function delete(Request $request)
    {
        $article = Article::find($request->id);
        return view('article.delete', ['article' => $article]); 
    }

    public function remove(Request $request)
    {
        $article = Article::find($request->id);
        $article->delete();
        return redirect('/');
    }
}

アクセス制限

ゲスト(ログインしていない人)の場合は、投稿記事の閲覧のみ可能とし、新規投稿などの機能は使わせたくない。

この場合、役に立つのがコンストラクターだ。これがmiddleware('auth')を呼び出し、アクセス制限をかけてくれる。さらにexcept(['index'])を記載すれば、アクセス制限の対象にindexアクションを除いてくれる。

コンストラクターやミドルウェアについては・・・ほとんど知らない。これらは今度の課題とする。

変更点

実は、変更があるのはcreateアクションだけだ。$article->user_id = $request->user()->id;Articleモデルのuser関数を呼び出し、関連するUserインスタンスのidプロパティArticleインスタンスのuser_idプロパティとして保存している。

その他のアクションについてもビューでユーザー名を表示させたい箇所があるが、ビューからの呼び出しが簡単にできるため、コントローラ部分の編集はほとんどないのだ。

ビュー編集

リンクの部分を編集する。@authディレクティブでユーザーとゲストの判別を行い、表示する項目を分けている。

views/layouts/layout.blade.php
<a href="{{ url('/') }}">トップページ</a>
<a href="{{ route('user_index') }}">ユーザー一覧</a>
<a href="{{ route('article_index') }}">記事一覧</a>
<a href="{{ route('article_add') }}">記事作成</a>
@auth
    <a href="{{ url('/home') }}">Home</a>
@else
    <a href="{{ route('login') }}">ログイン</a>
    <a href="{{ route('register') }}">会員登録</a>
@endauth

article.indexビュー

こちらは大幅に編集した。

resources/views/article/index.blade.php
@extends('...layouts.layout')

@section('article_index')
    投稿一覧<br>
    <table>
        <tr><th>ユーザーID</th><th>名前</th><th>タイトル</th><th>内容</th></tr>
    @foreach($articles as $article)
        <tr>
            <td>{{ $article->user_id }}</td>
            <td>{{ $article->user->name }}</td>
            <td>{{ $article->title }}</td>
            <td>{{ $article->content }}</td>
            @auth
                @if( ( $article->user_id ) === ( Auth::user()->id ) )
                    <td><a href="{{ route('article_edit') }}?id={{ $article->id }}">編集</a></td>
                    <td><a href="{{ route('article_delete') }}?id={{ $article->id }}">削除</a></td>
                @endif
            @endauth
        </tr>
    @endforeach
    </table>
@endsection
  • {{ $article->user_id }}の部分は、{{ $article->user->id }}と記述しても同様の結果が得られる。
  • {{ $article->user->id }}では、ArticleのUserモデルuser動的プロパティにより取得している。(動的プロパティ?)
  • @authディレクテブの中で@ifディレクティブを使用し、現在のユーザーにのみ表示される部分を作成している。
  • @ifディレクティブ中では、条件を{{ }}ではなく( )を使用して記述する。
  • Auth::user()->idは、現在ログインしているユーザーのidプロパティを取得する。
  • 結果、記事の投稿者にのみ編集・削除メニューの表示がされる。

ビューの中でメソッドが直接呼び出せるのは便利だ。

あと、動的プロパティについてはこちらから。commentsをarticlesに読み替えよう。分かったような分からないような気がする。

リレーションを定義したら、commentsプロパティによりコメントのコレクションへアクセスできます。Eloquentは「動的プロパティ」を提供しているので、モデルのプロパティとして定義したリレーションメソッドへアクセスできることを覚えておきましょう。

article.addビュー

必要のないユーザーID:<input type='text' name='user_id'><br>は削除した。しかし、どうやってuser_idを取得しているのだろうか?

createアクションでは$article->user_id = $request->user()->id;の記述がされている。HTTPリクエストインスタンスが代入されている$requestには、現在ログインしているユーザーの情報も入っているのだろうか?だとしたら、つじつまが合うのだか。

views/article/add.blade.php
@extends('...layouts.layout')

@section('article_add')
    投稿作成<br>

    <form action='{{ route('article_create') }}' method='post'>
        {{ csrf_field() }}
            タイトル:<input type='text' name='title'><br>
            内容:<input type='text' name='content'><br>
            <input type='submit' value='投稿'>
    </form>
@endsection

article.editビューarticle.deleteビューについては省略する。特に変更を加える必要はないのだが、ユーザー名を表示させたい場合は適宜{{ $article->user->name }}を追記しよう。

ユーザーの一覧表示

以前のCRUD実装で、投稿記事の一覧表示機能は作成した。ユーザーも同様に一覧表示ができるようにしたい。

コントローラ作成

Userコントローラを作成する。

$ php artisan make:controller UserController
Controller created successfully.

早速編集する。ログインしていなくても一覧表示機能を使用させるために、 $this->middleware('auth')->except(['index']);と記述する。(現在はindexアクションしか作成していないので、この記述がなくても同様の動きはする。)

UserController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\User;

class UserController extends Controller
{
    public function __construct()
    {
        $this->middleware('auth')->except(['index']);
    }

    public function index()
    {
        $users = User::All();
        return view('user.index', ['users' => $users]);
    }
}

ルーティング設定

web.php
Route::get('/user/index', 'UserController@index')->name('user_index');

ビュー作成

resources/viewsディレクトリuserディレクトリを用意し、index.blade.phpを作成する。

resources/views/user/index.blade.php
@extends('...layouts.layout')

@section('user_index')
    ユーザー一覧<br>
    <table>
        <tr><th>ユーザーID</th><th>名前</th></tr>
    @foreach($users as $user)
        <tr>
            <td>{{ $user->id }}</td>
            <td>{{ $user->name }}</td>
        </tr>
    @endforeach
    </table>
@endsection

こちらも忘れずに。

views/layouts/layout.blade.php
@yield('user_index')

入門編終了

CRUDとリレーションを実装することができたので、これで入門編は終了とする。他にも付け足したい機能はあるし、デザインも整えたい。AWSも使いたい。それらについては、まとまったら別記事として投稿する予定だ。

拙い記事ではあったが、誰かの何かのお役に立てれば、さらにベテランの方々にも初心を思い出していただければ、これ以上の喜びはない。

最後までお読みいただき、ありがとうございました!

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
58
Help us understand the problem. What are the problem?