開発ブログ

株式会社Nextatのスタッフがお送りする技術コラムメインのブログ。

電話でのお問合わせ 075-744-6842 ([月]-[金] 10:00〜17:00)

  1. top >
  2. 開発ブログ >
  3. PHP >
  4. LaravelのFormRequestをPSR-15対応で再現しようとした

LaravelのFormRequestをPSR-15対応で再現しようとした

こんにちは、ナカエです。

 

LaravelのFormRequestは便利ですよね。

コントローラのアクションの処理を変更することなく、事前にHTTPリクエスト入力のバリデーションを行うことができます。

PSR-15対応のフレームワークにおいて、このFormRequestの機能の一部を再現するライブラリを試作したので、本日はその紹介記事です。

要件

  1. RequestHandlerにユーザが定義したFormRequestを注入してバリデーションと認可の処理を行える
  2. FormRequestのバリデーションと認可の記述をLaravelのように手軽に行える
  3. FormRequestの継承階層は抽象クラス1段までで抑える
  4. MiddlewareディスパッチャやDIコンテナの実装に依らない

2はLaravelのFormRequestの継承階層が深すぎるという不満があったための制約です。 Laravelフレームワークのユーザが作成する独自のFormRequestクラスは、Symfony HTTP FoundationのRequest、Laravelの Illuminate HTTP Request、FormRequestを継承する事になっており、お世辞にも美しい設計とは言えません。

3はPSR-15という共通のインターフェースを無駄にしないための制約です。Webフレームワークの主要機能の一つであるMiddlewareやRequestHandler(またはController, Action)のディスパッチにPSR-15以外の制約が入るようでは、あまり使い勝手がいいとは言えません。

成果物

n1215/psr-15-form-request

FormRequestはPSR-15 Middlewareとして実装し、先日の記事のようにRequestHandler内でFormRequestのmiddlewareをディスパッチするという構成にしました。バリデーション機構にはilluminateのvalidationパッケージをそのまま使っています。

使い方

<?php

use Psr\Http\Message\ServerRequestInterface;

// 1. FormRequestMiddlewareInterfaceを実装したクラスを作成。抽象クラスを使うとLaravelと似た感じで書ける。

class YourFormRequest extends \N1215\PSR15FormRequest\FormRequest
{
    public function authorize(ServerRequestInterface $request): bool
    {
        return true;
    }
    protected function rules(): array
    {
        return [
            'id' => ['required', 'integer'],
        ];
    }
    protected function messages(): array
    {
        return [];
    }
    protected function attributes(): array
    {
        return [
            'id' => 'ID',
        ];
    }
}

// 2. FormRequestHandlerを作成。handle()の呼び出し時にFormRequestMiddlewareを通してからinnerHandle()を呼び出す処理になっている。

use Psr\Http\Message\ResponseInterface;

class YourRequestHandler extends \N1215\PSR15FormRequest\FormRequestHandler
{
    /**
     * @param YourFormRequest $formRequest
     */
    public function __construct(YourFormRequest $formRequest)
    {
        parent::__construct($formRequest);
    }
    protected function innerHandle(ServerRequestInterface $request): ResponseInterface
    {
        return new \Zend\Diactoros\Response\JsonResponse(['message' => 'handled.']);
    }
}

// 3. FormRequestMiddlewareをインスタンス化。illuminate/validationのパッケージとPSR-17 ResponseFactory, StreamFactoryに依存
use Illuminate\Filesystem\Filesystem;
use Illuminate\Translation\Translator;
use Illuminate\Translation\FileLoader;
use Illuminate\Validation\Factory;
use Zend\Diactoros\ResponseFactory;
use Zend\Diactoros\StreamFactory;

$langDirPath = __DIR__ . '/../resources/lang';
$validationFactory = new Factory(new Translator(new FileLoader(new Filesystem(), $langDirPath), 'en'));
$errorResponder = new \N1215\PSR15FormRequest\FormRequestErrorResponder(new ResponseFactory(), new StreamFactory());
$formRequest = new YourFormRequest($validationFactory, $errorResponder);

// 4. FormRequestHandlerをインスタンス化
$handler = new YourRequestHandler($formRequest);

// 5. PSR-15のRequestHandlerとして使う
use Zend\Diactoros\ServerRequestFactory;
use Zend\HttpHandlerRunner\Emitter\SapiEmitter;

$request = ServerRequestFactory::fromGlobals();
$response = $handler->handle($request);
(new SapiEmitter())->emit($response);

感想

LaravelのFormRequestの利便性はさすがですね。DIコンテナやディスパッチャ、HTTPリクエストと密結合気味なものの、その代わりにユーザ側の便利さを実現しているということでしょう。今回は利用しませんでしたが、AOPのライブラリを使うなどしないと、疎結合を保ったままここまでの利便性を再現するのはなかなか難しそうです。

  • posted by Nextatスタッフ
  • PHP , PSR
TOPに戻る