Laravelでテーブル同士を関連付ける

LaravelでDBの取り扱いについて

動作環境

Laravel 5.6
php 7.1.3
MySQL 5.7.22
Bootstrap 4.0.0

今回DBはMySQLで実装している。

掲示板に関連付けられた、コメントを取り出したいので、その例を取り上げる。

流れとしては、

1.テーブル、カラム生成(マイグレーション実行)

2.モデル作成、関連付け(Eloquentモデル使用)

3.コントローラーに反映

4.ビューに反映(Blade使用)

になる。

テーブル・カラムの生成

テーブル生成は、1.SQLを直接打つ方法と2.マイグレーションする方法がある。

Laravelはマイグレーション機能があるから、ドキュメントルートからArtisanコマンドを実行して、マイグレーションファイルを生成する。

マイグレーションについては、公式ドキュメントの日本語訳に概略が載っているので引用する。

マイグレーションとはデータベースのバージョンコントロールのような機能です。アプリケーションデータベースのスキーマの更新をチームで簡単に共有できるようにしてくれます。マイグレーションは基本的にLaravelのスキーマビルダとペアで使い、アプリケーションのデータベーススキーマの作成を楽にしてくれます。もしあなたが今まで、チームメイトに彼らのローカルデータベーススキーマに手作業でカラムを追加するよう依頼したことがあるなら、データベースマイグレーションは、そうした問題を解決してくれます。

Laravel 5.6 データベース:マイグレーション

マイグレーションを行うことで、「DBの実装の度合いを共有できる、スキーマ生成が楽になる。」くらいの理解だ。

マイグレーションファイルはドキュメントルートでartisanコマンドを実行することで生成できる。

php artisan make:migration create_boards_table --create=board

create_board_tableが生成したいファイル名。 –create=boardがオプション。

今回は、create_board_tableを生成した。

–create=boardとすることで、「boards_tableを生成しますよ。」とLaravelに伝えているので、最初からマイグレーションファイルにある程度の記述がされている。

同じようにコメント用のマイグレーションファイルも生成する。

そして、カラムをfunction up内に追記。

app\database\migrations\xxx_xx_xx_xxxxxx_create_boards_table
<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateBoardTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('boards', function (Blueprint $table) {
            $table->increments('id');
            $table->string('title')->nullable();
            $table->string('name')->nullable();
            $table->string('content', 2000)->nullable();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('boards');
    }
}

app\database\migrations\xxx_xx_xx_xxxxxx_create_comments_table
<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateCommentTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('comments', function (Blueprint $table) {
            $table->increments('id');
            $table->unsignedInteger('board_id');
            $table->string('name')->nullable();
            $table->string('comments')->nullable();
            $table->timestamps();

            $table->foreign('board_id')->references('id')->on('boards');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('comments');
    }
}

コメントテーブルには、board_id(親要素のid)と合わせて外部キー制約をつける。

Laravelは賢いので、boards_tableのidはインクリメントかつアンサインドで実装している。

そのため、外部キーであるboard_idも同様にアンサインドにしておかないとマイグレーション実装時に外部キー制約にひっかかってエラーを吐くので、要注意。

最後にartisanコマンドでmigrateを行う。

php artisan migrate

うまくいっていれば、先ほど生成したファイルのマイグレートが実行される。

これでテーブル生成は完了。

モデルの生成・関連付け

LaravelはEloquent ORMがあり、ざっくりいうと、クエリ及びクエリによって取得生成されたカラムをオブジェクト化したようなものがある。

これを使用するためには、Eloquent Modelの設定、外部キーを使うのであればモデル同士の関連付けが必要になる。

まず、Modelの生成から。

モデルの生成

ルートディレクトリで

php artisan make:model Models/Board

php artisan make:model Models/Comment

今回モデルは、app/Modelsに入れているので、ディレクトリも指定してあげる。

これで、モデルの生成は完了。

モデルとテーブルの関連付けについては、モデル内で実装する必要がある。

ただ、テーブル名がモデルの複数形である場合、Laravelの方で察して自動的にモデルとテーブルを関連付ける

モデル同士の関連付け

今回は、掲示板の書き込みとコメントが、1対多の関係になっている。

それをBoardモデルに書き込む。

その他の関係については、公式ドキュメントのEloquentを参照

app\Models\Board
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Board extends Model
{
    //relation

    public function Comment(){

        return $this->hasMany('App\Models\Comment', 'board_id');
    }
}

これで関連付けは終わり。

コントローラーに反映

今回は、書き込みの取得については、コントローラーに実装する。

コントローラー生成もartisanコマンドで実行できる。

php artisan make:controller BoardController

コントローラーには、

use文でBoardとCommentはEloquentモデルも使っていることを明言してあげる。

今回はindexに処理を書き込んでいく。

コントローラーには、書き込みを全部(Board::all())とってきて変数$boardsに入れてほしいと書く。

より詳しい条件として、書き込みの新しい順で(orderBy(‘created_at’, ‘desc’))、→書き込み5件ごとにページ分けして(paginate(5))もしてほしいと書き込む。

ここまでしたら、bbsドキュメントのindexを表示してね。( return view(‘bbs.index’))。

変数$boardsはboardsって名前で渡してね。(->with([‘boards’ => $boards,]);))

と書き込む。

app/Http/Controllers/BoardController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Board;
use App\Models\Comment;

class BoardController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */

    public function __construct()
    {
        //
    }

    public function index()
    {
        $boards = Board::all()->orderBy('created_at', 'desc')->paginate(5);
        return view('bbs.index')->with([
            'boards' => $boards,
         ]);
    }
}

ビューの生成

今回は、bladeテンプレートを使用します。

{{変数}}でエスケープをいい感じにしてくれたり、@extend()でレイアウトの継承ができたりとても便利です。

今回はレイアウトにBootstrapを使いますので、継承元には読み込みを指示しておきます。

基本的に変数は{{}}内で取り扱うので、そこを注目していきましょう。

resources/views/layouts/layouts.blade.php
<html>
  <head>
    <title>@yield('title')</title>
      <link href="{{ asset('css/app.css') }}" rel="stylesheet">
      <style>
        @yield('style')
    </style>
  </head>

  <body>
    <div id="app" class="row">
        <div class="col-2"></div>
        <div class="col-8">@yield('contents')</div>
        <div class="col-2"></div>
    </div>
    <script src="{{ asset('js/app.js') }}" defer></script>
  </body>
</html>
resources/views/bbs/index.blade.php
@extends('layouts.layouts')
  @section('title', 'board.index')
  @section('contents')
    @foreach($boards as $board)
         <div class="row">
            <div class="col-md-12">
                <div class="media" style="padding:10px;"><img class="rounded-circle mr-3" style="margin:10px 0px 0px 10px;padding:0px;">
                    <div class="media-body">
                        <h5 style="margin:10px 10px 10px 0px;">{{ $board->name }} : <b>{{ $board->title }}</b></h5>
                        <small>投稿日時:{{ date("Y年 m月 d日",strtotime($board->created_at)) }}</small>
                        <div class="mt-1">
                            <p>{!! nl2br(e($board->content), false) !!}</p>
                        </div>
                        <div class="col">
                            <a class="btn btn-outline-success" data-toggle="collapse" href="#post{{ $board->id }}" role="button" aria-expanded="false" aria-controls="collapseExample">返信する</a>
                            <p>コメント数:{{ $board->comment()->count() }}件</p>
                        </div>
                        <hr>
                            <div class="collapse" id="post{{ $board->id }}">
                                <form id="commentPost" action="{{ url('/') }}/comment/{{ $board->id }}/post" method="post">
                                    {!! csrf_field() !!}
                                    <label>名前</label>
                                    <input class="form-control col col-sm-10" type="text" name="comment_user_name" value="{{ old('name') }}">
                                    <label class="d-flex" style="margin:0px 0px 15px 0px;">内容</label>
                                    <textarea class="col col-sm-10" style="height:50%;" name="comments" value="{{ old('comment') }}"></textarea>
                                    <button class="btn btn-outline-success></button>
                                </form>
                            </div>
                            @foreach( $board->comment as $comment )
                                <div class="media"><img class="rounded-circle mr-3">
                                    <div class="media-body">
                                        <h5 style="margin:10px 10px 10px 0px;">{{ $comment->comment_user_name }}</h5>
                                        <small>投稿日時{{ date("Y年 m月 d日",strtotime($comment->created_at)) }}</small>
                                        <div class="mt-1">
                                            <p>{!! nl2br(e($comment->comments), false) !!}</p>
                                        </div>
                                    </div>
                                </div>
                                <hr>
                                @if($loop->iteration == 3 && $loop->remaining >= 1)
                                    {{-- ループ回数が3回時、ループ残が1以上ある場合--}}
                                    <div class="btn btn-outline-primary ml-3"><a href="{{ url('/')}}/bbs/show/{{$board->id }}">続きを読む</a></div>
                                    @break
                                @endif
                            @endforeach
                    </div>
                </div>
            </div>
        </div>
        @endforeach
    </div>
    <nav class="row justify-content-center">
        {{ $boards->appends(Request::only('keyword'))->links() }}
    </nav>
</div>

書き込みのデータは、コントローラーにboardsに変数$boardsとして渡すように指示をしていたので、そこから取り出してあげます。

その際、配列を1つずつ取り出すなら、foreach{{$boards as $board}}で順番に取り出すことができます。

更に、内部を見ると、$boardのtitleを出したいということで、{{$board->title}}などが書けます。

コメントについても書き込みに関連付けられているので、$boardに関連付けられたcommentを、1件ずつ取り出したいという意味で、foreach($board->comment as $comment)で取り出します。

投稿内容については、 改行を含んでいますが、{{}}は改行もエスケープしてしまうので、改行だけはエスケープしないように指示を出しています。<p>{!! nl2br(e($comment->comments), false) !!}</p>

これで、一通りの表示はできますが、投稿機能はここでは実装されていません。

また別の機会に書ければと思います。

シェアする

  • このエントリーをはてなブックマークに追加

フォローする