Laravel の FormRequest にアクセサ機能を追加するパッケージを作りました

バージョン2を作成しました

https://qiita.com/kazumacchi/items/4e44a977ef494caecfb3


今から入れていただける方は上をご利用ください

<br>
<br>
<br>
<br>



モデルアクセサを参考にして作りました
※ Model accessor がめっちゃ便利なので、FormRequest にも Laravel 標準でアクセサ追加出来るようにして欲しいです

モデルのアクセサに可能な限り寄せる事を目標に更新していくつもりでしたが、いつの間にか別方向に行っています
（[$immutable](#immutable), [$fill($fillable)](#fillfillable), [$guarded](#guarded), [$casts](##$casts), [$null_disabled](#casts), [$empty_disabled](#empty_disabled), [$enabled](#enabled), [$disabled](#disabled) オプションとか）

不具合報告、機能要望などございましたらコメントでも github の issue でもいいのでお知らせいただけましたら嬉しいです！！！！

# アップデートのお知らせ
## v1.9.0 - 2023/03/31
- $validated_accessor を追加
プロパティを指定した場合、validated() の返却値にアクセサの値も追加されます。


# packagist
https://packagist.org/packages/kanagama/laravel-add-formrequest-accessor

# github

https://github.com/kanagama/laravel-add-formrequest-accessor

# インストール可能なPHPバージョン
PHP8 以上
Laravel 8以上


# インストール
```bash
composer require kanagama/laravel-add-formrequest-accessor:1.*
```

# 最新版にアップデートする場合
```bash
composer update -w kanagama/laravel-add-formrequest-accessor
```


# 使い方

ただの Trait なので、使いたい 自作 Request で use して

```php
namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
// 追加
use Kanagama\FormRequestAccessor\FormRequestAccessor;

/**
 * @property-read string $last_name
 * @property-read string $first_name
 * @property-read string $full_name
 */
class BookingRequest extends FormRequest
{
    // 追加
    use FormRequestAccessor;
```

あとは、自作 Request クラス内で getXXXXXXXXXAttribute() を作成するだけ。

```php
namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Kanagama\FormRequestAccessor\FormRequestAccessor;

/**
 * @property-read string $last_name
 * @property-read string $first_name
 * @property-read string $full_name
 */
class BookingRequest extends ParentRequest
{
    use FormRequestAccessor;

    // 追加
    /**
     * 氏名
     *
     * @return string
     */
    public function getFullNameAttribute(): string
    {
        // '山田'
        // $this->input('last_name') 

        // '太郎'
        // $this->input('first_name')

        return $this->input('last_name') . ' ' . $this->input('first_name');
    }
```

controller とかでアクセス

```php
/**
 * @method View index(BookingRequest $request)
 */
class BookingController extends Controller
{
    /**
     * @param  BookingRequest  $request
     * @return View
     */
    public function index(BookingRequest $request): View
    {
        // full_name プロパティが追加されます
        // [
        //     'last_name' => '山田',
        //     'first_name' => '太郎',
        //     'full_name' => '山田 太郎',
        // ]
        dd($request->all());
        // '山田 太郎'
        dd($request->full_name);
    }
```

# オプション

## $immutable

true に設定した場合、[afterValidation()](https://qiita.com/kazumacchi/items/aebfe8dfccbfd28acaf4#aftervalidation%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89) 以降に merge(), replace(), offsetUnset(), offsetSet() ファンクションを呼び出すとImmutableException 例外が発生します（イミュータブルな Request クラスになります）

```php
/**
 * @property-read string $last_name
 * @property-read string $first_name
 * @property-read string $full_name
 */
class BookingRequest extends ParentRequest
{
    protected $immutable = true; 
}
```

```php
/**
 * @method View index(BookingRequest $request)
 */
class BookingController extends Controller
{
    /**
     * @param  BookingRequest  $request
     * @return View
     */
    public function index(BookingRequest $request): View
    {
        // ImmutableException が発生
        $request->merge([
            'last_name'  => '山田',
            'first_name' => '太郎', 
        ]);
    }
}
```



## $guarded

all() メソッドで取得したくないプロパティは $guarded に記述することで除去できます。
プロパティとしてはアクセスできます。

```php
/**
 * @property-read string $last_name
 * @property-read string $first_name
 * @property-read string $full_name
 */
class BookingRequest extends ParentRequest
{
    protected $guarded = [
        'last_name',
        'first_name',
    ];

    /**
     * 氏名
     *
     * @return string
     */
    public function getFullNameAttribute(): string
    {
        return $this->input('last_name') . ' ' . $this->input('first_name');
    }
}
```

```php
class BookingController extends Controller
{
    /**
     * @param  BookingRequest  $request
     * @return View
     */
    public function index(BookingRequest $request): View
    {
        // first_name, last_name が含まれない
        // [
        //     'full_name' => '山田 太郎',
        // ]
        dd($request->all());
        // '山田' が出力される
        dd($request->first_name);
    }
}
```


## $fill($fillable)

配列指定

all() メソッドで取得したいプロパティだけを記述することで、そのプロパティだけが出力されます。
指定されていなくてもプロパティとしてはアクセス可能です。
※同時に指定した場合、$fillable の設定が優先されます。

```php
/**
 * @property-read string $last_name
 * @property-read string $first_name
 * @property-read string $full_name
 */
class BookingRequest extends ParentRequest
{
    protected $fill = [
        'full_name',
    ];
    // もしくは
    protected $fillable = [
        'full_name',
    ];

    /**
     * 氏名
     *
     * @return string
     */
    public function getFullNameAttribute(): string
    {
        return $this->input('last_name') . ' ' . $this->input('first_name');
    }
}
```

```php
class BookingController extends Controller
{
    /**
     * @param  BookingRequest  $request
     * @return View
     */
    public function index(BookingRequest $request): View
    {
        // full_name のみ出力される
        // [
        //     'full_name' => '山田 太郎',
        // ]
        dd($request->all());
        // '山田' が出力される
        dd($request->first_name);
    }
}
```

## $casts

配列指定
型変換を行いたいプロパティを指定することで変換されます。
Model の $casts と同じです。

```php
/**
 * @property-read string $last_name
 * @property-read string $first_name
 * @property-read string $full_name
 */
class BookingRequest extends ParentRequest
{
    // どちらも int 型で 1 が格納されていると仮定
    protected $casts = [
        'id'       => 'string',
        'view_flg' => 'bool',
    ];
}
```

```php
/**
 * @method View index(BookingRequest $request)
 */
class BookingController extends Controller
{
    /**
     * @param  BookingRequest  $request
     * @return View
     */
    public function index(BookingRequest $request): View
    {
        // string 型で出力される
        // "1"
        dd($request->id);
        // bool 型で出力される
        // true
        dd($request->view_flg);
    }
}
```

## $null_disabled

bool 値指定
アクセサの戻り値が null のものを all() で出力しないか設定出来ます。

```php
class BookingRequest extends ParentRequest
{
    // bool 値で指定
    // true の場合は null が戻り値のアクセサを出力しません
    // 未指定の場合は false 指定の挙動になります
    protected $null_disabled = true;

    /**
     * @return string|null
     */
    public function getNullPropertyAttribute(): ?string
    {
        return null;
    }
}
```

```php
/**
 * @method View index(BookingRequest $request)
 */
class BookingController extends Controller
{
    /**
     * @param  BookingRequest  $request
     * @return View
     */
    public function index(BookingRequest $request): View
    {
        // null_property は未定義になり、出力されません
        // []
        dd($request->all());
        // 未定義のためエラー
        dd($request->null_property);
    }
}
```

## $empty_disabled

bool 値指定
戻り値が空 (※empty()チェックでtrue）のアクセサを all() で出力しないか設定出来ます。
$null_disabled も指定した場合、こちらが優先されます。

```php
class BookingRequest extends ParentRequest
{
    // bool 値で指定
    // true の場合は戻り値が空のアクセサを出力しません
    // 未指定の場合は false 指定の挙動になります
    protected $empty_guarded = true;

    protected $null_guarded = true;

    /**
     * @return string
     */
    public function getEmptyPropertyAttribute(): string
    {
        // 0 や null の場合も同様
        return '';
    }
}
```

```php
/**
 * @method View index(BookingRequest $request)
 */
class BookingController extends Controller
{
    /**
     * @param  BookingRequest  $request
     */
    public function index(BookingRequest $request)
    {
        // $null_guarded が指定されていますが、 $empty_disabled が優先されます
        // empty_property は未定義になり、出力されません
        // []
        dd($request->all());
        // 未定義のためエラー
        dd($request->empty_property);
    }
}
```

## $disabled

配列指定
$disabled で指定されたプロパティは、Request クラスから削除されます。
all() でも出力されません。

```php
class BookingRequest extends ParentRequest
{
    // 配列で指定
    protected $disabled = [
        'disabled_property',
    ];

    /**
     * @return string
     */
    public function getDisabledPropertyAttribute(): string
    {
        return 'sample';
    }
}
```

```php
/**
 * @method View index(BookingRequest $request)
 */
class BookingController extends Controller
{
    /**
     * @param  BookingRequest  $request
     */
    public function index(BookingRequest $request)
    {
        // disabled_property は未定義になり、出力されません
        // []
        dd($request->all());
        // 未定義のためエラー
        dd($request->disabled_property);
    }
}
```

## $enabled

配列指定
$enabled で指定されたプロパティ以外、Request クラスから削除されます。
all() でも出力されません。

$disabled と同時に指定した場合、こちらが優先されます。

```php
class BookingRequest extends ParentRequest
{
    // 配列で指定
    protected $enabled = [
        'enabled_property',
    ];

    protected $disabled = [
        'enabled_property',
    ];

    /**
     * @return string
     */
    public function getEnabledPropertyAttribute(): string
    {
        return 'sample';
    }
}
```

```php
/**
 * @method View index(BookingRequest $request)
 */
class BookingController extends Controller
{
    /**
     * @param  BookingRequest  $request
     * @rerurn View
     */
    public function index(BookingRequest $request): View
    {
        // $disabled, $enabled どちらも指定している場合、$enabled が優先されます
        // enabled_property のみ出力される
        // [
        //     'enabled_property' => 'sample',
        // ]
        dd($request->all());
        // 'sample' が出力されます
        dd($request->enabled_property);
    }
}
```

## $validated_accessor

bool値指定
true を設定した場合、validated() を実行した際、返却値にアクセサの値が追加されます。

```php
class BookingRequest extends ParentRequest
{
    /**
     * @var bool
     */
    protected $validated_accessor = true; 

    /**
     * @return string
     */
    public function getValidatedAccessorAttribute(): string
    {
        return 'sample';
    }
}
```

```php
/**
 * @method View index(BookingRequest $request)
 */
class BookingController extends Controller
{
    /**
     * @param  BookingRequest  $request
     * @rerurn View
     */
    public function index(BookingRequest $request): View
    {
        // BookingRequest にバリデーションがないので本来は空配列ですが
        // $validated_accessor が true のためアクセサが実行される
        // [
        //      'validated_accessor' => 'sample',
        // ]
        dd($request->validated());
    }}
```

## settingsメソッド
Request アクセサの設定値を dd() を使って出力します。
dd() を使っているのでその時点で処理が停止します。


```php
$request->settings();
```


```php
{#1748 ▼
  +"settings": array:6 [▼
    "immutable" => null,
    "fill" => null
    "guarded" => null
    "casts" => null
    "null_disabled" => false
    "empty_disabled" => false
  ]
  +"all": array:2 [▼
    "before_all" => {#1729}
    "after_all" => {#1718 ▼
      +"full_name": "山田 太郎"
    }
  ]
  +"accessor_methods": {#1737 ▼
    +"0": "getFullNameAttribute"
  }
}
```

## afterValidationメソッド

passedValidation() の後に動作するメソッドです。
passedValidation() の代替としてご利用下さい。

```php
class BookingRequest extends ParentRequest
{
    /**
     * passedValidation() 後に動作
     *
     * @return void
     */
    public function afterValidation(): void
    {

    }
}
```

## prepareForAccessorメソッド

アクセサを実行する前に動作するメソッド。
バリデーション実行後とアクセサ実行前の間に実行したい事がある場合にご利用下さい。
アクセサ実行前なので、アクセサにはアクセス出来ません。

```php
/**
 * @method View index(BookingRequest $request)
 */
class BookingRequest extends ParentRequest
{
    /**
     * アクセサ実行前に動作
     *
     * @return void
     */
    public function prepareForAccessor(): void
    {
        
    }

    // prepareForAccessor() 実行後に動作します。
    /**
     * 氏名
     *
     * @return string
     */
    public function getFullNameAttribute(): string
    {
        return $this->input('last_name') . ' ' . $this->input('first_name');
    }
}
```

## beforeAllメソッド

アクセサ実行前の Request インスタンスで all() を実行します。

```php
// /booking?id=1 でアクセスしていると仮定
class BookingRequest extends ParentRequest
{
    /**
     * @return int
     */
    public function getIdAttribute(): int
    {
        // string 型の id が 戻り値の型 int に変換される
        return $this->input('id');
    }
}
```

```php
/**
 * @method View index(BookingRequest $request)
 */
class BookingController extends Controller
{
    /**
     * @param  BookingRequest  $request
     * @return View
     */
    public function index(BookingRequest $request): View
    {
        // アクセサで変換される前の string "1" が出力される
        // [
        //      "id" => "1",
        // ]
        dd($request->beforeAll());
        // アクセサで変換された int 1 が出力される
        // [
        //      "id" => 1,
        // ]
        dd($request->all();
    }
}
```

## beforeメソッド
アクセサ実行前の Request インスタンスを取得できます。

```php
// /booking?id=1 でアクセスしていると仮定
class BookingRequest extends ParentRequest
{
    /**
     * @return int
     */
    public function getIdAttribute(): int
    {
        // string 型の id が 戻り値の型 int に変換される
        return $this->input('id');
    }
}
```

```php
/**
 * @method View index(BookingRequest $request)
 */
class BookingController extends Controller
{
    /**
     * @param  BookingRequest  $request
     */
    public function index(BookingRequest $request): View
    {
        // before() で返却されるのがアクセサが実行される前の Request インスタンスのため
        // string "1" が出力される
        // [
        //      "id" => "1",
        // ]
        dd($request->before()->id);
    }
}
```

## getControllerメソッド
コントローラー名を取得します

```php
/**
 * @method View index(BookingRequest $request)
 */
class BookingController extends Controller
{
    /**
     * @param  BookingRequest  $request
     * @return View
     */
    public function index(BookingRequest $request): View
    {
        // "BookingController" を出力
        dd($request->getController());
    }
```

## getActionメソッド
アクション名を取得します

```php
/**
 * @method View index(BookingRequest $request)
 */
class BookingController extends Controller
{
    /**
     * @param  BookingRequest  $request
     * @return View
     */
    public function index(BookingRequest $request): View
    {
        // "index" を出力
        dd($request->getAction());
    }
```


## 注意事項

1. Trait で passedValidation() をオーバーライドしているため、Request クラスで passedValidation() を利用出来ません。

2. passedValidation() で行う処理はアクセサみたいにプロパティ追加することがほとんどだと考えており、あまり問題視されないと思います（※願望）。
代替として [afterValidation()](#afterValidationメソッド) を用意したのでそちらをご利用下さい。

3. アクセサ処理は passedValidation() で実行しているため、passedValidation() より前の処理ではアクセサの値を取得出来ません。※validationData() など

4. 最新バージョンが安心です。

# 更新履歴

## v1.8.0 - 2022/10/19
- コントローラー名を取得する getController() メソッドを追加
- アクション名を取得する getAction() メソッドを追加

こういうメソッドは Route が適切かもしれませんが
別に Request で直に取得出来てもいいのかなと思いました！！！

## v1.7.2 - 2022/08/04
- immutable 時、replace(), offsetUnset(), offsetSet() も呼び出せないように修正
- fill と同じ挙動をするプロパティ fillable を追加
- 各プロパティ(※[$immutable](#immutable), [$fill($fillable)](#fillfillable), [$guarded](#guarded), [$casts](##$casts), [$null_disabled](#casts), [$empty_disabled](#empty_disabled), [$enabled](#enabled), [$disabled](#disabled)) に異なる型の値を入れたら例外を出すように修正
- Unit, Feature テストコードを追加
- テストコードで見つけた細かいバグの修正

## v1.7.1 - 2022/08/02
- null_disabled, empty_disabled が正常に動作しない不具合を修正
- input() の第2引数が正常に動作しない不具合を修正
- Feature テストコードを追加

## v1.7.0 - 2022/07/16
- [$immutable](https://qiita.com/kazumacchi/items/aebfe8dfccbfd28acaf4#immutable) プロパティを追加
- テストコードちょっと追加

## v1.6.0
- $enabled を追加
プロパティを指定した場合、指定したプロパティ以外は未定義になります。
all() でも出力されません。

- $disabled を追加
プロパティを指定した場合、指定したプロパティは未定義になります。
all() でも出力されません。

- prepareForAccessor() を追加
バリデーションの実行後、アクセサの実行前に動作する prepareForAccessor() を追加。

## v1.5.0
- $empty_disabled の不具合を修正
- beforeAll() メソッド追加。アクセサ実行前の Request インスタンスで all() を実行します。
- before() メソッド追加。アクセサ実行前の Request インスタンスを取得できます。

## v1.4.1

 - $casts の不具合対応
 - passedValidation() の後に動作する afterValidation() を追加
 - Request アクセサの設定値を取得する settings() を追加

## v1.4.0
 - $guarded が正常に動作しない不具合を修正
 - $fill が正常に動作しない不具合を修正
 - $null_disabled を追加
 - $empty_disabled を追加
 - $casts が model クラスと同じ挙動をするように修正

## v1.3.1
テストコードを追加

## v1.3.0
fill, casts（一部） に対応


<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NR3DM2YWBD"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());

  gtag('config', 'G-NR3DM2YWBD');
</script>