CakePHP3フォームヘルパーの自動入力タイプ設定を解説

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94360/9b4788f1-248c-97d9-971b-15462cd0ba3e.png)

CakePHPは、規約に沿ってコードを書くことで効率を最大化できるフレームワーク。フォームヘルパーを使ってフォームを自動生成する機能は、その一例と言えるかと。この記事では、CakePHPのフォームヘルパーがどのように入力タイプを自動設定するのか、具体的なカラム名やコンテキストの役割を解説します。

### フィールドタイプごとのチートシート

以下は、使用頻度が高いフィールドタイプごとに、どのようにコードを書けばそのタイプが生成されるかを示したチートシートです。それぞれのタイプについて、条件、生成方法、生成されるHTMLを示します。

#### 自動設定されるケース

#### `select`
- **条件**：カラム名が`_id`で終わる
- **生成方法**：`FormHelper::control('category_id')`
- **生成されるHTML**：
    ```html
    <select name="category_id">...</select>
    ```
_idサフィックスは他テーブルとの紐づけを示すためにも使われるので、混同を避けるために、selectカラムの自動設定には使わないという判断もあります。

#### `textarea`
- **条件**：オプションに`'rows'`または`'cols'`が含まれている
- **生成方法**：`FormHelper::control('description', ['rows' => '3'])`
- **生成されるHTML**：
    ```html
    <textarea name="description">...</textarea>
    ```

#### `checkbox`
- **条件**：オプションに`'checked'`が含まれている
- **生成方法**：`FormHelper::control('is_active', ['checked' => true])`
- **生成されるHTML**：
    ```html
    <input type="checkbox" name="is_active" checked="checked" />
    ```

#### `password`
- **条件**：カラム名が`passwd`、`password`などを含む
- **生成方法**：`FormHelper::control('password')`
- **生成されるHTML**：
    ```html
    <input type="password" name="password" />
    ```

#### `tel`
- **条件**：カラム名が`tel`、`telephone`、`phone`を含む
- **生成方法**：`FormHelper::control('phone')`
- **生成されるHTML**：
    ```html
    <input type="tel" name="phone" />
    ```

#### `email`
- **条件**：カラム名が`email`を含む
- **生成方法**：`FormHelper::control('email')`
- **生成されるHTML**：
    ```html
    <input type="email" name="email" />
    ```

#### `number`
- **条件**：カラムのタイプが`integer`、`float`、`decimal`
- **生成方法**：`FormHelper::control('price')`
- **生成されるHTML**：
    ```html
    <input type="number" name="price" />
    ```

#### `text`
- **条件**：その他のカラム、または特に指定がない場合
- **生成方法**：`FormHelper::control('username')`
- **生成されるHTML**：
    ```html
    <input type="text" name="username" />
    ```

#### 明示的に設定するタイプ

#### `datetime`
- **条件**：オプションに`'type' => 'datetime'`を指定
- **生成方法**：`FormHelper::control('appointment', ['type' => 'datetime'])`
- **生成されるHTML**：
    ```html
    <input type="datetime-local" name="appointment" />
    ```

#### `date`
- **条件**：オプションに`'type' => 'date'`を指定
- **生成方法**：`FormHelper::control('birthdate', ['type' => 'date'])`
- **生成されるHTML**：
    ```html
    <input type="date" name="birthdate" />
    ```

#### `time`
- **条件**：オプションに`'type' => 'time'`を指定
- **生成方法**：`FormHelper::control('meeting_time', ['type' => 'time'])`
- **生成されるHTML**：
    ```html
    <input type="time" name="meeting_time' />
    ```

### CakePHP 3.4と3.10(3系最終版)の違い

筆者が関わっているプロジェクトは3.4のため、違いについてまとめておきます。CakePHP 3.10には、CakePHP 3.4にない追加機能がいくつかあって、その中でも、特に重要な2つの違いについて説明します。ほとんどのユーザには不要な情報ですが・・

#### customValidityMessageの設定

**CakePHP 3.10での変更点**：
- **カスタム有効性メッセージ**：CakePHP 3.10では、フォームフィールドが必須の場合、エラーメッセージを設定することができます。このエラーメッセージは、HTML5の`setCustomValidity`メソッドを使用してクライアントサイドで表示されます。
- **イベントハンドラの自動設定**：`oninvalid`と`oninput`イベントが自動的に設定され、入力が無効な場合にカスタムメッセージが表示され、入力が有効になった場合にメッセージがクリアされます。

**CakePHP 3.4**：
- **対応していません**：CakePHP 3.4では、カスタム有効性メッセージや`oninvalid`、`oninput`イベントの自動設定はサポートされていません。そのため、手動でこれらの機能を実装する必要があります。

具体例として、CakePHP 3.7でのコード例を以下に示します。

```php
if (method_exists($context, 'getRequiredMessage')) {
    $message = $context->getRequiredMessage($fieldName);
    $message = h($message);

    if ($options['required'] && $message) {
        $options['templateVars']['customValidityMessage'] = $message;

        if ($this->getConfig('autoSetCustomValidity')) {
            $options['oninvalid'] = "this.setCustomValidity(''); if (!this.validity.valid) this.setCustomValidity('$message')";
            $options['oninput'] = "this.setCustomValidity('')";
        }
    }
}
```

#### maxlengthの設定の拡張

**CakePHP 3.10での変更点**：
- **maxlengthの設定**：CakePHP 3.10では、`maxlength`が自動的に設定されるようになりました。これは、コンテキストの`getMaxLength`メソッドを使用して、フィールドの最大長を取得し、その値がHTMLの`maxlength`属性として設定されます。

**CakePHP 3.4**：
- **対応していません**：CakePHP 3.4では、`maxlength`は自動的に設定されません。手動で設定する必要があります。

具体例として、CakePHP 3.10でのコード例を以下に示します。

```php
$typesWithMaxLength = ['text', 'textarea', 'email', 'tel', 'url', 'search'];
if (
    !array_key_exists('maxlength', $options)
    && in_array($options['type'], $typesWithMaxLength)
) {
    $maxLength = null;
    if (method_exists($context, 'getMaxLength')) {
        $maxLength = $context->getMaxLength($fieldName);
    }

    if ($maxLength === null && !empty($fieldDef['length'])) {
        $maxLength = $fieldDef['length'];
    }

    if ($maxLength !== null) {
        $options['maxlength'] = min($maxLength, 100000);
    }
}
```


### フォームヘルパーの自動入力タイプ設定の仕組み

フォームヘルパーの`_inputType`メソッドは、以下の手順で入力タイプを自動設定します。

1. **カラム名のチェック**：
    - カラム名が`*_id`で終わる場合、`select`タイプを返します。
    - カラム名が`password`や`tel`、`email`など特定の名前の場合、それに対応するタイプを返します。

2. **内部型の取得**：
    - コンテキストを通じてフィールドの内部型を取得します。これにより、データベースの型情報に基づいて適切な入力タイプを決定します。

3. **オプションのチェック**：
    - `checked`や`options`オプションが設定されている場合、それに応じたタイプを返します。

4. **デフォルトの型マッピング**：
    - 型マッピングに基づいて、デフォルトの入力タイプを決定します。例えば、`string`型は`text`、`boolean`型は`checkbox`となります。

### コンテキストの役割

フォームヘルパーは、コンテキストを使用してフォームフィールドの情報を取得します。コンテキストは、フォームに関連するデータやメタデータを提供し、フォーム生成をサポートします。以下のようなコンテキストが存在します。

- **`EntityContext`**：データベースのエンティティやそのコレクションに基づくコンテキスト。
- **`ArrayContext`**：配列データに基づくコンテキスト。
- **`FormContext`**：カスタムフォームオブジェクトに基づくコンテキスト。
- **`NullContext`**：特に情報がない場合に使用されるデフォルトのコンテキスト。

### コード例

以下に、実際のコード例を示します。これにより、フォームヘルパーがどのようにコンテキストを使用して入力タイプを決定するのかがわかります。

```php
protected function _inputType($fieldName, $options)
{
    $context = $this->_getContext();

    if ($context->isPrimaryKey($fieldName)) {
        return 'hidden';
    }

    if (substr($fieldName, -3) === '_id') {
        return 'select';
    }

    $internalType = $context->type($fieldName);
    $map = $this->_config['typeMap'];
    $type = isset($map[$internalType]) ? $map[$internalType] : 'text';
    $fieldName = array_slice(explode('.', $fieldName), -1)[0];

    switch (true) {
        case isset($options['checked']):
            return 'checkbox';
        case isset($options['options']):
            return 'select';
        case in_array($fieldName, ['passwd', 'password']):
            return 'password';
        case in_array($fieldName, ['tel', 'telephone', 'phone']):
            return 'tel';
        case $fieldName === 'email':
            return 'email';
        case isset($options['rows']) || isset($options['cols']):
            return 'textarea';
    }

    return $type;
}
```

### どこで確認できる？

1. **ソースコードの場所**：フォームヘルパーの自動入力タイプ設定は、CakePHP本体 `FormHelper`クラスの`_inputType`メソッドで行われます。このメソッドは、`/cakephp/src/View/Helper/FormHelper.php`にあります。
2. **データベーススキーマ**：内部型の取得は、`$context->type($fieldName)`で行われます。これにより、データベースのスキーマ情報が利用されます。
3. **コンテキストの確認**：フォームのコンテキストは、`_getContext`メソッドで取得されます。このメソッドは、`FormHelper`クラスの中にあります。

### まとめ

CakePHPのフォームヘルパーを利用することで、フォームフィールドの生成がすっきり簡単になります。規約に従ったカラム名や適切なコンテキストの使用により、入力タイプが自動的に設定されるため、開発者はコードを書く手間を大幅に削減でき、効率的なアプリケーション開発を加速させることができます。また、コードが読みやすくなり、メンテナンス性が向上するため、チーム開発においても大きなメリットがあります。個人的にはこのメリットが一番大きく感じられます。

この記事を通じて、CakePHPのフォームヘルパーの自動入力タイプ設定について理解が深まったでしょうか？ ぜひ、実際のプロジェクトで試してみてください！