Laravel QUEST #3【Router/Controller/Viewの構築・ユーザ登録機能の完成】

前回は、モデルについて学習してきました。

Modelは、データベースとアプリケーションの連携をする上で、欠かせない存在であることを理解して頂けたと思います。
重要な要素であるだけに、今回学習するその他の要素:Router/Controller/Viewと比べると独立した存在であるといえます。

逆に、今回学習する3つの要素は、単体として独立しているModelのような存在とは違い、3つがそれぞれ相互に影響を与え合うように機能します。
なので、3要素それぞれの関係性に特に着目しながら、学習を進めていきましょう。

●今回のキーポイント

  • Bladeファイルの記述方法
  • 共通部分をまとめる
  • 名前空間
  • トレイトについて
  • バリデーション
  • LaravelCollective導入
  • 新規ユーザ登録機能の完成

前回の内容はこちら

https://prog.quest-academia.com/laravel-quest-2

Router/Controller/Viewの関係

講義#1のMVC(+Router)の図を見て、MVC(+Router)それぞれの振り返りと同時に
Model以外の要素について深堀りしていきましょう。

Routerは、ブラウザから来たリクエストを「どの処理(Controllerなど)へ送るか」を判断する交通整理的な役割を果たすものでした。
このRouterのルーティング機能がなければ、処理自体が始まりません。

Controllerは、Laravelにおける実行係でした。
Routerによって指定されたControllerが指定されたアクションを実行します。
アクションとはController内の関数のことで、Controllerが複数のアクション(関数)を所有していることもあります。
Routerに指定されたControllerが特定のアクションを動かし、Modelを操作して、新規ユーザ作成や保存処理などを行うのです。

View表示機能を担うものでした。端的にいうとViewはWebページそのものです。
Controllerが処理した結果をもとに、Webページを出力して最終的にユーザに見せる役割です。Viewがないとユーザは何も情報を取得できないことになります。

トップページをつくろう

ではまず、トップページから作成していきたいと思います。

ユーザごとの登録・ログイン機能を設けていますが、ログインする前のトップページに必要なのは、あくまで「表示させること」だけです。
なので、ViewとRouterのみをいじるだけで良く、ControllerやModelを利用しなくてもOKです。

Router:トップページを表示する

早速、Routerを確認していきましょう。

現状の交通整理を担うRouterは、下記の通り書かれております。

laravel-quest > routes > web.php

web.php

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

Route::getは「GET(取得)メソッドでルーティング(命令の行き先)を指定する」という意味です。

ルーティングの流れとしては、ユーザがトップページ’/’へのアクセスを試みると、view(‘welcome’)が返されることになります。


view()関数を使ってview(‘welcome’)と書かれていますが、「Viewのwelcome.blade.phpというファイルを表示させなさい」という命令が実行されるということです。
(「.blade.php」はViewファイルの拡張子を示しますが、後ほど説明します)
つまり’welcome’は「.blade.php」というファイルの拡張子を短縮して表現していることになります。

要するに、用意されているview()関数を使えば、簡単に表示させたいViewファイルを表示させることができるということです。

また、通常であれば、Router → Controller → View という順序に命令が流れていきますが、
今回の場合は Router → View というControllerを省略した命令経路となります。

これは、RouterがControllerの処理部分を代わりに行ってしまっているともいえるのですが、
トップページの表示に限っては「Webページを表示する」という以外の処理を行っていない単純な命令のため、
Controllerを省いた経路を辿ることになっても構わないのです。

ここでは、Route::getとして、GETメソッドのみ解説しましたが、
それ以外のメソッドも指定できますので、後程詳しく内容を解説します。

View:共通部分のレイアウト

Viewは、上で見た通り表示機能を担っています。基本的にはHTMLを表すと思って頂ければ良いかと思います。
ただ、表記方法が通常のHTMLとやや異なり、View上のPHPの記述(<?php echo ○○; ?>など)を簡単にできるという利点があります。

これは、LaravelがViewに書かれたコードを変換して、HTMLを生成してくれているからです。
このLaravelの変換処理を行う部分を「テンプレートエンジン」といい、それが「Blade(ブレード)」と呼ばれる、Viewファイルの拡張子になります。
変換する元になるViewファイルには、「.blade.php」という拡張子が必要となり、この拡張子がなければHTMLに変換する処理が行われません。

では、早速Viewを作成していきましょう。

はじめに、共通する部分だけを取り出してファイル化していきます。
これは、同じ処理を複数ある各Viewファイルに書かなくて済むようにするためです。

例えば、タグなどを書き換えることになった場合、タグが他のViewファイルにも書かれていれば、それぞれのファイルにいちいち手を加えて書き換えなければなりません。
そうならないように、共通する部分はそれぞれファイルにまとめてしまいましょう。

下記のlayoutsフォルダを作り、その中にapp.blade.phpを作りましょう。

laravel-quest > resources > views > layouts > app.blade.php

app.blade.php

<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <title>YouTubeまとめ×SNS</title>
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
    </head>
    <body>
        @include('commons.header')
        <div class="container">
            @include('commons.error_messages')
            @yield('content')
        </div>
        @include('commons.footer')
        <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
        <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
        <script defer src="https://use.fontawesome.com/releases/v5.7.2/js/all.js"></script>
    </body>
</html>

このコードは、全てのページに反映されるべき内容です。つまり、各ページのレイアウト表示のために必須となってきます。

上記記述を共通化してレイアウト表示させるのですが、
共通化のためには@includeなどの記述で他のViewの呼び出しが必要です。

上記コードのうち、特に注目すべきは下記の部分です。

app.blade.php(※一部抜粋)

@include('commons.header')
<div class="container">
  @include('commons.error_messages')
  @yield('content')
</div>
@include('commons.footer')

@include(‘commons.header’)で、「commonsフォルダにある、header.blade.phpを呼び出す」という意味です。
@include(‘commons.error_messages’)も同様で、「commonsフォルダにある、error_messages.blade.phpを呼び出す」という意味ですね。
もちろん、@include(‘commons.footer’)も同様に考えて頂ければOKです。

また、@yield(‘content’)は、「ここに各ページの情報(コンテンツ)をはめ込みます」という宣言の記述です。

後で見ていきますが、welcome.blade.phpなどは、@extends(‘layouts.app’)や@section(‘content’)〜@endsectionという記述で共通化すべきレイアウトを呼び出します。
つまり、@yeildは後で出てくる@sectionの配置場所を示すんだということを覚えておいて下さい。

View:エラーメッセージ

次はエラーメッセージを表示させるファイルを作っていきます。

下記フォルダcommonsを作り、その中にerror_messages.blade.phpを作成し、下記コードを記述して下さい。


コードを要約すると「もしもエラー($errors)があったら、1つずつ表示します」という処理内容になります。

こちらも後ほど説明しますが、後述するバリデーションを定義すれば、$errors変数にエラーの値が代入されることになるのです。

laravel-quest > resources > views > commons > error_messages.blade.php

error_messages.blade.php

@if (count($errors) > 0)
    <ul class="alert alert-danger">
        @foreach ($errors->all() as $error)
            <li class="ml-4">{{ $error }}</li>
        @endforeach
    </ul>
@endif

{{ $error }} という変数の記述がありますが、これは、<?php echo $error; ?>とほぼ同じ意味となります。
テンプレートエンジンが{{ $error }}を処理して、HTMLへ変換してくれるのでこのような短縮した記述が可能なのです。つまり、{{ $変数 }}という形で、変数をViewの中に簡単に埋め込めます。

さらに、{{ }}の形を取ると、{{ }}の中の値をエスケープ処理してくれます。
エスケープ処理とは、万一{{ }}の中の値にHTML・JavaScriptなどのコードが入れ込まれても、コードをテキストとして表示してくれ、HTML・JavaScriptなどのコードとしては処理されないということです。
例えば、悪意のあるユーザが意図的に{{ }}の中の変数などに、他のユーザに悪影響を与えるコードを仕込んだ場合でも、その処理は実行されませんので安心です。
万一のことを考え、変数などを表示する場合は、原則この{{ }}でエスケープ処理を行って表示するようにしましょう。

Viewでは、if文・foreach文なども簡単に表記することができます。
記述方法は、下記の通りです。

if文 ※サンプルにつき記述不要です

@if(  条件①  )
   出力内容①
@else(  条件②  )
   出力内容②
@endif

for文 ※サンプルにつき記述不要です

@for(  初期値 ; 条件(最大値など) ; 増減式 )
   繰り返す出力内容
@endfor

foreach文 ※サンプルにつき記述不要です

@foreach(  $配列 as $変数 )
   繰り返す出力内容
@endforeach

上記コードでは、if文とforeach文を利用してエラーがあった場合に表示させるよう、処理を書いています。

View:ヘッダー/フッター

画面の上のメニューに当たるヘッダー、画面下部に表示すべきフッターをそれぞれ作っていきましょう。

header.blade.phpとfooter.blade.phpを作成し、下記コードを記述して下さい。

laravel-quest > resources > views > commons > header.blade.php

header.blade.php

<header class="mb-5">
    <nav class="navbar navbar-expand-lg navbar-light bg-light">
        <a class="navbar-brand" href="/">YouTube-Curation</a>
        <button type="button" class="navbar-toggler" data-toggle="collapse" data-target="#nav-bar">
            <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="nav-bar">
            <ul class="navbar-nav mr-auto"></ul>
            <ul class="navbar-nav">
                <li class="nav-item"><a href="" class="nav-link">新規ユーザ登録</a></li>
                <li class="nav-item"><a href="" class="nav-link">ログイン</a></li>
            </ul>
        </div>
    </nav>
</header>

laravel-quest > resources > views > commons > footer.blade.php

<footer class="mt-5">
        <nav class="navbar navbar-light bg-light justify-content-center">
                <div class="navbar-brand m-0">© QuestAcademia, All rights reserved.</div>
        </nav>
</footer>

View:welcome.blade.php

welcome.blade.phpがトップページに当たり、すでに下記コードが記述されています。
現時点で一度トップページを表示してみると、Laravelプロジェクトのトップページが表示されると思いますが、その内容を記述しているのがこの部分です。

laravel-quest > resources > views > welcome.blade.php

welcome.blade.php(一部抜粋)

<body>
    <div class="flex-center position-ref full-height">
        @if (Route::has('login'))
            <div class="top-right links">
                @auth
                    <a href="{{ url('/home') }}">Home</a>
                @else
                    <a href="{{ route('login') }}">Login</a>
                    <a href="{{ route('register') }}">Register</a>
                @endauth
            </div>
        @endif
        <div class="content">
            <div class="title m-b-md">
                Laravel
            </div>
            <div class="links">
                <a href="https://laravel.com/docs">Documentation</a>
                <a href="https://laracasts.com">Laracasts</a>
                <a href="https://laravel-news.com">News</a>
                <a href="https://forge.laravel.com">Forge</a>
                <a href="https://github.com/laravel/laravel">GitHub</a>
            </div>
        </div>
    </div>
</body>

上記トップページの内容は、全て消去していただき、下記のコードを記述してください。

welcome.blade.php

@extends('layouts.app')
@section('content')
    <div class="center jumbotron bg-warning">
        <div class="text-center text-white">
            <h1>YouTubeまとめ × SNS</h1>
        </div>
    </div>
@endsection

ここで、上で解説していた@extendsと@sectionが出てきたと思います。

まず、@extends(‘layouts.app’) で共通化しているレイアウト:layoutsフォルダにある app.blade.phpを呼び出しています

次に@section(‘content’)〜@endsection の中に書かれた内容を、上で解説していたapp.blade.phpの@yield(‘content’)部分にはめ込むという処理を行います。

これらの記述により、app.blade.phpを共通化することが可能なのです。

さて、これでトップページが完成しましたので、表示されるかテストしてみましょう。
上手く動けば、下記のように表示されるはずです。

新規ユーザ登録機能をつくろう

トップページの準備が完了したら、新規ユーザ登録機能の実装に入りましょう。

Router:新規ユーザ登録ページ表示/登録処理

新規ユーザ登録するための専用のControllerもLaravelには用意されています。
下記のファイルです。

laravel-quest > app > Http > Controllers > Auth > RegisterController.php

RegisterControllerについては、後程説明します。

まず、ルータを下記の様に変えましょう。

laravel-quest > routes > web.php

web.php(以下を追記して下さい)

// ユーザ登録
Route::get('signup', 'Auth\RegisterController@showRegistrationForm')->name('signup');
Route::post('signup', 'Auth\RegisterController@register')->name('signup.post');

->name() という記述により、ルーティングに命名して後で呼び出しやすくしています。
後で、Viewファイルでこの名前を利用することになります。

基本の4つのルーティング(アクション)

次は、Route::getやRoute::getの部分について学んでいきましょう。
Route::getは「GETメソッドでルーティング(命令の行き先)を指定する」という意味でした。

このGETを含めて、ルーティングの基本となる4つのメソッドがあります。
ここでは、少し「新規ユーザ登録機能」の開発からは離れて、この4つのルーティング(アクション)について学んでいきます。

それは、HTTPリクエストとして存在するメソッドで、POST, GET, PUT, DELETEの4つです。
システムの主要機能である4機能(Create,Read,Update,Delete)の頭文字を取って、CRUD処理とも呼ばれます。

それぞれ、言葉はやや異なりますが、下記のように対応します。

  • GETメソッド (Read)
  • POSTメソッド (Create)
  • PUTメソッド (Update)
  • DELETEメソッド (Delete)

4つのメソッド(処理)をそれぞれ行うとすれば、どのような記述になるでしょうか?
以下に例を書きます。

web.php ※サンプルコードにつき入力不要です

//各動画の詳細ページを表示する
Route::get('movies/{id}', 'MoviesController@show');
//動画を新規登録する(登録画面とは別です)
Route::post('movies', 'MoviesController@store');
//動画を更新する(編集画面とは別です)
Route::put('movies/{id}', 'MoviesController@update');
//動画を削除する
Route::delete('movies/{id}', 'MoviesController@destroy');

ルートの記述の基本は、このような形になります。

※サンプルコードにつき入力不要です

Route::get('アドレス(○○/{パラメータ})', '関数 or コントローラ名@アクション名');

第一引数には「アドレス」を、第二引数には処理:「関数」もしくは「コントローラ名@アクション名」を記載します。

上で解説した「トップページを表示するだけ」などの単純な処理でない場合、原則コントローラを経由させて処理を実行します。

アクション名は4つ show, store, update, destroy を書いています。
アクション名は任意の文字を指定できるので、絶対にこれでないといけない訳ではありませんが
基本のアクションを覚えるためにも、最初は上記の4つのアクション名+後程説明する残り3つのアクション名を使うようにしましょう。

アドレスのうちの{パラメータ}についても少し解説します。
{パラメータ}は、原則show, update, destroyアクションの場合に用いられるものです。

例えば、上記の例でshowアクションで考えると、取得したいmovieが分かっていないと、どのmovieを表示すればいいのか分かりません。
なので、下記のURLの例のように、{id}に1など具体的な{パラメータ}を指定して表示させる必要があります。

URL例)https://●●●●/movies/1

showと同時に、update, destroyアクションも同様です。
特定の{id}が指定されて初めて、その{id}と一致するmovieを更新したり、削除したりできます。


storeアクションで{パラメータ}が不要なのは、storeアクション自体がmovieを新規作成するアクションだからです。新しくレコードを作るので、まだidが発行される前の状態なので、既存の{id}を指定する必要はありません。

その他3つのルーティング(アクション)

先ほども少しだけ触れていました、残り3つのアクション index, create, edit に関しても解説していきます。

web.php ※サンプルコードにつき入力不要です

//動画一覧ページを表示する
Route::get('movies', 'MoviesController@index');
//動画の新規登録フォームを表示する
Route::get('movies/create', 'MoviesController@create');
//動画の編集フォームを表示する
Route::get('messages/{id}/edit', 'MoviesController@edit');

それぞれ確認していきましょう。

indexアクション

まず、一覧ページを表示するindexアクションです。なぜindexアクションが必要なのでしょうか。
これは、各動画の詳細ページを表示するshowアクションと関わっています。各動画の詳細ページを表示したい場合、動画一覧ページがないと詳細ページに辿り着くことすらできません。
YouTube動画を観る際も同様で、複数の動画が一覧で表示されているページから、個々の動画を選び、その動画の詳細ページに行って閲覧するはずです。
つまり、showアクションを実行して詳細ページを表示するためにも、indexアクションは必要なのです。

createアクション

新規登録フォーム表示に当たるcreateアクションは、動画の新規登録処理を行うstoreアクションと関わっており、新規登録を行うための入り口としてのフォームを表示させる役割です。
つまり、storeアクションを実行して新規登録処理を行うために、createアクションは必要となります。

editアクション

編集フォーム表示に当たるeditアクションは、動画情報の更新を行うupdateアクションと関わっており、動画情報の編集を行うための入り口としてのフォームを表示させる役割です。
つまり、updateアクションを実行して更新処理を行うために、editアクションは必要となるわけです。

以上が残り3つのアクションの解説です。
ここまできて、
showアクション, storeアクション, updateアクション に対応するそれぞれのアクション
を見てきたけど、destroyアクションに対応するものはないの?

と疑問に思われた方もいると思いますので、少し解説します。

destroyアクションに対応するアクションを作ってもいいのですが、destroyアクションは上記3つのアクションに比べると「消去するだけ」という単純な機能のため、通常「入力フォーム」のようなものは必要とされません。
なので、消去専用のフォーム画面を表示させる必要はなく、「消去ボタン」をViewのどこかに用意してやれば十分なのです。

Controller:RegisterController

次は、コントローラの実装に入りますが、まずはRegisterControllerをみていきましょう。
下記のフォルダにすでに存在します。

こちらのRegisterControllerは、名前の通り、新規ユーザ登録処理を担当します。

laravel-quest > app > Http > Controllers > Auth > RegisterController.php

RegisterController.php(一部抜粋)

<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use App\User;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
class RegisterController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Register Controller
    |--------------------------------------------------------------------------
    |
    | This controller handles the registration of new users as well as their
    | validation and creation. By default this controller uses a trait to
    | provide this functionality without requiring any additional code.
    |
    */
    use RegistersUsers;
    /**
     * Where to redirect users after registration.
     *
     * @var string
     */
    protected $redirectTo = RouteServiceProvider::HOME;
    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest');
    }
    /**
     * Get a validator for an incoming registration request.
     *
     * @param  array  $data
     * @return \Illuminate\Contracts\Validation\Validator
     */
    protected function validator(array $data)
    {
        return Validator::make($data, [
            'name' => ['required', 'string', 'max:255'],
            'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
            'password' => ['required', 'string', 'min:8', 'confirmed'],
        ]);
    }
    /**
     * Create a new user instance after a valid registration.
     *
     * @param  array  $data
     * @return \App\User
     */
    protected function create(array $data)
    {
        return User::create([
            'name' => $data['name'],
            'email' => $data['email'],
            'password' => Hash::make($data['password']),
        ]);
    }
}

Controller:名前空間について

RegisterController.php(一部抜粋)

namespace App\Http\Controllers\Auth;

という記述がRegisterController.phpの上の方にありますが、
こちらは「名前空間」と呼ばれるものを示しています。

Userモデルもそうでしたが、コントローラも「class(設計図)」として作成されます。
RegisterControllerは、クラスとしてApp\Http\Controllers\Authという名前空間に設置されます。

・RegisterControllerクラスの名前空間
 App\Http\Controllers\Auth

ここでお聞きしたいのですが、上記のようなフォルダ構成を見たことはないでしょうか?

考えてみると、RegisterController.phpのファイル自体が、名前空間と同様、下記フォルダの中に入っているのでした。

・RegisterController.phpのファイルの場所
 app > Http > Controllers > Auth

名前空間は、クラスを置いておく場所を示し、フォルダと同様の階層構造で成り立っています。

なぜ、名前空間というものを使うのでしょうか。
このLaravelプロジェクト自体もそうですが、これから他人の作ったライブラリを導入しながら開発を進めることになります


その場合に、万一クラス名が重複してしまうと、コンピュータがどちらのクラスを読み込んでいいか分からなくなってしまい、処理が正常に機能しない可能性が高くなります。
このような「クラス名の重複」を防ぐためにも、名前空間によって「どのクラス名を指しているのか」を明示する必要があるのです。

「どのクラスを指しているか」を誰が見ても分かりやすく明示するために、
原則として上記のように「コントローラファイルのフォルダ構成」と、名前空間を一致させて使うようにしましょう。

Controller:その他、登場するクラスの名前空間

namespace の後には、次のように書かれていると思います。

RegisterController.php(一部抜粋)

use App\User;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Validator;
use Illuminate\Foundation\Auth\RegistersUsers; 

こちらは、RegisterControllerの中に登場させたい他のクラスの名前空間を指定することで
使えるようにしています。

例えば、App\Userで、Userクラスを使えるようにしていて
App\Http\Controllers\Controllerで、Controllerクラスをいつでも使える状態にしています。

下記のような記述がありますが、

class RegisterController extends Controller

もしこのuse文がなければ、Controllerを使いたい場合、毎回下記のように記述しなければならず、記述が長くなってしまうことになるのです。

class RegisterController extends App\Http\Controllers\Controller

また、変数や関数の前に付いている下記の表記があります。

・public
・protected

これらは、アクセス権限を意味する記述です。
public は、その「変数や関数にどこからでもアクセスできる」ことを意味します。
protected は、「クラスの継承関係にあるクラスでのみアクセスできる」という意味です。

クラスの継承とは、RegisterControllerで言うと、クラス定義の下記部分で記述されており、

class RegisterController extends Controller

このRegisterControllerクラスは、Controllerクラスを継承します
という意味の宣言になります。

継承を行うと、Controllerクラスに記述されている変数や関数を、このRegisterControllerクラス内で扱えるので、Controllerで書いたのと同じ変数や関数をRegisterControllerでも書くという手間が減るメリットがあります。

Controller:トレイトについて

新規ユーザ登録機能実装のために、ルータにて、下記のように記述しました。

web.php(一部抜粋)

Route::get('signup', 'Auth\RegisterController@showRegistrationForm')->name('signup');
Route::post('signup', 'Auth\RegisterController@register')->name('signup.post');

ここでRegisterControllerをみると、showRegistrationFormアクションと registerアクションがどこにもないことに気づくと思います。

ここで登場するのが「トレイト」という機能です。トレイトとは、関数をまとめて定義したものです。

use RegistersUsers というのが「トレイトを使う」ということを表しています。
use RegistersUsers という記述がクラス内に存在し、この記述によってshowRegistrationFormアクションと registerアクションをクラス内に存在するアクションとして実行できます。

つまり、RegistersUsersの中に上記2つのアクションが存在し、それをトレイトとして
use RegistersUsers という記述により、RegisterControllerクラス内に引っ張ってきているということです。
以下のソースコードを見ていただくと、RegistersUsers トレイトの中身が確認できます。
https://github.com/laravel/framework/blob/6.x/src/Illuminate/Foundation/Auth/RegistersUsers.php

Controller:ミドルウェア

また、以下のような記述があると思います。

RegisterController.php(一部抜粋)

public function __construct()
{
    $this->middleware('guest');
}

__construct()は初期化を行う関数で、ミドルウェアは「処理がコントローラに渡る前に確認される条件」 と捉えて頂ければいいかと思います。

上記コードの意味するところは、「この処理を行うユーザは、必ずゲストでないといけない」ということです。
つまり、ログイン認証を通っていないユーザが利用した場合、次の処理(新規ユーザ登録)に移れるという意味です。
もしユーザが「ゲストでなく、ログイン認証を通っている」場合、別のページへ自動的に飛ばされる(リダイレクトされる)ことになります。

逆に、新規ユーザ登録が正常に行われた後は、リダイレクト先ページに自動で飛ぶようになります。
そのリダイレクト先を指定しているのが、__construct()関数の前に記述されている$redirectTo 変数です。

RegisterController.php(一部抜粋)

/**
 * Where to redirect users after registration.
 *
 * @var string
 */
protected $redirectTo = RouteServiceProvider::HOME;

上記を以下のように変更して下さい。
変更することにより、新規ユーザ登録が終われば、自動的にトップページに遷移することになります。

RegisterController.php(一部抜粋)

/**
 * Where to redirect users after registration.
 *
 * @var string
 */
protected $redirectTo = '/';

Controller:バリデーション(検証)

validator()は、例えば「文字数の上限を超えてフォームに入力する」などの行為を制限するために用いられる処理です。
つまり、入力フォームを検証して、入力された値が適切なものかどうかを判断して、エラーメッセージを表示させます。

RegisterController.php(一部抜粋)

return Validator::make($data, [
            'name' => ['required', 'string', 'max:255'],
            'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
            'password' => ['required', 'string', 'min:8', 'confirmed'],
        ]);

ここで判定されているのは、以下の項目です。

  • 名前 空欄なら× 文字列以外× 文字数255文字上限
  • メールアドレス 空欄なら× 文字列以外× メールの形式(○○○@△△△.comなど)以外× 文字数255文字上限 他ユーザとの重複アドレス×
  • パスワード 空欄なら× 文字列以外× 文字数8文字以上 パスワード欄とパスワード確認欄の一致判定

RegistersUsers トレイトの registerアクションにも、validator()の記載があり、この処理を呼び出している形になっています。https://github.com/laravel/framework/blob/6.x/src/Illuminate/Foundation/Auth/RegistersUsers.php

Controller:create()

RegisterControllerで使われているcreate()は、Userモデルを新たに作る処理となります。
validator()と同じく、RegistersUsers トレイトの registerアクションにも、create()の記載があり、この処理を呼び出している形になっています。

View:LaravelCollectiveの導入

では、新規ユーザ登録に関わる、Viewまわりのコードも記述していきましょう。
最初に、入力フォームのセキュリティ性を高めてくれ、リンクを作成することも可能な外部ライブラリLaravelCollectiveを導入しましょう。

ターミナル

$ composer require "laravelcollective/html":"6.*"

インストールする途中、下記のような警告文が出てくる場合があると思います。

Package phpunit/phpunit-mock-objects is abandoned, you should avoid using it. No replacement was suggested.

ダミーのオブジェクト(モックオブジェクト)を使ったテストを実施する場合は、
phpunit-mock-objectsが必要なのですが、本講義では使用しませんので、上記注意文は気にして頂かなくても大丈夫です。

(警告文が出ても)最終的にLaravelCollectiveのインストールに成功すればOKです。

View:新規ユーザ登録フォーム(ページ)

ここで、改めてRegistersUsersを見てください(下記リンク)。
https://github.com/laravel/framework/blob/6.x/src/Illuminate/Foundation/Auth/RegistersUsers.php

さらに、showRegistrationFormアクションに注目すると、処理としては非常に単純な下記のコードが書かれていることが分かると思います。

showRegistrationFormアクション

return view('auth.register');

これが意味するところは、「authフォルダにある、register.blade.phpというViewファイルを表示させる」という処理なので、Router → RegisterController の showRegistrationFormアクションへと命令が伝達されると、下記ファイルが表示されることになります。

laravel-quest > resources > views > auth > register.blade.php

ですが、現在はこれらのフォルダもファイルも存在しませんので、当然ながら処理は実行されません。
なので、まずはauthフォルダを作成して、その中にregister.blade.phpを作りましょう。

authフォルダができたら、register.blade.phpを下記の内容で書いてください。

laravel-quest > resources > views > auth > register.blade.php

register.blade.php

@extends('layouts.app')
@section('content')
    <div class="center jumbotron bg-warning">
        <div class="text-center text-white">
            <h1>YouTubeまとめ × SNS</h1>
        </div>
    </div>
    <div class="text-center">
        <h3 class="login_title text-left d-inline-block mt-5">新規ユーザー登録</h3>
    </div>
    <div class="row mt-5 mb-5">
        <div class="col-sm-6 offset-sm-3">
            {!! Form::open(['route' => 'signup.post']) !!}
                <div class="form-group">
                    {!! Form::label('name', '名前') !!}
                    {!! Form::text('name', old('name'), ['class' => 'form-control']) !!}
                </div>
                <div class="form-group">
                    {!! Form::label('email', 'メールアドレス') !!}
                    {!! Form::email('email', old('email'), ['class' => 'form-control']) !!}
                </div>
                <div class="form-group">
                    {!! Form::label('password', 'パスワード') !!}
                    {!! Form::password('password', ['class' => 'form-control']) !!}
                </div>
                <div class="form-group">
                    {!! Form::label('password_confirmation', 'パスワード確認') !!}
                    {!! Form::password('password_confirmation', ['class' => 'form-control']) !!}
                </div>
                {!! Form::submit('新規登録', ['class' => 'btn btn-primary mt-2']) !!}
            {!! Form::close() !!}
        </div>
    </div>
@endsection

先程の解説では、変数などを{{ }}で囲って記述していたのですが、ここでは {!! !!} と記述されていると思います。

LaravelCollectiveによるコードを記述するときは{!! !!} で囲わないといけないのです。
なぜなら、LaravelCollectiveによるコードを、{{ }}(htmlspecialchars関数)で囲って記述してしまうと、エスケープ処理がなされてしまい
LaravelCollectiveがつくるHTMLコードがそのままページ上に表出してしまうからです

また、Laravel Collective関数は、渡されたデータの無害化処理も行ってくれますので、悪意あるHTMLを生成することもありません。
ただ、Laravel Collectiveの場合は例外として、Viewでは原則的には{{ }}で囲みましょう。

次に、Laravel Collective 関数がどのように記述されているかを見ていきましょう。

register.blade.php(※一部抜粋)

{!! Form::open(['route' => 'signup.post']) !!}
// 中略
{!! Form::close() !!}

Form::open() がフォーム開始を表し、 Form::close() がフォームの終了を宣言しています。この間に記述されるのがフォームだということです。

(これらの宣言が何を表しているのか知りたい方は、Googleディベロッパーツールで確認してみましょう)

‘route’ => ‘signup.post’ でフォームを入力し終えたら、’signup.post’のルーティングに入るということを表しています。
‘signup.post’のルーティングに入った後は、RegisterController の registerアクションが実行されることになります。
registerアクションを見て頂くと分かりますが、このアクションを通じてログイン処理も行われることになります。

また、上記の記述方法は「連想配列」の形を取っていますので、

※サンプルコードにつき入力不要

{!! Form::open(['route' => 'signup.post','method' => 'post']) !!}

として,’method’ => ‘post’という記述を追記することで、「POSTメソッドを実行します」ということを明示しても良いのですが、
このフォームにおいては、何も記述しなければ自動でPOSTメソッドだと判断されますので、上記のような記述にはなっていません。

逆に、PUT メソッドや DELETE メソッドの場合は、どのメソッドとするか明示しなければなりませんので、上記と同様の記述が必要です。

Form::open() の中身の記述についても見ていきましょう。

register.blade.php(※一部抜粋)

    {!! Form::label('name', '名前') !!}
         {!! Form::text('name', old('name'), ['class' => 'form-control']) !!}
    // 中略
    {!! Form::submit('新規登録', ['class' => 'btn btn-primary mt-2']) !!}

このForm::label() は

※サンプルコードにつき入力不要

{!! Form::label('モデルのカラム名', 'ラベル名') !!}

という引数を取ります。
まず、この場合はUserモデルのカラム(上記は ‘name’ カラム)が置かれ、次にラベル名(上記は ‘名前’ )が置かれます。

まず最初に、Form::label()と同様に、Userモデルのカラ(上記は ‘name’ カラム)が置かれて、
次に入力欄に入力前から入れておきたい値を置きます(初期値がいらない場合はなしでOK)。
ここに記述のあるold(‘name’)とは、「直前に名前入力欄に入力されていた値を表示する」という意味です。
さらに、その次に入っている引数は、タグ(HTML)の情報です。

第三引数は タグの属性情報を配列形式で指定します。
‘class’ => ‘form-control’ なら、
HTMLでいうところの下記と同じです。

class=”form-control”

つまり、Bootstrapのフォーム指定をしているということです。

Form::submit() は、フォーム内容の送信ボタンをつくることを意味します。
最初には、ボタンに表示される「新規登録」というボタン名が書かれています。

View:ヘッダーの変更

ヘッダーのメニューにある「新規ユーザ登録」のリンクも、下記のように新規ユーザ登録のページへ移動できるように変更しましょう。
link_to_routeという関数を使います。こちらも、Laravel Collectiveの関数となります。

laravel-quest > resources > views > commons > header.blade.php

header.blade.php

<ul class="nav navbar-nav navbar-right">
   <li class="nav-item">{!! link_to_route('signup', '新規ユーザ登録', [], ['class' => 'nav-link']) !!}</li>
   <li class="nav-item"><a href="" class="nav-link">ログイン</a></li>
</ul>

link_to_route関数について説明します。
まず、最初の引数に「ルーティング名」を取り、次にリンクとして「表示される文字列」を取ります。
3番目は、上で解説しましたURLパラメータとして代入したい値を入力します(入力方式は配列)。
4番目は、タグ(HTML)の情報が入っている形となります。

新規ユーザ登録を実行してみよう

さて、ここまでザッと理解して実装ができましたら、実際にユーザ登録(Signup)を実行してみましょう。

トップページから、ヘッダーのSignupを押して、任意のユーザ情報を入力すると、
またトップページに戻って来れますでしょうか?

戻って来れたらユーザ登録機能は完成しているはずですが、念のためにtinkerで確認してみましょう。

ターミナル

>>> use App\User
>>> User::all()

先程登録したユーザ情報が表示されれば、ユーザは新規登録されています。

ただし、現状「ログアウト機能」を作っていないため、現時点で新規ユーザ登録を行うと、「ログアウトできない」という問題が発生しています。

なので、強制的にログアウトさせるべく、一旦今登録したユーザを削除しておきましょう。

delete()で今登録したユーザのみを消去しても良いですが、
下記のいずれかのやり方で、すべてのレコードを削除してしまってもOKです。

ターミナル

>>> use App\User;
>>> User::truncate();
$ php artisan migrate:refresh

この状態でトップページに行けば、ログイン前の画面となるはずです。

また、バリデーションが問題なく動作するかも試してみましょう!

これは一例なのですが
下記のような条件で「新規ユーザー登録」フォームを入力すると
データベースに保存されずに、下記画像のようなバリデーションエラーが表示されるはずです。

  • 名前 → 空欄のとき
  • Eメール → メールの形式(○○○@△△△.comなど)以外のとき
  • パスワード → 8文字未満の時
  • パスワード確認 → パスワード欄とパスワード確認欄の内容が不一致のとき

バリデーションエラーの種類も様々ですので、色々入力して試してみて下さい。


以上で、新規ユーザ登録機能の実装は完了です!
お疲れ様でした。

次回はこちら

https://prog.quest-academia.com/laravel-quest-4