【画像あり】「どちらか一方は入力必須」のvalidationをCakePHP3.8で行う

# 今回のお題

---

CakePHP3.8を使って実装を進めた際に起きました。
調べてみても意外にヒットしなかったので、自分なりの対処方法を残しておきます。

---

## 環境
PHP: 5.6.40
CakePHP: 3.8
OS: CentOS7.2
DB: MySQL5.7.21

今さらPHP5.6かよ、というのは置いておき。。

---

## 画面イメージ

---

よくあるこのような画面です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130324/4ddbe92d-6d59-c057-6819-fb4c670b50e2.png)

今回は身長か体重のどちらかは入力必須(両方入力されていてもOK)というお話です。

---

## CakePHP3実装

---

### model

---

Usersモデルに以下のカラムがある、という前提で進めます。
身長： height
体重： weight

```src/Model/Table/UsersTable.php
    public function validationDefault(Validator $validator)
    {
        $validator
            ->notEmptyString('height', '身長か体重のいずれかを入力してください。', function ($context) {
                return (bool) (!$context['data']['height']) && (!$context['data']['weight']);
            });

        return $validator;
    }
```

カスタムバリデーションで `function($value, $context)` を使いますが
$valueは当該の項目、$contextはリクエスト全体をそれぞれ定義します。
今回は$contextを使ってどちらの項目もチェックする、ということをしています。

---

### view

---

```src/Template/Users/edit.ctp
    <dt class="title">身長/体重<br>※いずれか必須<span class="must">必須</span></dt>
    <dd class="input">
        <?= $this->Form->control('height', [
            'type' => 'text',
            'label' => false,
            'required' => false,
        ]) ?> cm
        <?= $this->Form->control('weight', [
            'type' => 'text',
            'label' => false,
            'required' => false,
        ]) ?> kg
    </dd>
```

こちらでは特に変わったことはしていませんね。

---

### 結果イメージ

---

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130324/1c8b43ec-dd92-7193-d9ec-becb9b11aa1a.png)

身長のエラーとして表示されることがわかります。

---

## おまけ

---

さらに身長・体重にそれぞれバリデーションをかける場合も一緒に記載します。

前提条件
・身長・体重のどちらかは入力必須。
・どちらも半角数字での入力しか認めない。

---

### modelにルールを追加

---

```src/Model/Table/UsersTable.php
    public function validationDefault(Validator $validator)
    {
        $validator
            ->notEmptyString('height', '身長か体重のいずれかを入力してください。', function ($context) {
                return (bool) (!$context['data']['height']) && (!$context['data']['weight']);
            })
            ->numeric('height', '身長は半角数字で入力してください。');

        $validator
            ->allowEmptyString('weight', null)
            ->numeric('weight', '体重は半角数字で入力してください。');

        return $validator;
    }
```

heightにnumericのvalidationルールを
weightにはallowEmptyStringとnumericのルールを追加しました。

---

### 実行

---

先ほどのviewをそのまま使うと以下のようになってしまいます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130324/be36f791-7921-4c88-bdc7-16ef50402aec.png)

validationメッセージが表示されると、全体的にレイアウトが崩れてしまいました。
まあ、これはviewの書き方が悪い、とも言えますが。。。

---

### 対応策

---

viewを以下のように変更します。

```src/Template/Users/edit.ctp
    <dt class="title">身長/体重<br>※いずれか必須<span class="must">必須</span></dt>
    <dd class="input">
        <?= $this->Form->control('height', [
            'type' => 'text',
            'label' => false,
            'required' => false,
            'error' => false,
        ]) ?> cm
        <?= $this->Form->control('weight', [
            'type' => 'text',
            'label' => false,
            'required' => false,
            'error' => false,
        ]) ?> kg
        <?= $this->Form->error('height') ?>
        <?= $this->Form->error('weight') ?>
    </dd>
```

フォームではエラー表示をオフにし、別のところでエラーを出すようにしました。

---

### 結果

---

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130324/3750ed76-7e44-e96c-6204-6dc626256266.png)

崩れていたレイアウトもスッキリしましたね。

まだ需要があるかはわかりませんが、どなたかのお役に立てたのなら嬉しいです。