序章

Laravel フォームリクエストは、通常のリクエストクラスの機能を拡張する特別なクラスであり、高度な検証機能を有効にします。 フォームリクエストは、すべての検証ロジックをフォームリクエストクラスに移動できるため、コントローラーのアクションをよりクリーンに保つのにも役立ちます。 もう1つの利点は、コントローラーのアクションに到達する前にリクエストをフィルター処理できることです。

このガイドでは、管理領域にアクセスする前にユーザーにパスワードの確認を要求するパスワード検証手順を実装します。 この方法は二重チェックとして機能し、アプリケーションにセキュリティの追加レイヤーを提供します。

前提条件

このガイドをフォローアップするには、組み込みのLaravel認証が設定された動作するLaravel5.6以降のアプリケーションが必要です。 設定方法の詳細については、公式ドキュメントを確認してください。

ステップ1—ビューを作成する

まず、ユーザーのプロフィール編集ページを設定します。

このチュートリアルの執筆時点では、artisanコマンドユーティリティはビューを生成しないため、ビューを手動で作成する必要があります。

ファイルresources/views/profile/edit.blade.phpを作成し、次のコードを追加します。

@extends('layouts.app')

@section('content')
<div class="container">
    @if (session('info'))
        <div class="row">
            <div class="col-md-12">
                <div class="alert alert-success alert-dismissible">
                    <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
                    {{ session('info') }}
                </div>
            </div>
        </div>        
    @elseif (session('error'))
        <div class="row">
            <div class="col-md-12">
                <div class="alert alert-danger alert-dismissible">
                    <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
                    {{ session('error') }}
                </div>
            </div>
        </div>
    @endif
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <div class="panel panel-default">
                <div class="panel-heading">Update Profile</div>

                <div class="panel-body">
                    <form class="form-horizontal" method="POST" action="{{ route('profile.update', ['user' => $user]) }}">
                        {{ csrf_field() }}
                        {{ method_field('PUT') }}
                        <div class="form-group{{ $errors->has('name') ? ' has-error' : '' }}">
                            <label for="name" class="col-md-4 control-label">Name</label>
                            <div class="col-md-6">
                                <input id="name" type="text" class="form-control" name="name" value="{{ $user->name }}">

                                @if ($errors->has('name'))
                                    <span class="help-block">
                                        <strong>{{ $errors->first('name') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group{{ $errors->has('password') ? ' has-error' : '' }}">
                            <label for="password" class="col-md-4 control-label">Password</label>

                            <div class="col-md-6">
                                <input id="password" type="password" class="form-control" name="password">

                                @if ($errors->has('password'))
                                    <span class="help-block">
                                        <strong>{{ $errors->first('password') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group">
                            <label for="password-confirm" class="col-md-4 control-label">Confirm Password</label>

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

                        <div class="form-group{{ $errors->has('current_password') ? ' has-error' : '' }}">
                            <label for="current-password" class="col-md-4 control-label">Current Password</label>

                            <div class="col-md-6">
                                <input id="current-password" type="password" class="form-control" name="current_password">

                                @if ($errors->has('current_password'))
                                    <span class="help-block">
                                        <strong>{{ $errors->first('current_password') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group">
                            <div class="col-md-6 col-md-offset-4">
                                <button type="submit" class="btn btn-primary">
                                    Update
                                </button>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

「プロファイルの編集」ページでは、infoおよびerrorのフラッシュメッセージを確認し、ユーザーに表示します。

namepasswordpassword_confirmation、およびcurrent_passwordフィールドがあります。

ユーザーが変更を加えるたびに、データベースに更新をコミットするために正しいcurrent_passwordフィールドを指定する必要があります。

passwordおよびpassword_confirmationフィールドを使用すると、ユーザーはパスワードを変更できます。 両方とも空のままにすると、ユーザーの現在のパスワードが保持され、保存されているパスワードは変更されません。

私たちの見解の主要なプレーヤーは、passwordpassword_confirmation、およびcurrent_passwordフィールドです。

nameフィールドについては、ケースを拡張してフィールドを追加するための例として役立ちます。

ステップ2—フォームリクエストを作成する

次に、このチュートリアルの最も重要な部分に移ります。

次のコマンドを実行して、フォームリクエストを作成します。

  1. php artisan make:request UpdateProfile

上記のコマンドは、app/Http/Requests/UpdateProfile.phpという名前のファイルを作成します。

このセクションのすべてのコード変更は、このファイルに対して行われます。

最初に行う必要があるのは、クラス宣言の前にエイリアスLaravelのハッシュファサードです。

use Illuminate\Support\Facades\Hash;

次に、フォームリクエストで承認を実行していないため、authorizeメソッドからtrueを返す必要があります。

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

rulesメソッドは、このリクエストの検証ルールの概要を示す配列を返します。

/**
 * Get the validation rules that apply to the request.
 *
 * @return array
 */
public function rules()
{
    return [
        'name' => 'required|string|max:255',
        'password' => 'nullable|required_with:password_confirmation|string|confirmed',
        'current_password' => 'required',
    ];
}

nameおよびcurrent_passwordのルールは一目瞭然です。

passwordルールでは、パスワードはconfirmed宣言を使用して確認されると規定されています。

また、required_with:password_confirmationも宣言されています。つまり、ユーザーがパスワードの確認を提供する場合は、パスワードも提供する必要があります。

これらの検証ルールは、コントローラーアクションでタイプヒントを入力すると(後で実行します)、要求ごとに自動的にチェックされます。

最後に行う必要があるのは、リクエストでwithValidatorメソッドを宣言することです。このメソッドは、検証ルールが実行される前に、完全に構築されたバリデーターインスタンスに渡されます。

/**
 * Configure the validator instance.
 *
 * @param  \Illuminate\Validation\Validator  $validator
 * @return void
 */
public function withValidator($validator)
{
    // checks user current password
    // before making changes
    $validator->after(function ($validator) {
        if ( !Hash::check($this->current_password, $this->user()->password) ) {
            $validator->errors()->add('current_password', 'Your current password is incorrect.');
        }
    });
    return;
 }

withValdatorメソッド内に、afterフックを追加しました。これは、すべての検証チェックが行われた後に実行される関数です。

afterフックでは、ユーザーが提供したパスワードをデータベースに設定されたパスワードと比較しました。

$this->current_passwordcurrent_passwordフォームフィールド値を提供しますが、Laravelは$this->user()を使用して現在認証されているユーザーにアクセスできるため、$this->user()->passwordは保存されたユーザーのハッシュパスワードを提供しますデータベース内。

2つのパスワードは、Hashファサードのcheckメソッドを使用して比較されます。

ハッシュチェックが失敗した場合、 $validator->errors()->add('current_password', 'Your current password is incorrect.')を使用して、キーcurrent_passwordでバリデーターにエラーが追加されます。

これが完全なUpdateProfileフォームリクエストです。

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

use Illuminate\Support\Facades\Hash;

class UpdateProfile 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|string|max:255',
            'password' => 'nullable|required_with:password_confirmation|string|confirmed',
            'current_password' => 'required',
        ];
    }

    /**
     * Configure the validator instance.
     *
     * @param  \Illuminate\Validation\Validator  $validator
     * @return void
     */
    public function withValidator($validator)
    {
        // checks user current password
        // before making changes
        $validator->after(function ($validator) {
            if ( !Hash::check($this->current_password, $this->user()->password) ) {
                $validator->errors()->add('current_password', 'Your current password is incorrect.');
            }
        });
        return;
    }
}

ステップ3—コントローラーのセットアップ

フォームリクエストを使用するには、コントローラーアクションでタイプヒントを入力する必要があります。

次のコマンドを実行して、プロファイルコントローラを生成します。

  1. php artisan make:controller ProfileController

ファイルapp/Http/Controllers/ProfileController.phpを開き、次のコントローラーアクションを追加します。

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

/**
 * Show the form for editing the specified resource.
 *
 * @param  \App\User  $user
 * @return \Illuminate\Http\Response
 */
public function edit(Request $request, User $user)
{
    // user
    $viewData = [
        'user' => $user,
    ];
    // render view with data
    return view('profile.edit', $viewData);
}

/**
 * Update the specified resource in storage.
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  \App\User  $user
 * @return \Illuminate\Http\Response
 */
public function update(UpdateProfile $request, User $user)
{
    // form data
    $data = $request->all();
    $user->update($data);
    return redirect(route('profile.edit', ['user' => $user]))
                ->with('info', 'Your profile has been updated successfully.');
}

プロファイルコントローラーのコンストラクターは、authミドルウェアを設定して、ユーザーがプロファイルを編集する前にログインしていることを確認します。

editアクションは、ビューデータを使用してビューを提供しますが、updateアクションは、ユーザープロファイルを更新し、対応するフラッシュメッセージを使用してプロファイルの編集ページにリダイレクトします。

editアクションの署名に注意してください。ここでは、UpdateProfileリクエストをタイプヒントしました。これは、UpdateProfileフォームリクエスト内で検証を実行するために必要なすべてです。

また、コントローラークラス宣言の前に、フォームリクエストとユーザーモデルのエイリアスを作成する必要があります。

use App\Http\Requests\UpdateProfile;
use App\User;

ステップ4—保護されたルートとデータミューテーターの設定

ファイルapp/routes/web.phpを開き、次のコードを追加してコントローラーアクションを関連付けます。

Route::get('/profile/{user}/edit', 'ProfileController@edit')->name('profile.edit');
Route::put('/profile/{user}', 'ProfileController@update')->name('profile.update');

以前にフォームリクエストに追加した検証ルールに基づいて、nullパスワードが通過する可能性があります。

いかなる状況でも、ユーザー(またはアプリケーション開発者)はパスワードをnullまたは空の文字列に設定することを望んでいません。

ユーザーパスワードが提供された場合にのみユーザーパスワードが設定されるようにするために、EloquentORMのミューテーターを使用します。

ファイルapp/User.phpを開き、次のコードを追加します。

// Only accept a valid password and 
// hash a password before saving
public function setPasswordAttribute($password)
{
    if ( $password !== null & $password !== "" )
    {
        $this->attributes['password'] = bcrypt($password);
    }
}

頻繁なミューテーターは、命名スキームset<camel-cased-attribute-name>Attributeに従う必要があります。

password属性のミューテーターを宣言しているため、ミューテーターにsetPasswordAttributeという名前を付けました。

ミューテイタ関数には、ミューテイタでは$password変数である設定値が渡されます。

ミューテーターでは、$password変数がnullまたは空の文字列でないかどうかを確認し、$this->attributes['password']を使用してモデルに設定します。

また、パスワードは保存する前にハッシュされるため、アプリケーションの他の場所で行う必要はありません。

デフォルトのLaravelAuth/RegisterControllerAuth/ResetPasswordControllerも、データベースに永続化する前にパスワードをハッシュするため、それぞれのコントローラーでcreateresetPasswordメソッドを更新する必要があります上記のミューテーターを宣言した後。

ファイルapp/Http/Controllers/Auth/RegisterController.phpを開き、次のコードを追加します。

/**
 * 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' => $data['password'],
    ]);
}

ファイルapp/Http/Controllers/Auth/ResetPasswordController.phpを開き、次のコードを追加します。

/**
 * Reset the given user's password.
 *
 * @param  \Illuminate\Contracts\Auth\CanResetPassword  $user
 * @param  string  $password
 * @return void
 */
protected function resetPassword($user, $password)
{
    $user->password = $password;

    $user->setRememberToken(Str::random(60));

    $user->save();

    event(new PasswordReset($user));

    $this->guard()->login($user);
}

ResetPasswordControllerの場合、クラス宣言の前に使用されるそれぞれのクラスのエイリアスも必要になります。

use Illuminate\Support\Str;
use Illuminate\Auth\Events\PasswordReset;

これですべて完了し、パスワードの検証は期待どおりに機能します。

結論

このガイドでは、ユーザーが管理領域へのアクセスを許可されていることを表明するための追加のパスワード検証手順を実装する方法について説明しました。 Laravelアプリケーション内にフォーム検証を実装するためのフォームリクエストを作成および設定する方法を見てきました。

検証とフォームリクエストの詳細については、公式のLaravelドキュメントを確認してください。