LaravelのFormRequestにaccessor機能をつける

さてさて、ここのところLaravelの記事から遠ざかってしまったので、この辺で前から一度作ってみたかった「ちょっとした機能」をつくってみることにしました。

それは、この間公開した「完全な手順!Laravelバリデーション前にデータを加工する方法」が元ネタになっているのですが、簡単にどういう機能かと言うと、

送信されてきた値をaccessorのように簡単に加工できるようにする

というものです。

Laravelをよく使う方ならご存知だと思うのですが、accessorとはデータベースの値を簡単に加工できる強力な機能で、例えば名字と名前を結合したfull_nameという値を取得するには、モデル内にget****Attribute()というメソッドを追加することで有効になります。

<?php

namespace App;

// 省略

class User extends Authenticatable
{
    // 省略

    // Accessor
    public function getFullNameAttribute() {

        return $this->last_name .' '. $this->first_name;

    }
}

使い方はこうです。

$user = \App\User::find(1);
echo $user->full_name; // 元々存在していない "full_name" が使える!

※ つまり、毎回結合するコードを書かなくてもいいわけですね。

そして、ずっと前からこの機能がFormRequestにもほしいなと考えていたんです。元ネタの記事ではall()を上書きする形で実装しましたが、保守管理はacessor形式の方がずっといいと思ったんです。

そこで!

今回は、FormRequest内でaccessorが使えるようにする方法をご紹介します。(いつも以上に簡単にできます)

ぜひ皆さんのお役に立てると嬉しいです😊✨

開発環境: Laravel 5.7

やりたいこと

具体的に実装したい機能は次のとおりです。

  • FormRequest のデータを加工して取得できる
  • 加工したデータは、通常通りバリデーションに使える
  • 同じく、加工データは $request->xxxxx;としてアクセスできる

では実行してみましょう!

まずは、独自のFormRequestをつくる

今回はテストとして、TestRequestをつくって説明を進めていきます。

では、以下のコマンドを実行してください。

php artisan make:request TestRequest

すると、app/Http/Requests/TestRequest.phpというファイルが作成されるので、このファイルを開いておいてください。

accessor が使えるようにトレイトを作成する

次にTestRequest.phpaccessor機能が使えるように独自の「トレイト」を作ります

※ トレイトというのは、ある特定の機能をPHPクラスに追加するもので、Vueを使う人なら「ミックスイン」というイメージが分かりやすいかと思います。

では、app/FormRequestAppendable.phpというファイルをつくって中身を以下のようにしてください。

<?php

namespace App;

use Illuminate\Support\Str;

trait FormRequestAppendable {

    public function all($keys = null) {

        $results = parent::all($keys);

        foreach($this->appends as $append) {

            $method = Str::camel('get_'. $append .'_attribute');

            if(method_exists($this, $method)) {

                $results[$append] = $this->{$method}($results);

            }

        }

        return $results;

    }

}

この中では、FormRequestall()を上書きしています。

流れとしては、初期値をparent::all()から取得し、そこに$appendsで指定されたaccessorの値を追加するようにしています。

使い方

使い方としてはFormRequest$appendsというメンバー変数を作り、それに該当するaccessorを独自につくるだけでOKです。

例えば、「時」「分」を結合したtimeという値をつくるにはこのようにします。(太字が追加した部分です)

<?php

namespace App\Http\Requests;

use App\FormRequestAppendable;
use Illuminate\Foundation\Http\FormRequest;

class TestRequest extends FormRequest
{
    use FormRequestAppendable;
    protected $appends = ['time'];

    // 省略

    // Accessor
    public function getTimeAttribute($values) {

        if($this->filled('hours', 'minutes')) {

            return $values['hours'] .':'. $values['minutes'];

        }

    }
}

バリデーションにつかえる

先ほどaccessor機能でつくった値timeは、通常のデータと同じようにバリデーションにも使えます。

class TestRequest extends FormRequest
{
    // 省略

    public function rules()
    {
        return [
            'time' => 'required|date_format:H:i'
        ];
    }

データ取得もできます

例えば、バリデーションが成功してコントローラーでtimeの値を取得するのもいつもと同じ使い方でOKです。

class HomeController extends Controller
{
    public function store(TestRequest $request) {

        echo $request->time; // 時・分を結合したデータ

    }

元データの加工にも使える

今回のトレイトは、新しい値をつくるためだけのものではなく、元データの加工にも使うことができます。

例えば、name_kanaという「名前のかな」を入力する場合で、これをカタカナで統一したい場合は以下のようにします。

<?php

// 省略

class TestRequest extends FormRequest
{
    use FormRequestAppendable;
    protected $appends = ['name_kana'];

    // 省略

    // Accessor
    public function getNameKanaAttribute($values) {

        return mb_convert_kana($values['name_kana'], 'C'); // カタカナで統一

    }
}

お疲れ様でした😊✨

パッケージ化しました

はじめは、独自にトレイトをつくって実装すればいいかなとも思ったんですが、毎回このトレイトをコピペするのはめんどうですので、composerパッケージとして公開しました。(MITライセンスですので、自由に使ってください)

GitHub: SUKOHI/FormRequestAppendable

インストールは以下のコマンドになります。

composer require sukohi/form-request-appendable:1.*

なお、このパッケージでaccessor機能を使う場合は、今回説明したものとはネームスペースが違っているので気をつけてください。(その他の使い方は全く一緒です)

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Sukohi\FormRequestAppendable\FormRequestAppendable;

class TestRequest extends FormRequest
{
    // 省略

おわりに

ということで、今回はaccessor機能をFormRequest内でも使えるようにしてみました。これで保守管理もより良くなれば嬉しいです。

なお、今回のaccessorで使えるケースをまとめてみました。
こちらも参考にしてみてくださいね。

  • 名字と名前を結合して “full_name” をつくる
  • 郵便番号の前半と後半をつなげる
  • mb_convert_kana()を使って全角の英数字を半角に統一する
  • 日付の “/” を “-” に置き換える
  • カナカナで統一する

などなど

ぜひ皆さんも使ってみてくださいね。

ではでは〜!

「久しぶりに独自パッケージを公開しました😊」

この記事が役立ちましたらシェアお願いします😊✨