【Laravel5.7】メール認証を使った会員登録(仮登録→本登録)

はじめに

こちらのめちゃくちゃ丁寧に書いてくださっているQiitaの記事を元に作成しています。
ですが初心者の私には今どこをやっているんだろうと、すんなりといかなかったので完成画像や構成を紹介しながら分かりやすくしてみました。
この記事で分かりにくかった方には、参考元のQiitaの記事には筆者さまのソースも載っています。

 

機能

1.仮会員登録(メールアドレス / パスワードのみ)
2.メール送信(本登録用のURL記載)
3.本登録

1仮会員登録

完成イメージ(*画像)

①仮登録フォーム 画面  pre_register.blade.php

②仮登録内容 確認画面  pre_register_check.blade.php

③仮登録完了 画面  pre_registered.blade.php

仮登録の手順

①各画面を作成(3つ)
②usersテーブルにメール認証用のカラム追加
③RegisterControllerに仮登録 register()  と pre_check() を作成・変更 & we.phpにルート追加

追加 / 変更する点

views(authディレクトリー)
・仮登録フォーム 画面  pre_register.blade.php
・仮登録内容 確認画面  pre_register_check.blade.php
・仮登録完了 画面  pre_registered.blade.php

users_table
・users_tableにカラム追加
・users_tableのnameカラムをnullableに変更

RegisterController
・pre_check() 仮登録内容 確認内容に出す内容について記載
・register() もともとlaravelのauthに入っているregisterを変更

User.php
$fillableに追加したカラムを追加(email_token , email_varified)

仮登録

Laravelの認証機能を利用

php artisan make:auth

 

User_tableにカラムを追加

php artisan make:migration add_votes_to_users_table --table=users
class AddColumnsUsersTable extends Migration
{
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->boolean('email_verified')->default(0);
            $table->string('email_verify_token')->nullable();
            $table->string('phone_number')->nullable();
            $table->dateTime('birthday')->nullable();
            $table->dateTime('address')->nullable();
     }

     public function down()
    { 
        Schema::table('users', function (Blueprint $table) { 
            $table->dropColumn('email_verified'); 
            $table->dropColumn('email_verify_token');
            $table->>dropColumn('phone_number');
            $table->>dropColumn('birthday');
            $table->>dropColumn('address'); 
        }); 
    }

booleanはemail_varified(メール認証)がされていれば1、されていなければ0
email_verify_tokenはYkBnbWFpbC5jb20=のようなもの

 

このままではname欄が必須のままでエラーとなるのでnullでも大丈夫なように変更します

php artisan make:migration change_column_users_table --table=users

$table->string(‘name’)->nullable(); としてください

 

php artisan migrate

 

仮登録フォーム画面作成 pre_register.blade.php

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">{{ __('Register') }}</div>

                <div class="card-body">
                    <form method="POST" action="{{ route('register.pre_check') }}">
                        @csrf

                        
                        <div class="form-group row">
                            <label for="email" class="col-md-4 col-form-label text-md-right">{{ __('E-Mail Address') }}</label>

                            <div class="col-md-6">
                                <input id="email" type="email" class="form-control @error('email') is-invalid @enderror" name="email" value="{{ old('email') }}" required autocomplete="email">

                                @error('email')
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $message }}</strong>
                                    </span>
                                @enderror
                            </div>
                        </div>

                        <div class="form-group row">
                            <label for="password" class="col-md-4 col-form-label text-md-right">{{ __('Password') }}</label>

                            <div class="col-md-6">
                                <input id="password" type="password" class="form-control @error('password') is-invalid @enderror" name="password" required autocomplete="new-password">

                                @error('password')
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $message }}</strong>
                                    </span>
                                @enderror
                            </div>
                        </div>

                        <div class="form-group row">
                            <label for="password-confirm" class="col-md-4 col-form-label text-md-right">{{ __('Confirm Password') }}</label>

                            <div class="col-md-6">
                                <input id="password-confirm" type="password" class="form-control" name="password_confirmation" required autocomplete="new-password">
                            </div>
                        </div>

                        <div class="form-group row mb-0">
                            <div class="col-md-6 offset-md-4">
                                <button type="submit" class="btn btn-primary">
                                    {{ __('Register') }}
                                </button>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

新たに作成、名前をpre_しなくても、php artisan make:auth で作成されたview/auth/register.blade.phpと同じです。もしわかりやすいように名前をpre_で揃えるのであれば、RegistersUsersのshowRegistrationForm()でreturnされるview名も変更

仮登録は「メールアドレス」「パスワード」のみの登録なので、pre_register.blade.php のnameの記載を削除

-                        <div class="form-group row">
-                            <label for="name" class="col-md-4 col-form-label text-md-right">{{ __('Name') }}</label>
-
-                            <div class="col-md-6">
-                                <input id="name" type="text" class="form-control{{ $errors->has('name') ? ' is-invalid' : '' }}" name="name" value="{{ old('name') }}" required autofocus>
-
-                                @if ($errors->has('name'))
-                                    <span class="invalid-feedback">
-                                        <strong>{{ $errors->first('name') }}</strong>
-                                    </span>
-                                @endif
-                            </div>
-                        </div>

今回は仮登録内容 確認画面 を出すのでpre_register.blade.php のformの送信先を変更

-                    <form method="POST" action="{{ route('register') }}">
+                    <form method="POST" action="{{ route('register.pre_check') }}">

 

web.phpに追加

Route::post('register/pre_check', 'Auth\RegisterController@pre_check')->name('register.pre_check');

 

RegisterController変更

「名前」を削除したのでValidator()からnameを削除

    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:6|confirmed',
        ]);
    }

 

先程web.phpに記載したpre_check()をRegisterControllerに記載

public function pre_check(Request $request){

        $this->validator($request->all())->validate();

        $bridge_request = $request->all();
        // password マスキング
        $bridge_request['password_mask'] = '******';

        return view('auth.register_check')->with($bridge_request);
    }

 

User.php

  protected $fillable = [
        'name', 'email', 'password',
       'email_verified', 'email_verify_token','address', 'phone_number', 'birthday'
    ];

 

 

いったん仮登録フォーム完成

 


 

2メール

完成イメージ(*画像)

mailtrapに本登録URL付きのメールが届きます

 

メール送信の手順

①mailtrapアカウントを作成(2、3分で無料でできます)
②.envファイル変更
③EmailVerification.php作成 & 変更
user_register_plain (メール本文)を作成
⑤RegisterControllerの create()  を変更

追加 / 変更する点

views(authディレクトリー)
・メールテンプレート user_register_plain.blade.php

.env
mailtrap利用の為、変更
実際のメールアドレスでテストするのは大変なのでmailtrapというwebサービスを利用します。

EmailVerification.php
・__construct()
・build() メールの内容や件名を指定

RegisterController
・create()変更 実際にここでメールを送る指示を出す

メール送信

mailtrapアカウントを作成

2~3分で簡単に作成できます。テストデータの中に間違って本番ユーザのメールアドレスがあった場合には、取り返しのつかない事故が発生してしまう可能性もあります。そんな危険を回避しつつテストしやすくしてくれるのがmailtrapというWebサービスです。SMTPでメールを送信しても実際の宛先には飛ばさず、WebサイトやAPIから確認することができるというサービスです。こちらの記事を参考にして作成してください。

 

.envファイルの変更

MAIL_DRIVER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME= (黒塗りの該当する箇所)
MAIL_PASSWORD= (黒塗りの該当する箇所)
MAIL_ENCRYPTION = tls
+ MAIL_FROM_ADDRESS=from@example.com(メール送信時のデフォルト値設定可能)
+ MAIL_FROM_NAME=LaravelExampler(メール送信時のデフォルト値設定可能)

 

認証メールに使用するviewと、tokenを返すクラスを作成

php artisan make:mail EmailVerification

このコマンドで、app/MailにMailableを継承したEmailVerificationクラスが作成されます。
このクラスにメール送信に使用するviewやメールタイトルなどの設定を記述することになります。

 

EmailVerification.php

EmailVerification.php が作成され、その中に __construct()  と build()  があります。

__construct() にユーザー情報を渡すために変数を定義

<?php

namespace App\Mail;

use ...

class EmailVerification extends Mailable
{
    use Queueable, SerializesModels;

    protected $user;


    public function __construct($user)
    {
        $this->user = $user;
    }

 

build()でメールを送る際の件名やviewを指定します。

    public function build()
    {
        return $this
            ->subject('【activity】登録手続きのご案内')
            ->view('emails.user_register_plain')
            ->with(['token' => $this->user->email_verify_token, ]);
    }

 

user_register_plain.blade.php (メール本文)を作成

今回emailsディレクトリを作成してその中に入れてますが、いい感じの名前に変更してください

サイトへのアカウント仮登録が完了しました。<br>
<br>
以下のURLからログインして、本登録を完了させてください。<br>
{{url('register/verify/'.$token)}}

 

RegisterController

pre_register_check.blade.php でフォームの送信先となっている register は RegisterController で use しているRegistersUsersにあります

    public function register(Request $request)
    {
        event(new Registered($user = $this->create( $request->all() )));

        return view('auth.pre_registered');
    }

ここで実際にDBに作成されるのは、$user = $this->create( $request->all() )) で RegisterController の create() です。

ここでusersDBに入れられ、メール送信の指示を追加します。

 protected function create(array $data)
    {
        $user = User::create([
            'email' => $data['email'],
            'password' => Hash::make($data['password']),
            'email_verify_token' => base64_encode($data['email']),
        ]);

        $email = new EmailVerification($user);
        Mail::to($user->email) ->send($email);

        return $user;
    }

 


 

3本登録

完成イメージ(*画像)

①本登録フォーム 画面  main_register.blade.php

 

②本登録内容 確認画面  main_register.blade.php

 

③本登録 完了画面  main_register.blade.php

 

本登録の手順

UserForm.php 作成
②各画面を作成(3つ)
③RegisterControllerに本登録 showForm()  と mainCheck() とmainRegister() を作成・変更 & web.phpにルート追加
④User.phpを変更

追加 / 変更する点

views(authディレクトリー)
・本登録フォーム 画面  main_register.blade.php
・本登録内容 確認画面  main_register_check.blade.php
・本登録完了 画面 main_registered.blade.php

RegisterController
・showForm() 本登録フォームを出す
・mainCheck()  本登録内容 確認画面を出す
・mainRegister() フォーム送信 DB登録

User.php
$fillableに氏名、電話番号その他保存したい情報を追加

UserForm
※参考元の登録フォームにはlaravelのformは使用していないので、使用しない方はそちらを参考にしてください。

Rules/Birthday.php
存在しない日付をエラーで返す

config/const.php
statusを記載

本登録

FormsディレクトリとUserForm.phpを作成

php artisan make:form Forms/UserForm

 

composer.jsonにlaravel-form-builderを追加

    "require": {
        "php": "^7.1.3",
        "aws/aws-sdk-php": "~3.0",
        "doctrine/dbal": "^2.9",
        "fideloper/proxy": "^4.0",
        "kris/laravel-form-builder": "dev-master",
      }

 

ターミナルでcomposer update

composer update

 

config/app.phpにlaravel-form-builderを追加

    'providers' => [    
                                    :
                                    :
                                Kris\LaravelFormBuilder\FormBuilderServiceProvider::class,
                             ];

    'aliases' => [    
                                    :
                                    :
                                'formBuilder' => 'Kris\LaravelFormBuilder\Facades\FormBuilder',

                             ];


 

UserForm.php

<?php

namespace App\Forms;

use Kris\LaravelFormBuilder\Form;
use App\Rules\Birthday;
use App\User;


$BIRTH_MONTH_CHOICES = [];
foreach (range(1, 12) as $month) {
    $BIRTH_MONTH_CHOICES[$month] = $month;
}

$BIRTH_DAY_CHOICES = [];
foreach (range(1, 31) as $day) {
    $BIRTH_DAY_CHOICES[$day] = $day;
}

define('BIRTH_MONTH_CHOICES', $BIRTH_MONTH_CHOICES);
define('BIRTH_DAY_CHOICES', $BIRTH_DAY_CHOICES);


class UserForm extends Form
{
    public function buildForm()
    {
        $this
            ->add('name','text',[
                'rules' => 'required',
                'label' => '名前'
            ])

            ->add('address','text',[
                'rules' => 'required',
                'label' => '住所'
            ])

            ->add('birth_month','select',[
                'rules' => 'required',
                'attr' => ['class' => 'form-control rounded-pill'],
                'choices' => BIRTH_MONTH_CHOICES,
                'empty_value' => '--'
            ])

            ->add('birth_day','select',[
                'rules' => 'required',
                'attr' => ['class' => 'form-control rounded-pill'],
                'choices' => BIRTH_DAY_CHOICES,
                'empty_value' => '--'
            ]);

        $this->addBirthYear();
        $this->addPhoneNumber();

    }

    public function addBirthYear()
    {
        $currentYear = date('Y');

        $birthYearChoices = [];

        foreach (range($currentYear, $currentYear - 100) as $year) {
            $birthYearChoices[$year] = $year;
        }

        $this
            ->add('birth_year', 'select', [
                'attr' => ['class' => 'form-control rounded-pill'],
                'rules' => ['required', new Birthday($this->request->all())],
                'choices' => $birthYearChoices,
                'empty_value' => '----',
            ]);
    }

    public function addPhoneNumber()
    {
        $placeholder = ['090', '1234', '5678'];

        for ($i = 1; $i <= 3; $i += 1) {
            $this
                ->add("phone_number$i", 'number',[
                    'attr' => [
                        'class' => 'form-control rounded-pill',
                        'placeholder' => $placeholder[$i - 1]
                    ],
                    'rules' => 'required',
                ]);
        }

    }

    public function getFieldValues($with_nulls = true)
    {
        $values = parent::getFieldValues($with_nulls);


        $birthdayValues = [];

        foreach (['year', 'month', 'day'] as $birth) {
            $birthdayValues[] = $values["birth_$birth"];
        }

        $values['birthday'] = sprintf("%04d-%02d-%02d", ...$birthdayValues);


        $phoneNumberValues = [];

        for ($i = 1; $i <= 3; $i += 1) {
            $phoneNumberValues[] = $values["phone_number$i"];
        }

        $values['phone_number'] = join('-', $phoneNumberValues);

        for ($i = 1; $i <= 3; $i += 1) {
            unset($values["phone_number$i"]);
        }


        return $values;


    }

    public function check()
    {
        $values = $this->getFieldValues();

        $user = new User();

        $user->name = $values['name'];
        $user->address = $values['address'];
        $user->phone_number = $values['phone_number'];
        $user->birthday = $values['birthday'];



        return $user;

    }

誕生日はuserの入力画面ではbirth_year , month , dayと3つに分けていますが、DBに登録のときはbirthdayと一つのカラムとして登録するようにgetFieldValues()で整えています。

addBirthYear()では毎年新しい年が自動で追加されるよう、currentYear(今年)から100年前までを選択表示させるようにしています。

addPhoneNumber()では元から一つの入力フォームにしても大丈夫ですが、今回は分けています。

 

Rules/Birthday.php 作成

Rulesディレクトリを作成し、Birthday.phpを作成します。ここでは存在しない日付が選択されないよう、チェックします。(2月31日など)

<?php

namespace App\Rules;

use Illuminate\Contracts\Validation\Rule;

class Birthday implements Rule
{
    private $values;

    /**
     * Create a new rule instance.
     *
     * @return void
     */
    public function __construct($values)
    {
        $this->values = $values;
    }

    /**
     * Determine if the validation rule passes.
     *
     * @param  string  $attribute
     * @param  mixed  $value
     * @return bool
     */
    public function passes($attribute, $value)
    {
        $birthdayValues = [];

        foreach (['month', 'day', 'year'] as $suffix) {
            $birthdayValues[] = $this->values["birth_$suffix"];
        }

        return checkdate(...$birthdayValues);
    }

    /**
     * Get the validation error message.
     *
     * @return string
     */
    public function message()
    {
        return '生年月日に誤りがあります';
    }
}

 

本登録フォーム main_register.blade.phpを作成

@extends('layouts.app')
@section('content')
    <div class="container">
        <div class="row d-flex">
            <div class="col-md-8 justify-content-center">
                <div class="card">
                    <div class="card-header">本会員登録</div>

                    @isset($message)
                        <div class="card-body">
                            {{$message}}
                        </div>
                    @endisset

                    @empty($message)
                        <div class="card-body">
                            <form action="{{ route('register.main_pre_check', ['token' => $email_token]) }}">
                                @csrf


                                <div class="row">
                                    <div class="col-sm-6">
                                        <div class="form-group">
                                            <label>{!! form_label($form->name) !!}</label>
                                            {!! form_widget($form->name) !!}
                                        </div>
                                    </div>
                                </div>


                                <div class="col-sm-6">
                                    <div class="form-group">
                                        <label>生年月日</label>
                                        <div class="row">
                                            <div class="col">
                                                {!! form_widget($form->birth_year) !!}
                                            </div>
                                            <div class="m-auto">
                                                /
                                            </div>
                                            <div class="col">
                                                {!! form_widget($form->birth_month) !!}
                                            </div>
                                            <div class="m-auto">
                                                /
                                            </div>
                                            <div class="col">
                                                {!! form_widget($form->birth_day) !!}
                                            </div>
                                        </div>
                                        <span class="error">{!! form_errors($form->birth_year) !!}</span>
                                    </div>
                                </div>



                                <div class="col-sm-6">
                                    <div class="form-group">
                                        <label>電話番号</label>
                                        <div class="row">
                                            <div class="col">
                                                {!! form_widget($form->phone_number1) !!}
                                            </div>
                                            <div class="m-auto">
                                                -
                                            </div>
                                            <div class="col">
                                                {!! form_widget($form->phone_number2) !!}
                                            </div>
                                            <div class="m-auto">
                                                -
                                            </div>
                                            <div class="col">
                                                {!! form_widget($form->phone_number3) !!}
                                            </div>
                                        </div>
                                    </div>
                                </div>




                                <div class="row">
                                    <div class="col-sm-6">
                                        <div class="form-group">
                                            <label>{!! form_label($form->address) !!}</label>
                                            {!! form_widget($form->address) !!}
                                        </div>
                                    </div>
                                </div>


                                <div class="form-group row mb-0">
                                    <div class="col-md-6 offset-md-4">
                                        <button type="submit" class="btn btn-primary">
                                            確認画面へ
                                        </button>
                                    </div>
                                </div>

                            </form>
                        </div>
                    @endempty
                </div>
            </div>
        </div>
    </div>
@endsection

 

web.php

メールアドレスに記載の本会員登録用URLがクリックされると、作成したmain_register.blade.php に遷移されるように設定(register/verify/{token})

Route::get('register/verify/{token}', 'Auth\RegisterController@showForm');

 

RegisterController に showForm() を追加

①DBに$email_tokenがなければエラーを出す

②もしstatusが ‘登録’ になっていたら既に本登録されてる通知を出す

 public function  showForm($email_token)
    {

        $form = $this->form(UserForm::class);

        if ( !User::where('email_verify_token', $email_token)->exists()){

            return view('auth.main_register', compact('form'))->with('message', '無効なトークンです。');

        } else {

            $user = User::where('email_verify_token', $email_token)->first();

            if ($user->status == config('const.USER_STATUS.REGISTER')) {
                return view('auth.main_register', compact('form'))->with('message', 'すでに本登録されています。ログインして利用してください。');
            }

            $user->status = config('const.USER_STATUS.MAIL_AUTHED');
            $user->email_verified_at = Carbon::now();

            if ($user->save()) {
                return view('auth.main_register', compact('email_token','form'));
            } else {
                return view('auth.main_register', compact('form'))->with('message', 'メール認証に失敗しました。再度、メールからリンクをクリックしてください。');
            }

        }
    }

 

いったん本登録フォーム完成

 

users_tableにstatusカラムを追加

 php artisan make:migration add_column_users_table --table=users

 

class AddColumnUsersTable extends Migration
{
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->tinyInteger('status')->default(0);
        });
    }

    public function down()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->dropColumn('status');
        });
    }
}

 

php artisan migrate

 

config/const.phpを作成

ステータス値をconst(定数)で管理するようにしています。

<?php

return [
    /*
    |--------------------------------------------------------------------------
    | Const
    |--------------------------------------------------------------------------
    */

    // 0:仮登録 1:本登録 2:メール認証済 9:退会済
    'USER_STATUS' => ['PRE_REGISTER' => '0', 'REGISTER' => '1', 'MAIL_AUTHED' => '2', 'DEACTIVE' => '9',],
];

 

本登録内容 確認画面  main_register_check.blade.php作成

@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-8">
                <div class="card">
                    <div class="card-header">本会員登録確認</div>

                    <div class="card-body">
                        <form method="POST" action="{{ route('register.main.registered', ['token' => $email_token]) }}">
                            @csrf

                            <div class="form-group row">
                                <label for="name" class="col-md-4 col-form-label text-md-right">名前</label>
                                <div class="col-md-6">
                                    <span class="">{{$user->name}}</span>
                                    <input type="hidden" name="name" value="{{$user->name}}">
                                </div>
                            </div>

                            <div class="form-group row">
                                <label for="phone_number" class="col-md-4 col-form-label text-md-right">電話番号</label>
                                <div class="col-md-6">
                                    <span class="">{{$user->phone_number}}</span>
                                    <input type="hidden" name="phone_number" value="{{$user->phone_number}}">
                                </div>
                            </div>

                            <div class="form-group row">
                                <label for="birthday" class="col-md-4 col-form-label text-md-right">生年月日</label>
                                <div class="col-md-6">
                                    <input type="hidden" name="birthday" value="{{$user->birthday}}">
                                    {{ $user->birthday }}
                                </div>
                            </div>

                            <div class="form-group row">
                                <label for="address" class="col-md-4 col-form-label text-md-right">住所</label>
                                <div class="col-md-6">
                                    <input type="hidden" name="address" value="{{$user->address}}">
                                    {{ $user->address }}
                                </div>
                            </div>


                            {!! form_errors($form->address) !!}

                            <div class="form-group row mb-0">
                                <div class="col-md-6 offset-md-4">
                                    <button type="submit" class="btn btn-primary">
                                        本登録
                                    </button>
                                </div>
                            </div>
                        </form>
                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection

 

本登録完了 画面 main_registered.blade.php 作成

@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-8">
                <div class="card">
                    <div class="card-header">本会員登録完了</div>

                    <div class="card-body">
                        <p>本会員登録が完了しました。</p>
                        <a href="{{url('/')}}" class="sg-btn">トップページへ戻る</a>

                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection

 

RegisterController に 確認画面 mainCheck() と 本登録 mainRegister() を追加

public function mainCheck($email_token)
    {
        $form = $this->form(UserForm::class);

        if (!$form->isValid()) {
            return redirect()->back()->withErrors($form->getErrors())->withInput();
        }

        $user = $form->check();


        return view('auth.main_register_check', compact('user' , 'form', 'email_token'));
    }



    public function mainRegister(Request $request, $email_token)
    {
        $user = User::where('email_verify_token', $email_token)->first();
        
        $user->name = $request->name;
        $user->phone_number = $request->phone_number;
        $user->birthday = $request->birthday;
        $user->address = $request->address;
        $user->save();

        return view('auth.main_registered');
    }

 

 

web.php

本登録確認画面、本登録のルートを登録

Route::get('register/main_check/{token}', 'Auth\RegisterController@mainCheck')->name('register.main_pre_check');
Route::post('register/main_register/{token}', 'Auth\RegisterController@mainRegister')->name('register.main.registered');

 

 

 

以上でメール認証ができたかと思います。

 

コメントを残す

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