82
77

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

【Laravel】FormRequestはValidationの為だけに在らず

Last updated at Posted at 2019-07-15

みなさん、 フォームリクエスト使ってますか?
Laravel 5.8 バリデーション

Laravelの標準クラス(ファサード)として提供されているFormRequestクラスは、同じく標準クラスであるRequestクラスを継承し、リクエストパラメータに対するバリデーションルールの定義や、リクエストを送信したユーザーの認証チェックの機能を提供しています。

例えば、ユーザー登録画面から送られてきたリクエストに対してフォームリクエストを作るとします。
php artisan make:request UserRequest とコマンドを打つと、以下のフォームリクエストクラス UserRequest が生成できます。

UserRequest.php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class UserRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize() //ユーザーの認証チェック
    {
        return false;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules() //バリデーションルール設定
    {
        return [
            //
        ];
    }
}

Controllerでは、各アクションメソッドの引数に生成したフォームリクエストクラスをタイプヒントするだけ。あとはRequestクラスと同様の使い方ができます。

UserController.php
public function store(UserRequest $request)
{
    $validated = $request->validated(); //UserRequest->rules()のバリデーションルールが適用される
}

主にバリデーションを実装する際に、Controllerからバリデーションルールを分離して可読性が向上、管理もしやすくなるということで、お世話になっている方も多いと思います。

ただ自分の場合、初めはFormRequest = バリデーションで使えるちょっと便利な機能なんだなという程度の認識でした。
しかし使っていくうちに、実は非常に便利なクラスであることを実感したので、今回はそれについて書こうと思います。

FormRequestのメソッドは自由に定義できる

FormRequestクラスはRequestクラスを継承しているので、FormRequestクラスの中でRequestクラスのメソッドをオーバーライドしたり、独自のカスタムメソッドを実装することが可能です。

1. Requestクラスのメソッドのオーバーライド

例えば、 http://localhost:8080/admin/users にアクセスした際、Requestクラスのメソッド url() で以下のURLが返ります。

Illuminate\Http\Request.php
    public function url()
    {
        return rtrim(preg_replace('/\?.*/', '', $this->getUri()), '/');
    }

"http://localhost:8080/admin/users"

これを admin/users のみ返したいとなった場合、 FormRequestクラス内で url() をオーバーライドしてこんな書き方ができます。
(正規表現のところもう少し良いやり方ありそう…)

UserRequest.php
    //Override
    public function url()
    {
        $plainUrl = rtrim(preg_replace('/\?.*/', '', $this->getUri()), '/');
        return preg_replace('/http:\/\/localhost:8080\//', '', $plainUrl); //プロトコル、ドメインを削除
    }

"admin/users"

2.カスタムメソッドの実装

リクエストで送られてきたユーザー情報の中から、郵便番号と住所の情報を抜き出して「住所」オブジェクトのインスタンスを作りたいとしましょう。
これもフォームリクエストの中に独自メソッドを作ることで、Controllerに処理を書かずメソッドの呼び出しだけでインスタンスを取得できます。

UserRequest.php
public function makeAddressInstance()
{
    // Addressクラスのインスタンスを生成して返す
    return new Address(
        $this->input('postal_code'),
        $this->input('address')
    )
}

個々のリクエストに特化したFormRequestを作る

フォームリクエストの大きな強みは、バリデーションルールや上記のようなカスタムメソッドを個々のリクエストごとに生成できる点だと思います。

今回この記事を書くことになったきっかけも、この長所を実感するシチュエーションに遭遇したからです。
現在進めているプロジェクトで、より良い設計を目指す一環としてDDDやオニオンアーキテクチャを取り入れています。(Laravel×DDDやアーキテクチャに関する詳しい解説は割愛します。知識が身についたらいつか書きたい)
その中で、リクエストで受け取ったパラメータをValueObjectインスタンスとしてApplication層に渡したい、という状況に直面しました。
当初の実装では、Controller上でインスタンスを作ってApplication層のメソッドに渡すようにしたのですが、機能ごとに必要なValueObjectはそれぞれ異なり、その1つ1つについてControllerに処理を記述しなければならず、必然的にControllerの肥大化・修正の難化が目立ってきました。

UserController.php
public function store(
    Request $request
    UserInteractor $interactor
) {
    // ユーザー登録のためのパラメータをValueObjectに
    $interactor->saveUser(
        new UserName($request->input('user')),
        new PostalCode($request->input('postal_code')),
        new Address($request->input('address')),
        new TelephoneNumber($request->input('tel')),
        new Mailaddress($request->input('email')),
        new Password($request->input('password'))
    );
    // ユーザー登録以外の機能でも同様の処理を記述したいが、リクエストパラメータは毎度異なるのでその分の記述が必要
}

この問題を解決してくれたのがまさにフォームリクエストでした。
ValueObjectのインスタンスを作る処理をUserRequestに移し toValueObject() メソッドとすることで、Controllerの記述量はグッと減りました。
しかも、リクエストごとに対応するFormRequestクラスを作り、そのリクエストで必要なValueObjectインスタンスの生成処理を個別に書くことで、機能単位で処理を管理することができ、保守性も向上するという恩恵を受けられました。

UserRequest.php
public function toValueObject()
{
    // ValueObjectを生成しarrayで返す
    return array(
        'user' => new UserName($request->input('user')),
        'postal_code' => new PostalCode($request->input('postal_code')),
        'address' => new Address($request->input('address')),
        'tel' => new TelephoneNumber($request->input('tel')),
        'email' => new Mailaddress($request->input('email')),
        'password' => new Password($request->input('password'))
    )
}
UserController.php
public function store(
    UserRequest $request
    UserInteractor $interactor
) {
    // 引数でUserRequestクラスのtoValueObject()の戻り値を指定
    $interactor->saveUser($request->toValueObject());
}

結論

フォームリクエストは、バリデーションを実装しやすくする為だけでなく、工夫次第でより柔軟なシステム作りに貢献してくれるポテンシャルを持っていると感じました。積極的に使っていきたいですね…!

まだまだ未熟者のため間違った認識もあるかと思います。ご指摘、ご意見などありましたらコメントお待ちしております。

82
77
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
82
77

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?