Skip to content

Latest commit

 

History

History
258 lines (203 loc) · 9.64 KB

File metadata and controls

258 lines (203 loc) · 9.64 KB

Ray.WebFormModule

Continuous Integration Coding Standards Scrutinizer Code Quality

Ray.WebFormModuleはアスペクト指向でフォームのバリデーションを行うモジュールです。 フォームライブラリにはAura.Inputを使い、 特定のアプリケーションフレームワークの依存なしで利用できます。

Installation

Composer install

$ composer require ray/web-form-module

Module install

use Ray\Di\AbstractModule;
use Ray\WebFormModule\WebFormModule;

class AppModule extends AbstractModule
{
    protected function configure()
    {
        $this->install(new WebFormModule());
    }
}

互換性のため Ray\WebFormModule\AuraInputModule クラスも WebFormModule の薄い サブクラスとして残されています。新規コードでは WebFormModule を使ってください。

Usage

Form

init()メソッドでフォームのinput要素を登録とルールの設定を行います。

use Ray\WebFormModule\AbstractForm;
use Ray\WebFormModule\SetAntiCsrfTrait;

class MyForm extends AbstractForm
{
    /**
     * {@inheritdoc}
     */
    public function init()
    {
        // set input fields
        $this->setField('name', 'text')
             ->setAttribs([
                 'id' => 'name'
             ]);
        // set input filters
        $this->filter->validate('name')->is('alnum');
        $this->filter->useFieldMessage('name', 'Name must be alphabetic only.');
    }
}

メソッドの引数を名前付き引数にしたものがフォームオブジェクトに渡されバリデーションされます。

// このメソッドの場合['id' => $id, 'name' => $name]配列が渡されます
public function createAction($id, $name, $body)
{

Ray\WebFormModule\WebFormModule\SubmitInterfaceを実装するとsubmit()メソッドで返された値がフォームオブジェクトに渡されます。

    /**
     * {@inheritdoc}
     */
    public function submit()
    {
        return $_POST;
    }
}

init()メソッドでで利用できるメソッドについて詳しく`はAura.Inputをご覧ください

Controller

コントローラークラスにフォームをインジェクトします。フォームのバリデーションを行うメソッドを#[FormValidation]で アノテートします。この時フォームのプロパティ名をformで、バリデーションが失敗したときのメソッドをonFailureで指定します。

use Ray\Di\Di\Inject;
use Ray\Di\Di\Named;
use Ray\WebFormModule\Annotation\FormValidation;
use Ray\WebFormModule\FormInterface;

class MyController
{
    /**
     * @var FormInterface
     */
    protected $contactForm;

    #[Inject]
    public function setForm(#[Named("contact_form")] FormInterface $form)
    {
        $this->contactForm = $form;
    }

    #[FormValidation(form: "contactForm", onFailure: "badRequestAction")]
    public function createAction()
    {
        // validation success
    }

    public function badRequestAction()
    {
        // validation faild
    }
}

View

フォームのinput要素やエラーメッセージを取得するには要素名を指定します。

  echo $form->input('name'); // <input id="name" type="text" name="name" size="20" maxlength="20" />
  echo $form->error('name'); // "Name must be alphabetic only." or blank.

CSRF Protections

CSRF対策は opt-in で、独立した 2 つの経路のいずれかで有効化できます。

  • フォーム単位: フォームに use SetAntiCsrfTrait; を追加します。 Ray.Di がトレイトの #[Inject] setter 経由で AntiCsrfInterface を注入し、 postConstruct() でトークンフィールドが追加され、apply() の呼び出しごとに トークンが検証されます。
  • アクション単位: バリデーション対象のコントローラメソッドに #[CsrfProtection] を付与します。AuraInputInterceptorapply() 実行前に AntiCsrfInterface をフォームへ注入します。

どちらの経路でも、トークン不一致時には AbstractForm::apply()CsrfViolationException を throw します。どちらも使わない場合は CSRF 検証は 行われません。両方を併用しても害はありませんが冗長なので、用途に合わせて どちらか一方を選んでください。

アクション単位 — コントローラのメソッドで宣言:

use Ray\WebFormModule\Annotation\CsrfProtection;
use Ray\WebFormModule\Annotation\FormValidation;

class MyController
{
    #[FormValidation(form: "contactForm")]
    #[CsrfProtection]
    public function createAction()
    {
    }
}

フォーム単位 — フォーム自身で宣言:

use Ray\WebFormModule\AbstractForm;
use Ray\WebFormModule\SetAntiCsrfTrait;

class MyForm extends AbstractForm
{
    use SetAntiCsrfTrait;
}

セキュリティレベルを高めるためにはユーザーの認証を含んだカスタムCsrfクラスを作成してフォームクラスにセットします。 詳しくはAura.InputのApplying CSRF Protectionsをご覧ください。

0.x からのマイグレーション

1.0 で Doctrine Annotations を廃止し、PHP 8 Attributes に完全移行しました。 型宣言も強化されています。主な書き換え:

Before (0.x) After (1.0)
@FormValidation(form="f", onFailure="badRequest") #[FormValidation(form: 'f', onFailure: 'badRequest')]
@FormValidation(form="f", antiCsrf=true) #[FormValidation(form: 'f')] + #[CsrfProtection]
@InputValidation(form="f") #[InputValidation(form: 'f')]
@VndError(message="...", logref="...") #[VndError(message: '...', logref: '...')]
new AuraInputInterceptor($injector, $reader) new AuraInputInterceptor($injector) (Reader 引数は不要)
public function input($input) / public function error($input) public function input(string $input): string / error(string $input): string

破壊的変更の完全なリストは CHANGELOG.md を参照してください。

Claude Code による自動マイグレーション

リポジトリ同梱の Claude Code skill .claude/skills/migrate-to-1.0/SKILL.md が上記の書き換え (アノテーション → アトリビュート、antiCsrf=true#[CsrfProtection] 分割、Reader 引数削除、FormInterface 署名更新) を AI アシスタントに案内します。利用側プロジェクトの .claude/skills/ に ディレクトリをコピーして /migrate-to-1.0 で起動してください。

Validation Exception

#[FormValidation]の代わりに#[InputValidation]とアノテートするとバリデーションが失敗したときにRay\WebFormModule\Exception\ValidationExceptionが投げられるよになります。この場合はHTML表現は使われません。Web APIアプリケーションなどに便利です。

use Ray\WebFormModule\Annotation\InputValidation;

class Foo
{
    #[InputValidation(form: "form1")]
    public function createAction($name)
    {
      // ...
    }

以下のように Ray\WebFormModule\FormVndErrorModuleをインストールするとフォームのバリデーションが失敗したときにRay\WebFormModule\Exception\ValidationException例外が投げられるよになります。

use Ray\Di\AbstractModule;

class FakeVndErrorModule extends AbstractModule
{
    protected function configure()
    {
        $this->install(new WebFormModule());
        $this->override(new FormVndErrorModule());
    }

キャッチした例外のerrorプロパティをechoするとapplication/vnd.error+jsonメディアタイプの表現が出力されます。

http_response_code(400);
echo $e->error;

//{
//    "message": "Validation failed",
//    "path": "/path/to/error",
//    "validation_messages": {
//        "name": [
//            "Name must be alphabetic only."
//        ]
//    }
//}

#[VndError]属性でvnd.error+jsonに必要な情報を加えることができます。

    #[FormValidation(form: "contactForm")]
    #[VndError(message: "foo validation failed", logref: "a1000", path: "/path/to/error", href: ["_self" => "/path/to/error", "help" => "/path/to/help"])]

このオプションのモジュールはAPIアプリケーションの時に有用です。

Demo

$ php -S docs/demo/1.csrf/web.php