Laravelの基本はこちらから学べます

Laravel でスレッド掲示板を作成(中級)。5 章スレッド作成機能




今回は前回に続けて開発を進めていきましょう。
前章をまだご覧になっていない方は以下から開発を始めましょう。

5 章:スレッド作成機能

MVC の復習

MVC 覚えておりますでしょうか。

油そば

MVC ? 確か、Model View Controller の先頭の文字をとった、Web フレームワークで使われる設計思想だよな。
各々がどんなな役割を担うかは忘れたな。。

そんな方は、下記の記事の「MVC のイメージをつかむ」をみてみよう。
また、もっと知りたいよ!という方は是非ググってみましょう。

では、軽く MVC を思い出したところで
重要な機能である「スレッド作成」機能を実装していきましょう。

必要ファイルの作成

それではまずは、一覧画面を作成していきましょう。

  1. コントローラー
  2. ルーティング
  3. ビュー

の順で作成していきましょう。

laradock % docker-compose exec workspace bash

/var/www# php artisan make:controller ThreadController --resource
Controller created successfully.

`–resource` とは?という方は以下のドキュメント「リソースコントローラー」を確認してみましょう。
https://readouble.com/laravel/6.x/ja/controllers.html#resource-controllers

web.php

<?php

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', function () {
    return view('welcome');
});

Auth::routes();

Route::get('/home', 'HomeController@index')->name('home');
Route::resource('/threads', 'ThreadController')->except(['create', 'update']);

新規作成画面、更新処理は行わない予定なので、ルートで処理するアクションの一部を指定しました。
ルーティングが正常に機能しているかを念の為に確認しておきましょう。

laradock % docker-compose exec workspace bash

root@b15d7b5e8dc3:/var/www# php artisan route:list

+--------+----------+------------------------+------------------+------------------------------------------------------------------------+--------------+
| Domain | Method   | URI                    | Name             | Action                                                                 | Middleware   |
+--------+----------+------------------------+------------------+------------------------------------------------------------------------+--------------+
|        | GET|HEAD | /                      |                  | Closure                                                                | web          |
|        | GET|HEAD | api/user               |                  | Closure                                                                | api,auth:api |
|        | GET|HEAD | home                   | home             | App\Http\Controllers\HomeController@index                              | web,auth     |
|        | GET|HEAD | login                  | login            | App\Http\Controllers\Auth\LoginController@showLoginForm                | web,guest    |
|        | POST     | login                  |                  | App\Http\Controllers\Auth\LoginController@login                        | web,guest    |
|        | POST     | logout                 | logout           | App\Http\Controllers\Auth\LoginController@logout                       | web          |
|        | GET|HEAD | password/confirm       | password.confirm | App\Http\Controllers\Auth\ConfirmPasswordController@showConfirmForm    | web,auth     |
|        | POST     | password/confirm       |                  | App\Http\Controllers\Auth\ConfirmPasswordController@confirm            | web,auth     |
|        | POST     | password/email         | password.email   | App\Http\Controllers\Auth\ForgotPasswordController@sendResetLinkEmail  | web          |
|        | GET|HEAD | password/reset         | password.request | App\Http\Controllers\Auth\ForgotPasswordController@showLinkRequestForm | web          |
|        | POST     | password/reset         | password.update  | App\Http\Controllers\Auth\ResetPasswordController@reset                | web          |
|        | GET|HEAD | password/reset/{token} | password.reset   | App\Http\Controllers\Auth\ResetPasswordController@showResetForm        | web          |
|        | POST     | register               |                  | App\Http\Controllers\Auth\RegisterController@register                  | web,guest    |
|        | GET|HEAD | register               | register         | App\Http\Controllers\Auth\RegisterController@showRegistrationForm      | web,guest    |
|        | GET|HEAD | threads                | threads.index    | App\Http\Controllers\ThreadController@index                            | web          |
|        | POST     | threads                | threads.store    | App\Http\Controllers\ThreadController@store                            | web          |
|        | GET|HEAD | threads/{thread}       | threads.show     | App\Http\Controllers\ThreadController@show                             | web          |
|        | DELETE   | threads/{thread}       | threads.destroy  | App\Http\Controllers\ThreadController@destroy                          | web          |
|        | GET|HEAD | threads/{thread}/edit  | threads.edit     | App\Http\Controllers\ThreadController@edit                             | web          |
+--------+----------+------------------------+------------------+------------------------------------------------------------------------+--------------+

それではコントローラーの処理を記載していきましょう。
まずは、一覧画面を表示する画面を記入していきます。

ThreadController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class ThreadController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        return view('threads.index');
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        //
    }

    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function show($id)
    {
        //
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function edit($id)
    {
        //
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function destroy($id)
    {
        //
    }
}

index メソッドに threads\index.blade.php を表示するように記載しました。
まだ、threads\index.blade.php は作成していない為、作成しましょう。

views\threads\index.blade.php

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <h5 class="card-header">新規スレッド作成</h5>
                <div class="card-body">
                    <form method="POST" action="{{ route('threads.store') }}">
                        @csrf
                        <div class="form-group">
                            <label for="thread-title">スレッドタイトル</label>
                            <input name="name" type="text" class="form-control" id="thread-title"
                                placeholder="タイトル">
                        </div>
                        <div class="form-group">
                            <label for="thread-first-content">内容</label>
                            <textarea name="content" class="form-control" id="thread-first-content" rows="3"></textarea>
                        </div>
                        <button type="submit" class="btn btn-primary">スレッド作成</button>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

Bootstrap : Card
https://getbootstrap.jp/docs/4.2/components/card/
Bootstrap : Forms
https://getbootstrap.jp/docs/4.2/components/forms/

デザインは上記の Bootstrap から拝借しています。

Form の送信先は `route(‘threads.store’)` としております。

油そば

ルーティングの名前で指定しているんだよな。
今回でいうと、App\Http\Controllers\ThreadController@store になっているな。
php artisan route:list で確認できるね。

それでは一度現状がどうなっているのかを確認してみましょう。

http://localhost/threads

質素なデザインですが、掲示板作成の画面を作ることができました。

Threads テーブルに「内容」に該当するカラムがないんでは?と思った方。正解です。
今回は、新規スレッドを立ち上げる際には「内容」(messages テーブルのレコード)を同時に作成する設計でコーディングしていきます。

FormRequest Validation

油そば

バリデーションは Form の入力チェックだよね。
名前は〇〇文字以上にしてください、とかメアドはメールアドレスの形式で入力されていますか、とか。

そうですね。
バリデーションは Controller に記入しても良いのですが、
コントローラーが Fat(太る) になってしまいます。

FatController はコードが冗長になり、読みにくいコードになってしまうことが多いです。
バリデーションは FormRequest に切り出しましょう。

フォームリクエストバリデーション
https://readouble.com/laravel/6.x/ja/validation.html#form-request-validation

では作成していきます。

laradock $ docker-compose exec --user=laradock workspace bash

/var/www# $ php artisan make:request ThreadRequest
Requests\ThreadRequest.php

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class ThreadRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'name' => 'required',
            'content' => 'required|min:10',
        ];
    }

    public function messages()
    {
        return [
            'name.required'  => trans('validation.required'),
            'content.required'  => trans('validation.required'),
            'content.min'  => trans('validation.min')
        ];
    }
}

実際に Form から送信された入力内容を作成した FormRequest でチェックするようにしましょう。

ThreadController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Http\Requests\ThreadRequest;

class ThreadController extends Controller
{
    // 略

    /**
     * Store a newly created resource in storage.
     *
     * @param  App\Http\Requests\ThreadRequest  $request
     * @return \Illuminate\Http\Response
     */
    public function store(ThreadRequest $request)
    {
        //
    }

    // 略

こちらで store メソッドの処理の前にユーザーからのリクエスト内容を ThreadRequest でチェックできるようになりました。
バリデーションに引っかかった際のメッセージを表示するコードを追加していきましょう。

views\threads\index.blade.php

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            @include('layouts.flash-message')
            <div class="card">
        
// 略
油そば

@include でフラッシュメッセージを表示する blade を読み込んでいるのか。
フラッシュメッセージを表示するコンポーネントは他の blade にも使いそうだもんな。

使い回せるものは、出来る限りコンポーネントにしておくと便利ですね。
では実際に使い回すコンポーネントを作成していきましょう。

views\layouts\flash-message.blade.php

@if ($message = Session::get('success'))
<div class="alert alert-success alert-block">
    <button type="button" class="close" data-dismiss="alert">×</button>
    <strong>{{ $message }}</strong>
</div>
@endif

@if ($message = Session::get('error'))
<div class="alert alert-danger alert-block">
    <button type="button" class="close" data-dismiss="alert">×</button>
    <strong>{{ $message }}</strong>
</div>
@endif

@if ($message = Session::get('warning'))
<div class="alert alert-warning alert-block">
    <button type="button" class="close" data-dismiss="alert">×</button>
    <strong>{{ $message }}</strong>
</div>
@endif

@if ($message = Session::get('info'))
<div class="alert alert-info alert-block">
    <button type="button" class="close" data-dismiss="alert">×</button>
    <strong>{{ $message }}</strong>
</div>
@endif

@if (count($errors) > 0)
<div class="alert alert-danger alert-block">
    <button type="button" class="close" data-dismiss="alert">×</button>
    @foreach ($errors->all() as $error)
    <strong>{{ $error }}</strong>
    @endforeach
</div>
@endif
油そば

Session::get(‘success’) 〜 Session::get(‘info’) までで、Bootstrap を使って 4種類のメッセージ表示を可能にしているな。
最後のブロックでは、バリデーションエラーの為のものだな。

ここまでで、実際に入力がバリデーションに引っかかった際のテストをしてみましょう。
また、バリデーションルールにはたくさん種類がありますので、目を通しておきましょう。
https://readouble.com/laravel/6.x/ja/validation.html#collection-method-list2

保存処理

それでは、バリデーションの処理が完了しましたので実際に保存処理を書いていきましょう。

DB の処理を記載していきますので、まずはモデルを作成していきます。
Thread モデル、Message モデルを作成していきます。

Eloquent : 利用の開始
https://readouble.com/laravel/6.x/ja/eloquent.html

laradock % docker-compose exec workspace bash

/var/www# php artisan make:model Thread
/var/www# php artisan make:model Message
Model created successfully.
Thread.php

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Thread extends Model
{
    /**
     * The table associated with the model.
     *
     * @var string
     */
    protected $table = 'threads';

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name', 'user_id', 'is_user_checked', 'latest_comment_time'
    ];
}
Message.php

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Message extends Model
{
    /**
     * The table associated with the model.
     *
     * @var string
     */
    protected $table = 'messages';

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'body', 'thread_id', 'user_id'
    ];
}

モデルを通してデータベースからデータを取得、保存、更新、削除出来るようになりましたので、実際に保存処理を記入していきましょう。

保存処理できそう!という方はぜひ挑戦してみてくださいね。

油そば

これは前回の Laravel 掲示板でもやったから、挑戦してみるぞ。
前回の記事はこちら。
https://aburasoba.org/laravel6-board-app-1/

ThreadController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Http\Requests\ThreadRequest;
use App\Message;
use App\Thread;
use Carbon\Carbon;
use Illuminate\Support\Facades\Auth;

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

    // 略

    /**
     * Store a newly created resource in storage.
     *
     * @param  App\Http\Requests\ThreadRequest $request
     * @return \Illuminate\Http\Response
     */
    public function store(ThreadRequest $request)
    {
        // save Thread
        $thread = new Thread();
        $thread->name = $request->name;
        $thread->user_id = Auth::id();
        $thread->latest_comment_time = Carbon::now();
        $thread->save();

        // save Message
        $message = new Message();
        $message->body = $request->content;
        $message->user_id = Auth::id();
        $message->thread_id = $thread->id;
        $message->save();

        // redirect to index method
        return redirect()->route('threads.index')->with('success', 'スレッドの新規作成が完了しました。');
    }
油そば

できたぞ。
ポイントは

① middleware をつけた。
Message, Thread には user_id が必要でしょ。ログインしているユーザーだけが store メソッドにアクセス出来るようにした。

② Carbon クラスを使った。
Thread テーブルの latest_comment_time に日時を保存するために Carbon クラスを使ったよ。

③ redirect で with を使いフラッシュメッセージを表示した。
先ほど作成した、flash-message.blade.php を使う為に with でセッションに「success」という key で「スレッドの新規作成が完了しました。」という value を保存した。これで保存ができたらフラッシュメッセージが表示出来る!

ログインした状態で、入力して、保存。。。。

保存できていますね。素晴らしい。

ですが、まだまだ修正する場所はありそうですね。
これは次の章で確認していきましょう!

それでは、先に動作確認として、MySQL で保存されているかを確認していきましょう。

Laradock の MySQL コンテナに接続

では、Laradock で立ち上げている MySQK コンテナに接続して、先ほどのスレッド情報が保存できているかを確認していきましょう。

それでは ターミナルでコマンドを打ち込んでいきます。

laradock tt% docker-compose exec mysql bash

root@bf402a279a73:/# mysql -u default -proot
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 4
Server version: 5.7.28 MySQL Community Server (GPL)

Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> 
油そば

お、 MAMP の MySQL で見たことがある画面になったぞ。ちなみに
-u default -proot の「default」と「root」はどこから出てきたんだ?

良い指摘ですね。
これらは、「第二章:環境構築 Laradock を使ってみる」で記載したコードを元に指定しています。
もう一度確認してみましょう

以下は Laradock の .env です。(2ch の .env ではありません)

.env

### MYSQL #################################################

MYSQL_VERSION=5.7
MYSQL_DATABASE=2ch // DB の名前を 2ch にしています。
MYSQL_USER=default // default という user を MySQL に作ります。
MYSQL_PASSWORD=root // パスワードは root にしています(覚えやすいので)。
MYSQL_PORT=3306
MYSQL_ROOT_PASSWORD=root
MYSQL_ENTRYPOINT_INITDB=./mysql/docker-entrypoint-initdb.d

思い出しましたでしょうか。
こちらで、データベースの名前、ユーザー、パスワードを作成しました。
よって、default と root で MySQL にログインできたことがお分かりいただけたと思います。

では、続きをやっていきましょう

mysql> use 2ch
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> select * from threads;
+----+---------+-----------+-----------------+---------------------+---------------------+---------------------+
| id | user_id | name      | is_user_checked | latest_comment_time | created_at          | updated_at          |
+----+---------+-----------+-----------------+---------------------+---------------------+---------------------+
|  1 |       1 | testtestt |               0 | 2021-04-18 10:09:13 | 2021-04-18 10:09:13 | 2021-04-18 10:09:13 |
+----+---------+-----------+-----------------+---------------------+---------------------+---------------------+
1 row in set (0.00 sec)

いかがでしょうか。上記で、先ほど入力した thread の名前のデータが保存されているはずです。

ここまでで、スレッド作成機能の実装が完了しました。
次の章で、先ほどのコードのリファクタリングを行っていきます。

5 章での変更点は以下からご確認いただけます。
https://github.com/t-aburasoba/thread-board/pull/4/files

もしこの記事をいいねと思ったたり、ご参考になりましたら下記ボタンからサポートしていただけますと、とても励みになります!




コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です