[Practical BEAR.Sunday] Ray.WebFormModule（Aura.Filter v2）カスタムバリデーションの作成

[BEAR.Sunday のフォーム](http://bearsunday.github.io/manuals/1.0/ja/form.html) で `Aura.Filter（v2.x）` のカスタムルールを使ってバリデーションを行う例を紹介します。

## Ray.WebFormModule のフォームバリデーション

[Ray.WebFormModule](https://github.com/ray-di/Ray.WebFormModule)  では入力フィールドの検証に [Aura.Filter](https://github.com/auraphp/Aura.Filter) （v2.x） が利用されます。具体的には下図のように `SubjectFilter` クラスを使います。

- フォーム抽象クラス（※バリデーションに関わる箇所の抜粋）

![Untitled Diagram (6) (3).png](https://qiita-image-store.s3.amazonaws.com/0/15295/87ab5b3b-5ee8-8982-52a2-7e6f048bcff8.png)

SubjectFilter は個々のフィールドに対するルール定義を通して要素の集合に対する検証を行います。要素の集合とは配列またはオブジェクトで、これをサブジェクトと呼びます。

- [Aura.Filter v2.x マニュアル "SubjectFilter"](https://github.com/auraphp/Aura.Filter/blob/2.x/docs/subject-filter.md)

フォームでカスタムバリデーションを行いたい場合は SubjectFilter にその設定を加える必要があります。設定できる箇所はフォームクラスのセッタにて定義されている `FilterFactory` クラスです。

## カスタムバリデーションの実装

下記の例で考えます。

- `「（商品の注文操作時に）商品の在庫があるかどうか」`

の検証をするとします。

- 注文業務の仕様（商品在庫あり）を満たすかどうかのチェック
- 複数のフィールドにまたがった検証（`商品ID` と `数量` の２項目を参照）

サンプルコードの Github はこちら [kumamidori/ExampleSpecForm](https://github.com/kumamidori/ExampleSpecForm)です。

### 業務の仕様

```php
// 商品在庫有り仕様
class InStockItemSpecification implements SpecificationInterface
{
    public function isSatisfiedBy($order)
    {
        // 実際にはここでDB検索を呼び出して $order の在庫があるかをチェック
        return false;
    }
}
```

### AuraFilter の設定

- バリデータ（業務の仕様を使う）

```php
class ValidateInStockItem implements ValidateInterface
{
    /**
     * @Inject
     */
    private $spec;

    public function __construct(InStockItemSpecification $spec)
    {
        $this->spec = $spec;
    }

    public function __invoke($subject, $field)
    {
        // 実際にはここでフォーム要素群（$subject）からバリデーションに必要な値セット（$order）へと変換
        $order = $subject;

        return $this->spec->isSatisfiedBy($order);
    }
}
```

- `ロケータファクトリ （Locator Factories）` を設定（プロバイダー）

カスタムルールのためのファクトリ設定。 `FilterFactory` コンストラクタからロケータに渡される。

```php
class FilterFactoryProvider implements ProviderInterface
{
    private $validateFactories;

    /**
     * @Inject
     */
    public function __construct(
        ValidateInStockItem $validateInStockItem
    )
    {
        $this->validateFactories = [
            'app.in-stock-item' => function () use ($validateInStockItem) {
                return $validateInStockItem;
            },
        ];
    }

    public function get()
    {
        return (new FilterFactory($this->validateFactories));
    }
}
```

※ ここで設定するルール名がグローバルである点（名前空間のようなグルーピングが必要か）が気になりました。ここではライブラリ同梱のルール名と区別できる規約として `app.` プレフィックスを付けました。

※設定箇所を `プロバイダー（Provider）` のコンストラクタにしたので、設定の知識がわかりづらい課題があります（これについては[別記事](https://qiita.com/kumamidori/items/5c364a5219cec3698ec3)で別途改善を考えました）。

### フォーム側

作ったルールを使うだけです。

```php
class ItemSelectForm extends AbstractForm
{
    use InStockItemFilterInject;

    /**
     * {@inheritdoc}
     */
    public function init()
    {
        $this->setField('item_id');
        $this->setField('quantity');
　
        $this->filter->validate('quantity')->is('app.in-stock-item');
        $this->filter->useFieldMessage('item_id', '選択された商品は在庫切れとなっております。');
    }
}
```

### モジュールでの束縛

- FilterFactory をプロバイダーに束縛

```php
        $this->bind(FilterFactory::class)->toProvider(FilterFactoryProvider::class);
        $this->bind(FormInterface::class)->annotatedWith('app.itemSelectForm')->to(ItemSelectForm::class);
        $this->bind(InStockItemSpecification::class);
        $this->bind(ValidateInStockItem::class);
```

## 補足

- ここではカスタムルール機能を利用しましたが、ユーザインターフェイスのバリデーションであれば Aura.Filter マニュアル記載の標準機能のみでまかなえるケースも多いです。必須とオプションの区別も標準機能でできます。

- 上記は一例です。BEARではフォームライブラリを自由に選ぶこともできます（参考記事：
 [BEAR.SundayとZF2 Formを使ったフルスタックスタイルフォームの作り方 by zingooo](https://qiita.com/zingooo/items/46c9e49375270c612172) ）


## 関連モジュール

- [satomif/ExtraAuraFilterModule](https://github.com/satomif/ExtraAuraFilterModule/)

こちらは設定ファイルのみでカスタムバリデーション設定を行うモジュールです。制約があって、設定からユーザサイドでオブジェクト生成を行うものなので、DI基盤を使いたい場合には利用することができません。

- [kumamidori/DiAuraFilterModule](https://github.com/kumamidori/DiAuraFilterModule)

DI基盤を使いたいニーズに対応するためにDIインジェクタを実装に利用したモジュールです。紹介記事は[こちら](https://qiita.com/kumamidori/items/5c364a5219cec3698ec3)。

## 関連リンク

- [BEAR\.Sundayのフォーム - Qiita](https://qiita.com/koriym/items/8e19dd874088b2e601e2)
- [Aura\.Filter クロージャを使ったカスタムバリデーション by shin1x1 - Qiita](https://qiita.com/shin1x1/items/82cf51d18e3a816c5cab)