Laravel の Form Request Validation と Yii の Model を継承したフォームクラスの違いについて

## はじめに
Laravel と Yii どちらともにアクション毎にフォームのバリデーションを分けたいとき、Laravel では Form Request Validation、Yii では Model を継承したフォームクラスを使えるんですが、役割に若干の違いがあるので、そこらへんをまとめたいと思います。

## 環境
Laravel 6.x
Yii 2.x

## Laravel Form Request Validation

モデル:

```php:app/Models/Post.php
declare(strict_types=1);

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    protected $table = 'post';

    protected $fillable = [
        'title',
        'content',
    ];

    // ...
}
```

Form Request:

```php:app/Http/Requests/PostStore.php
declare(strict_types=1);

namespace App\Http\Requests;

use Illuminate\Contracts\Validation\Validator;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\ValidationException;

class PostStore extends FormRequest
{
    public function authorize(): bool
    {
        return true;
    }

    public function attributes(): array
    {
        return [
            'title' => 'Title',
            'content' => 'Content',
        ];
    }

    public function rules(): array
    {
        return [
            'title' => 'required|max:100',
            'content' => 'required|max:10000',
        ];
    }

    protected function prepareForValidation(): void
    {
        // バリデーション前に何かを処理したい場合、ここに書くことができる
    }

    protected function failedValidation(Validator $validator): void
    {
        // バリデーション失敗時に何かを処理したい場合、ここに書くことができる

        throw new ValidationException($validator);
    }

    protected function passedValidation(): void
    {
        // バリデーションが通ったあとに何かを処理したい場合、ここに書くことができる
    }
}
```

コントローラー:

```php:app/Http/Controllers/PostController.php
declare(strict_types=1);

namespace App\Http\Controllers;

use App\Http\Requests\PostStore;
use App\Models\Post;
use Illuminate\Http\RedirectResponse;
use Illuminate\View\View;

class PostController extends Controller
{
    // ...
    
    public function create(): View
    {
        return view('post.create');
    }

    public function store(PostStore $request): RedirectResponse
    {
        Post::create($request->all());

        return redirect()->route('post.index');
    }
}
```


## Yii の Model を継承したフォームモデル

モデル:

```php:models/Post.php
declare(strict_types=1);

namespace app\models;

use yii\db\ActiveRecord;

/**
 * @property int $id
 * @property string $title
 * @property string $content
 */
class Post extends ActiveRecord
{
    public static function tableName(): string
    {
        return 'post';
    }
}
```

フォームモデル:

```php:models/PostCreateForm.php
declare(strict_types=1);

namespace app\models;

use yii\base\Model;

class PostCreateForm extends Model
{
    public $title;
    public $content;

    public function attributeLabels(): array
    {
        return [
            'title' => 'Title',
            'content' => 'Content',
        ];
    }

    public function rules(): array
    {
        return [
            [['title', 'content'], 'trim'],
            [['title', 'content'], 'required'],
            ['title', 'string', 'max' => 100],
            ['content', 'string', 'max' => 10000],
        ];
    }

    public function beforeValidate(): bool
    {
        // バリデーション前に何かを処理したい場合、ここに書くことができる

        return parent::beforeValidate();
    }

    public function afterValidate(): void
    {
        // バリデーション後に何かを処理したい場合、ここに書くことができる

        parent::afterValidate();
    }

    public function save(): bool
    {
        // 処理が単純な場合、beforeValidate(), afterValidate() を使わずに validate() の前後に処理を書くこともできる
        if ($this->validate()) {
            // 例えばデータ保存処理

            // データ保存後に何かを処理したい場合、ここに書くことができる

            return true;
        }

        // バリデーションの失敗時に何かを処理したい場合、ここに書くことができる

        return false;
    }
}
```

コントローラー:

```php:controllers/SiteController.php
declare(strict_types=1);

namespace app\controllers;

use app\models\PostCreateForm;
use Yii;
use yii\web\Controller;
use yii\web\Response;
// ...

class SiteController extends Controller
{
    // ...

    /**
     * @return string|Response
     */
    public function actionCreate()
    {
        $post = new PostCreateForm;

        if ($post->load(Yii::$app->request->post()) && $post->save()) {
            return $this->redirect(['index']);
        }

        return $this->render('index', [
            'post' => $post,
        ]);
    }
}
```

## まとめ
Laravel の Form Request Validation は「ユーザーがリソースを更新する権限を持っているかの判断」と「ユーザーのフォーム入力に対してのバリデーション処理」と「バリデーション失敗時のリダイレクト処理」という役割を担っていて、Yii のモデルを継承したフォームクラスは、基本的に「ユーザーのフォーム入力に対してのバリデーション処理のみ」がメインで、あとの諸々の処理 (ログイン処理であったり、データの保存処理であったり) は適当にそこでやってくれという感じで、わりとモデルとの関わりが濃い印象でした ([Model](https://www.yiiframework.com/doc/api/2.0/yii-base-model) を継承しているので当たり前ですが) 。

## リンク
[Laravel Form Request Validation](https://laravel.com/docs/6.x/validation#form-request-validation)
[Yii フォームを扱う](https://www.yiiframework.com/doc/guide/2.0/ja/start-forms)