1. Home
  2. PHP
  3. CakePHP
  4. CakePHP3のFormHelper入門編&フォームHTML出力を独自テンプレートで行う

CakePHP3のFormHelper入門編&フォームHTML出力を独自テンプレートで行う

  • 公開日
  • 更新日
  • カテゴリ:CakePHP
  • タグ:PHP,CakePHP,FormHelper
CakePHP3のFormHelper入門編&フォームHTML出力を独自テンプレートで行う

CakePHP3 には、フォームを作成する際に FormHelper という機能を使うと、フォーム作成が簡単に行えます。

今回は CakePHP3 の FormHelper の入門を行い、フォーム HTML を独自テンプレートで出力するまでを行います。

Contents

  1. 開発環境
  2. FormHelper を使う前に
  3. マイグレーションでテーブルの作成
    1. マイグレーションクラスの生成
    2. マイグレーション実行
  4. モデル(Table/Entity)の生成
  5. コントローラの作成
  6. FormHelper の実装
    1. ビューテンプレートの作成
    2. FormHelper でのフォーム実装
    3. control() メソッドのオプションについて
    4. 画面確認
  7. 出力 HTML の独自テンプレート
    1. 独自のテンプレートを設定する
    2. 画面確認

開発環境

今回の環境は以下の通りです。

  • Linux CentOS 7
  • Apache 2.4
  • PHP 7.1
  • CakePHP 3.5

CakePHP のバージョンについては3系であれば同一手順で進めていけます。

CakePHP のルートディレクトリを「cakephp/」としています。

FormHelper を使う前に

実はフォームヘルパーの恩恵を最大限に生かすならば、CakePHP の規約に則ると幸せになれます。

  1. マイグレーションでテーブルの作成
  2. モデル(Table/Entity)の生成
  3. コントローラの作成
  4. ビュー(テンプレート)を作成してフォームヘルパーでのフォーム

※1~3は bake コマンドで行う。4は手動でも OK

3までを作成した時点で、フォームヘルパーを使う上で基本形となるバックエンドの実装がほぼ出来上がります。これを手動で行っていると地味に時間がかかるので、フォームを作りたい気持ちを抑えつつ、まずはテーブル設計などから始めると後がスムーズです。

今回のメインはフェーズ4ですが、1~3についてもダイジェストで紹介だけ行います。詳しい話は上記のリストそれぞれにリンクを張っておいたので、興味があれば参考程度に覗いてみてください。

マイグレーションでテーブルの作成

フェーズ1です。まずはテーブルを作成します。今回は、何かのグループのメンバーでも登録できるフォームを作ろうと思うので、以下のようなテーブルを作ろうと思います。

  • members テーブル
    • id 主キー
    • name 名前
    • email メールアドレス
    • password パスワード
    • age 年齢
    • gender 性別
    • birthplace 出身地
    • birth_at 生年月日
    • created 作成日
    • modified 更新日

年齢があるのに生年月日まであるのはご愛嬌です。フォームヘルパーのフェーズで例に出すのに必要なので、若干被ってますがこれでいきます。

マイグレーションクラスの生成

まずはマイグレーションクラスを生成します。 CakePHP のルートディレクトリへ移動し、以下の bake コマンドを叩きます。

# CakePHP のルートディレクトリへ移動する
cd /path/to/cakephp

# bake コマンドでマイグレーションクラスを生成する
bin/cake bake migration CreateMembers name:string email:string:unique:EMAIL_INDEX password:string age:integer?[3] age:integer?[1] gender:integer?[1] birthplace:integer?[2] birth_at created modified

# 実行結果
[demo@localhost cakephp]# bin/cake bake migration CreateMembers name:string email:string:unique:EMAIL_INDEX password:string age:integer?[3] age:integer?[1] gender:integer?[1] birthplace:integer?[2] birth_at created modified

Creating file /var/www/html/cakephp/config/Migrations/XXXX_CreateMembers.php
Wrote `/var/www/html/cakephp/config/Migrations/XXXX_CreateMembers.php`

cakephp/config/MigrationsXXXX_CreateMembers.php が生成されます。

cakephp
├─ config
│   ├─ Migrations
│   │   ├─ XXXX_CreateMembers.php
cakephp/config/Migrations/XXXX_CreateMembers.php
<?php
use Migrations\AbstractMigration;

class CreateMembers extends AbstractMigration
{
    public function change()
    {
        $table = $this->table('members');
        $table->addColumn('name', 'string', [
            'default' => null,
            'limit' => 255,
            'null' => false,
        ]);
        $table->addColumn('email', 'string', [
            'default' => null,
            'limit' => 255,
            'null' => false,
        ]);
        $table->addColumn('password', 'string', [
            'default' => null,
            'limit' => 255,
            'null' => false,
        ]);
        $table->addColumn('age', 'integer', [
            'default' => null,
            'limit' => 1,
            'null' => true,
        ]);
        $table->addColumn('gender', 'integer', [
            'default' => null,
            'limit' => 1,
            'null' => true,
        ]);
        $table->addColumn('birthplace', 'integer', [
            'default' => null,
            'limit' => 2,
            'null' => true,
        ]);
        $table->addColumn('birth_at', 'datetime', [
            'default' => null,
            'null' => false,
        ]);
        $table->addColumn('created', 'datetime', [
            'default' => null,
            'null' => false,
        ]);
        $table->addColumn('modified', 'datetime', [
            'default' => null,
            'null' => false,
        ]);
        $table->addIndex([
            'email',
        ], [
            'name' => 'EMAIL_INDEX',
            'unique' => true,
        ]);
        $table->create();
    }
}

マイグレーション実行

マイグレーションファイルが生成出来たので、マイグレーションを実行して members テーブルを作成します。 CakePHP のルートディレクトリへ移動し、以下のコマンドを叩きます。

# CakePHP のルートディレクトリへ移動する
cd /path/to/cakephp

# マイグレーション実行
bin/cake migrations migrate

# 実行結果
[demo@localhost cakephp]# bin/cake migrations migrate
using migration paths 
 - /var/www/html/cakephp/config/Migrations
using seed paths 
 - /var/www/html/cakephp/config/Seeds
using environment default
using adapter mysql
using database cakephp_db

== 20180203081919 CreateMembers: migrating
== 20180203081919 CreateMembers: migrated 0.1087s

All Done. Took 0.1905s
using migration paths 
 - /var/www/html/cakephp/config/Migrations
using seed paths 
 - /var/www/html/cakephp/config/Seeds
Writing dump file `/var/www/html/cakephp/config/Migrations/schema-dump-default.lock`...
Dump file `/var/www/html/cakephp/config/Migrations/schema-dump-default.lock` was successfully written

これで members テーブルの作成が完了しました。

mysql> show tables;
+----------------------+
| Tables_in_cakephp_db |
+----------------------+
| members              |
| phinxlog             |
+----------------------+

mysql> show columns from members;
+------------+--------------+------+-----+---------+----------------+
| Field      | Type         | Null | Key | Default | Extra          |
+------------+--------------+------+-----+---------+----------------+
| id         | int(11)      | NO   | PRI | NULL    | auto_increment |
| name       | varchar(255) | NO   |     | NULL    |                |
| email      | varchar(255) | NO   | UNI | NULL    |                |
| password   | varchar(255) | NO   |     | NULL    |                |
| age        | int(1)       | YES  |     | NULL    |                |
| gender     | int(1)       | YES  |     | NULL    |                |
| birthplace | int(2)       | YES  |     | NULL    |                |
| birth_at   | datetime     | NO   |     | NULL    |                |
| created    | datetime     | NO   |     | NULL    |                |
| modified   | datetime     | NO   |     | NULL    |                |
+------------+--------------+------+-----+---------+----------------+

モデル(Table/Entity)の生成

続いては、members テーブルに関するモデルを作成します。 CakePHP3 でのモデルは2つあって、Table クラスと Entity クラスです。

CakePHP のルートディレクトリへ移動し、以下の bake コマンドを叩きます。

# CakePHP のルートディレクトリへ移動する
cd /path/to/cakephp

# bake コマンドでモデル生成
bin/cake bake Model members

# 実行結果
[demo@localhost cakephp]# bin/cake bake Model members
One moment while associations are detected.

Baking table class for Members...

Creating file /var/www/html/cakephp/src/Model/Table/MembersTable.php
Wrote `/var/www/html/cakephp/src/Model/Table/MembersTable.php`
Deleted `/var/www/html/cakephp/src/Model/Table/empty`

Baking entity class for Member...

Creating file /var/www/html/cakephp/src/Model/Entity/Member.php
Wrote `/var/www/html/cakephp/src/Model/Entity/Member.php`
Deleted `/var/www/html/cakephp/src/Model/Entity/empty`

Baking test fixture for Members...

Creating file /var/www/html/cakephp/tests/Fixture/MembersFixture.php
Wrote `/var/www/html/cakephp/tests/Fixture/MembersFixture.php`
Deleted `/var/www/html/cakephp/tests/Fixture/empty`
Bake is detecting possible fixtures...

Baking test case for App\Model\Table\MembersTable ...

Creating file /var/www/html/cakephp/tests/TestCase/Model/Table/MembersTableTest.php
Wrote `/var/www/html/cakephp/tests/TestCase/Model/Table/MembersTableTest.php`

以下のファイルが生成されます。

  • cakephp/src/Model/Table/MembersTable.php
  • cakephp/src/Model/Entity/Member.php

テーブルクラスの MembersTable.php を開いてみてください。その中にある validationDefault() メソッド部分

public function validationDefault(Validator $validator)
{
    $validator
        ->integer('id')
        ->allowEmpty('id', 'create');

    $validator
        ->scalar('name')
        ->requirePresence('name', 'create')
        ->notEmpty('name');

    $validator
        ->email('email')
        ->requirePresence('email', 'create')
        ->notEmpty('email')
        ->add('email', 'unique', ['rule' => 'validateUnique', 'provider' => 'table']);

    $validator
        ->scalar('password')
        ->requirePresence('password', 'create')
        ->notEmpty('password');

    $validator
        ->integer('age')
        ->allowEmpty('age');

    $validator
        ->integer('gender')
        ->allowEmpty('gender');

    $validator
        ->integer('birthplace')
        ->allowEmpty('birthplace');

    $validator
        ->dateTime('birth_at')
        ->requirePresence('birth_at', 'create')
        ->notEmpty('birth_at');

    return $validator;
}

ここで、members テーブルに関するバリデーションが生成されています。 members テーブルのカラムに関する必要最低条件を記したバリデーションが既にこうして作成されているので、この後の FormHelper を使ったフォーム作成がとても楽になります。

コントローラの作成

最後にコントローラです。これもコマンドで作成します。 CakePHP のルートディレクトリへ移動し、以下の bake コマンドを叩きます。

# CakePHP のルートディレクトリへ移動する
cd /path/to/cakephp

# bake コマンドでコントローラ生成
bin/cake bake controller members

# 実行結果
[demo@localhost cakephp]# bin/cake bake controller members

Baking controller class for Members...

Creating file /var/www/html/cakephp/src/Controller/MembersController.php
Wrote `/var/www/html/cakephp/src/Controller/MembersController.php`
Bake is detecting possible fixtures...

Baking test case for App\Controller\MembersController ...

Creating file /var/www/html/cakephp/tests/TestCase/Controller/MembersControllerTest.php
Wrote `/var/www/html/cakephp/tests/TestCase/Controller/MembersControllerTest.php`

cakephp/src/Controller 配下に MembersController.php が生成されます。

cakephp
├─ src
│   ├─ Controller
│   │   ├─ MembersController.php

これでコントローラの作成は完了です。中身はひとまずスルーで OK です。

FormHelper の実装

お待たせしました。ここからメインの FormHelper の実装に入っていきます。

ビューテンプレートの作成

まずは元になるテンプレートを作成します。 cakephp/src/Template 配下に Members ディレクトリを作成し、add.ctp というファイルを作成してください。中身は以下の通りです。

cakephp/src/Template/Members/add.ctp
<style>
  h1 {
    margin-left: 10px;
    font-size: 18px;
  }
</style>
<h1>
  members add
</h1>
<div class="form_wrap">
 
</div>

この div タグの中に、これから FormHelper でフォームを作成していきます。

FormHelper でのフォーム実装

それでは FormHelper でフォームを作成していきます。今回は基本形で記述します。百聞は一見にしかずなので、説明の前にまずは実装後のソースを以下に記します。

<style>
  h1 {
    margin-left: 10px;
    font-size: 18px;
  }
</style>
<h1>
  members add
</h1>
<div class="form_wrap">
  <?php
  // フォーム開始
  echo $this->Form->create($member, [
    'type' => 'post',
    'url' => ['controller' => 'Members', 'action' => 'add'],
  ]);

  // hidden の生成
  echo $this->Form->control('test', ['type' => 'hidden', 'value' => 12345]);

  // 一般的なテキスト入力
  echo $this->Form->control('name', ['label' => '一般的なテキスト']);

  // メールアドレス
  echo $this->Form->control('email', ['type' => 'email', 'label' => 'メールアドレス']);

  // パスワード
  echo $this->Form->control('password', ['label' => 'パスワード']);

  // 数値のみの入力
  echo $this->Form->control('age', ['type' => 'number', 'label' => '数値のみ', 'required' => true, 'min' => 20, 'max' => 100]);

  // ラジオボタン
  echo $this->Form->control('gender', [
    'type' => 'radio',
    'label' => 'ラジオボタン',
    'required' => true,
    'options' => [
      1 => '女性',
      2 => '男性',
      3 => 'その他'
    ],
  ]);

  // セレクトボックスで使うoptionの定義
  $list = [
    [ 'text' => '北海道', 'value' => 1 ],
    [ 'text' => '東京都', 'value' => 2 ],
    [ 'text' => '大阪府', 'value' => 3 ],
    [ 'text' => '福岡県', 'value' => 4 ],
    [ 'text' => '沖縄県', 'value' => 5 ],
  ];

  // セレクトボックス
  echo $this->Form->control('birthplace', [
    'type' => 'select',
    'label' => 'セレクトボックス',
    'required' => true,
    'options' => $list,
    'multiple' => false,
    'empty' => '選択してください'
  ]);

  // 日時入力
  echo $this->Form->control('birth_at', [
    'type' => 'datetime',
    'label' => '日時',
    'required' => true,
    'monthNames' => false,
    'minYear' => date('Y'),
    'maxYear' => date('Y')+1,
  ]);

  // チェックボックス
  echo $this->Form->control('checkbox_1', ['type' => 'checkbox', 'label' => '通知を受け取らない']);
  echo $this->Form->control('checkbox_2', ['type' => 'checkbox', 'label' => '申し込む']);

  // 送信ボタン
  echo $this->Form->submit();

  // フォーム終了
  echo $this->Form->end();
  ?>
</div>

上記をもとに説明していきます。尚、オプションに関してはひとまず、上記で記述しているものを解説します。

create()

$this->Form->create();

最初のフォームタグを生成します。「フォーム開始」の部分です。ここには HTML で言う action や method などを定義します。上記の記述だと実際には以下のような HTML が生成されます。

<form method="post" accept-charset="utf-8" action="/members/add">
  <div style="display:none;">
    <input type="hidden" name="_method" value="POST"/>
  </div>

オプションに関しては、以下の通りです。

type メソッドを指定します。 HTML では「method 」にあたります。設定できる項目は「get 」「post 」「file 」「put 」「delete 」「patch 」です。 url どこへ向けて送信するかを指定します。 HTML でいうところの「action 」にあたります。コントローラとアクションを指定します。#### control()

$this->Form->control();

フォームパーツを生成するメソッドです。上記の記述だと実際には以下のような HTML が生成されます。

<div style="display:none;">
  <input type="hidden" name="_method" value="POST"/>
</div>
<input type="hidden" name="test" id="test" value="12345"/>
<div class="input text required">
  <label for="name">一般的なテキスト</label>
  <input type="text" name="name" required="required" maxlength="255" id="name"/>
</div>
<div class="input email required">
  <label for="email">メールアドレス</label>
  <input type="email" name="email" required="required" maxlength="255" id="email"/>
</div>
<div class="input password required">
  <label for="password">パスワード</label>
  <input type="password" name="password" required="required" id="password"/>
</div>
<div class="input number required">
  <label for="age">数値のみ</label>
  <input type="number" name="age" required="required" min="20" max="100" id="age"/>
</div>
<div class="input radio required">
  <label>ラジオボタン</label>
  <input type="hidden" name="gender" value=""/>
  <label for="gender-1">
    <input type="radio" name="gender" value="1" id="gender-1" required="required">女性
  </label>
  <label for="gender-2">
    <input type="radio" name="gender" value="2" id="gender-2" required="required">男性
  </label>
  <label for="gender-3">
    <input type="radio" name="gender" value="3" id="gender-3" required="required">その他
  </label>
</div>
<div class="input select required">
  <label for="birthplace">セレクトボックス</label>
  <select name="birthplace" required="required" id="birthplace">
    <option value="">選択してください</option>
    <option value="1">北海道</option>
    <option value="2">東京都</option>
    <option value="3">大阪府</option>
    <option value="4">福岡県</option>
    <option value="5">沖縄県</option>
  </select>
</div>
<div class="input datetime required">
  <label>日時</label>
  <select name="birth_at[year]">
    <option value="2019">2019</option>
    <option value="2018" selected="selected">2018</option>
  </select>
  <select name="birth_at[month]">
    <option value="01">1</option>
    <option value="02" selected="selected">2</option>
    <!-- 省略 -->
    <option value="12">12</option>
  </select>
  <select name="birth_at[day]">
    <option value="01" selected="selected">1</option>
    <option value="02">2</option>
    <!-- 省略 -->
    <option value="31">31</option>
  </select>
  <select name="birth_at[hour]">
    <option value="00">0</option>
    <option value="01">1</option>
    <!-- 省略 -->
    <option value="18" selected="selected">18</option>
    <!-- 省略 -->
    <option value="23">23</option>
  </select>
  <select name="birth_at[minute]">
    <option value="00">00</option>
    <option value="01">01</option>
    <!-- 省略 -->
    <option value="09" selected="selected">09</option>
    <!-- 省略 -->
    <option value="59">59</option>
  </select>
</div>
<div class="input checkbox">
  <input type="hidden" name="checkbox_1" value="0"/>
  <label for="checkbox-1">
    <input type="checkbox" name="checkbox_1" value="1" id="checkbox-1">通知を受け取らない
  </label>
</div>
<div class="input checkbox">
  <input type="hidden" name="checkbox_2" value="0"/>
  <label for="checkbox-2">
    <input type="checkbox" name="checkbox_2" value="1" id="checkbox-2">申し込む
  </label>
</div>

今回の例では、フォームパーツは全てこの control() メソッドで作成しています。

実は FormHelper では、input() や select() など、そのフォームパーツ種別に関する生成メソッドも存在していますが、基本的にはそれらは使わずに control() メソッドで生成する事をおすすめします。理由は、以下の通りです。

  • オプション設定などで融通が利く(ラベル設定など)
  • ループで簡単にパーツ生成が出来る(条件分岐が不要)
  • 全体を俯瞰した時に見やすい(単なる主観)

control() メソッドの書式ですが、基本は以下のようになります。

echo $this->Form->control('フィールド名', オプション);

フィールド名というのは、対象のカラム名の事で、「<input name="●●"」の部分の事です。

オプションは配列で渡します。項目についてはフォームのタイプによって違いはありますが、大体以下の通りです。

  • type
    • フォームパーツのタイプを指定します。「input 」「password 」「email 」「text 」「select 」「radio 」「checkbox 」「date 」「datetime 」など、あの辺はここで指定します。
  • label
    • フォームパーツの上部にラベルを生成します。指定しなければデフォルトでフィールド名が表示されますが、「false 」を指定するとラベルは表示されなくなります。
  • required
    • 必須項目として指定できます。基本的に必須に関しては設定しなくてもバリデーションで設定されている場合は自動的に付与されます。
  • min/max
    • 数値系のフォームで使います。最小値と最大値を指定します。
  • options
    • セレクトボックスやラジオボタンなどで使用します。プルダウンの項目などは、ここに配列で指定します。
  • multiple
    • セレクトボックスで使用します。ここを true で指定すると、複数選択可能なパーツになります。
  • empty
    • セレクトボックスで使用します。プルダウンでよくある「選択してください」のブランクの option をわざわざ作らなくても、ここで指定する事で生成してくれます。
  • monthNames
    • 日時入力「datetime 」で使います。ここを true にすると、月の表示が数字ではなく「January 」「February 」のような文字列になります。
  • minYear/maxYear
    • 日時入力系で使います。最小値と最大値をそれぞれ設定します。例えば minYear を今日の日付とかで設定しておくことで初期値としてセットされるのでユーザビリティも向上しますよね。それです。

主たる項目は一通り紹介しましたが、全てではないので、その他は cookbook で参照してください。

[CookBook]Form
https://book.cakephp.org/3.0/ja/views/helpers/form.html

submit()

$this->Form->submit();

送信ボタンを生成します。公式では、送信ボタンに関してはこのメソッドで生成する事を推奨しています。

上記の記述だと実際には以下のような HTML が生成されます。

<div class="submit">
  <input type="submit" value="Submit"/>
</div>

end()

$this->Form->end();

フォームを終了させます。

上記の記述だと実際には以下のような HTML が生成されます。

</form>

かなり働きが地味なやつですが、なくてはならないやつなので温かく迎えてあげてください。

control() メソッドのオプションについて

モデルと連動させて FormHelper を使う場合は、テーブルクラスで定義されているバリデーションに沿って自動で制御値を生成してくれるので、基本的にはそれ以外のオプションを設定すれば良いという事になります。

要するに、ラベルや初期値、選択済みなどの要素をメインで設定していく。という事になります。

ただし必須項目であったり、HTML5 の機能で制御したい(POST する前に)場合もあると思いますが、そういったものについてはバリデーションに沿って自動で出力してくれるのでオプションに指定する必要はありません。

画面確認

ここまでの実装での画面表示を一度確認しておきます。
http://YOUR-DOMAIN/members/add
にアクセスしてみます。

こんな感じで、フォームが生成されていることが確認できました。

ちなみにソースはこんな感じで生成されています。フォームヘルパーを記述した<div class="form_wrap">の中は以下のようになっています。

<div class="form_wrap">
  <form method="post" accept-charset="utf-8" action="/members/add">
    <div style="display:none;">
      <input type="hidden" name="_method" value="POST"/>
    </div>
    <input type="hidden" name="test" id="test" value="12345"/>
    <div class="input text required">
      <label for="name">一般的なテキスト</label>
      <input type="text" name="name" required="required" maxlength="255" id="name"/>
    </div>
    <div class="input email required">
      <label for="email">メールアドレス</label>
      <input type="email" name="email" required="required" maxlength="255" id="email"/>
    </div>
    <div class="input password required">
      <label for="password">パスワード</label>
      <input type="password" name="password" required="required" id="password"/>
    </div>
    <div class="input number required">
      <label for="age">数値のみ</label>
      <input type="number" name="age" required="required" min="20" max="100" id="age"/>
    </div>
    <div class="input radio required">
      <label>ラジオボタン</label>
      <input type="hidden" name="gender" value=""/>
      <label for="gender-1">
        <input type="radio" name="gender" value="1" id="gender-1" required="required">女性
      </label>
      <label for="gender-2">
        <input type="radio" name="gender" value="2" id="gender-2" required="required">男性
      </label>
      <label for="gender-3">
        <input type="radio" name="gender" value="3" id="gender-3" required="required">その他
      </label>
    </div>
    <div class="input select required">
      <label for="birthplace">セレクトボックス</label>
      <select name="birthplace" required="required" id="birthplace">
        <option value="">選択してください</option>
        <option value="1">北海道</option>
        <option value="2">東京都</option>
        <option value="3">大阪府</option>
        <option value="4">福岡県</option>
        <option value="5">沖縄県</option>
      </select>
    </div>
    <div class="input datetime required">
      <label>日時</label>
      <select name="birth_at[year]">
        <option value="2019">2019</option>
        <option value="2018" selected="selected">2018</option>
      </select>
      <select name="birth_at[month]">
        <option value="01">1</option>
        <option value="02" selected="selected">2</option>
        <option value="03">3</option>
        <!--
         .
         . 省略
         .
        -->
        <option value="12">12</option>
      </select>
      <select name="birth_at[day]">
        <option value="01">1</option>
        <option value="02" selected="selected">2</option>
        <option value="03">3</option>
        <!--
         .
         . 省略
         .
        -->
        <option value="31">31</option>
      </select>
      <select name="birth_at[hour]">
        <option value="00">0</option>
        <option value="01">1</option>
        <!--
         .
         . 省略
         .
        -->
        <option value="09">9</option>
        <option value="10" selected="selected">10</option>
        <option value="11">11</option>
        <!--
         .
         . 省略
         .
        -->
        <option value="23">23</option>
      </select>
      <select name="birth_at[minute]">
        <option value="00">00</option>
        <option value="01">01</option>
        <!--
         .
         . 省略
         .
        -->
        <option value="33">33</option>
        <option value="34" selected="selected">34</option>
        <option value="35">35</option>
        <!--
         .
         . 省略
         .
        -->
        <option value="59">59</option>
      </select>
    </div>
    <div class="input checkbox">
      <input type="hidden" name="checkbox_1" value="0"/>
      <label for="checkbox-1">
        <input type="checkbox" name="checkbox_1" value="1" id="checkbox-1">通知を受け取らない
      </label>
    </div>
    <div class="input checkbox">
      <input type="hidden" name="checkbox_2" value="0"/>
      <label for="checkbox-2">
        <input type="checkbox" name="checkbox_2" value="1" id="checkbox-2">申し込む
      </label>
    </div>
    <div class="submit">
      <input type="submit" value="Submit"/>
    </div>
  </form>
</div>

出力 HTML の独自テンプレート

フォームヘルパーによって生成された HTML ソースを見てみるとわかりますが、フォーム以外にも HTML が生成されているのに気が付いたでしょうか?(主に div タグ)

デフォルトでの生成のままだと使いにくい場合も多々あります。そこで、この出力される HTML を独自テンプレートを作成する事で変更してみます。

独自のテンプレートを設定する

フォームヘルパーでのフォーム生成の際に同時に生成される HTML は、独自に定義する事で変更する事が出来ます。

templates()

基本型は templates() メソッドでの定義です。

$this->Form->templates();

ここに、定義する個所を識別子として、HTML で定義していきます。具体的には以下のように定義します。

$this->Form->templates([
  /*
   * input に関するテンプレート
   */
  // フォームのラッパーを定義
  'inputContainer' => '<div id="test">{{content}}</div>',
  // フォーム自体を定義
  'input' => '<input type="{{type}}" id="input_sample" name="{{name}}"{{attrs}}>',
  // エラーメッセージ
  'inputContainerError' => '<div class="input {{type}}{{required}} error">{{content}}{{error}}</div>',

  // 別パターン
  'inputContainer' => '{{content}}',
]);

これは、input 系のフォームの出力を定義した記述です。フォームのラッパー定義の部分で、フォームパーツの周りの HTML を変更する事が出来ます。また、別パターンを見てもらうと HTML が何も記述されていませんが、こうすると、フォームの周りに HTML が出力されなくなります。

ちなみに、フォームパーツ自体やエラーメッセージ部分もカスタマイズが可能です。それらを定義している部分を見てわかる通り、自由に class をつけたりもできます。

テンプレート定義時に使っている{{}}に囲まれた定数的なものがありますが、以下のようなものがあります。

  • {{content}}
    • フォームパーツ
  • {{attrs}}
    • 各属性(required など)
  • {{text}}
    • 表示テキスト。
  • {{name}}
    • name 値。フィールド名。
  • {{value}}
    • value 値
  • {{label}}
    • ラベル
  • {{year}} / {{month}} / {{day}} / {{hour}} / {{minute}} / {{second}} / {{meridian}}
    • datetime フォームで使う。それぞれ。「年」「月」「日」「時」「分」「秒」「子午線」
  • {{error}}
    • エラーメッセージ

create()

もう一つの手段として、create() メソッド内でテンプレートを定義する方法があります。

その場合は、以下のように templates プロパティに対して配列で指定していきます。

// フォーム開始
echo $this->Form->create($member, [
  'type' => 'post',
  'url' => ['controller' => 'Members', 'action' => 'add'],
  'templates' => [
    // フォームのラッパーを定義
    'inputContainer' => '<div id="test">{{content}}</div>',
    // フォーム自体を定義
    'input' => '<input type="{{type}}" id="input_sample" name="{{name}}"{{attrs}}>',
    // エラーメッセージ
    'inputContainerError' => '<div class="input {{type}}{{required}} error">{{content}}{{error}}</div>',
  ]
]);

もちろんここに直接記入しなくても良いので、以下のように外に出してしまうとすっきりします。

$form_template = [
  // フォームのラッパーを定義
  'inputContainer' => '<div id="test">{{content}}</div>',
  // フォーム自体を定義
  'input' => '<input type="{{type}}" id="input_sample" name="{{name}}"{{attrs}}>',
  // エラーメッセージ
  'inputContainerError' => '<div class="input {{type}}{{required}} error">{{content}}{{error}}</div>',
];

// フォーム開始
echo $this->Form->create($member, [
  'type' => 'post',
  'url' => ['controller' => 'Members', 'action' => 'add'],
  'templates' => $form_template
]);

全てではありませんが、大体主要なものを集めてみました。

$form_template = [
  // フォーム設定関連
  'formstart' => '<form{{attrs}}>',
  'formend' => '</form>',
  'formGroup' => '{{label}}{{input}}',
  'groupContainer' => '<div class="input {{type}}{{required}}">{{content}}</div>',
  'groupContainerError' => '<div class="input {{type}}{{required}} error">{{content}}{{error}}</div>',
  'legend' => '<legend>{{text}}</legend>',
  'fieldset' => '<fieldset>{{content}}</fieldset>',

  // エラーメッセージ
  'error' => '<div class="error-message">{{content}}</div>',
  'errorList' => '<ul>{{content}}</ul>',
  'errorItem' => '<li>{{text}}</li>',

  // ラベル
  'label' => '<label{{attrs}}>{{text}}</label>',

  // hidden
  'hiddenblock' => '<div style="display:none;">{{content}}</div>',

  // input
  'input' => '<input type="{{type}}" name="{{name}}"{{attrs}}>',
  'inputsubmit' => '<input type="{{type}}"{{attrs}}>',

  // ファイル
  'file' => '<input type="file" name="{{name}}"{{attrs}}>',

  // select
  'select' => '<select name="{{name}}"{{attrs}}>{{content}}</select>',
  'selectMultiple' => '<select name="{{name}}[]" multiple="multiple"{{attrs}}>{{content}}</select>',
  'option' => '<option value="{{value}}"{{attrs}}>{{text}}</option>',
  'optgroup' => '<optgroup label="{{label}}"{{attrs}}>{{content}}</optgroup>',

  // radio
  'radio' => '<input type="radio" name="{{name}}" value="{{value}}"{{attrs}}>',
  'radioWrapper' => '{{label}}',

  // checkbox
  'checkboxWrapper' => '<div class="checkbox">{{input}}{{label}}</div>',
  'checkboxFormGroup' => '{{input}}{{label}}',
  'checkbox' => '<input type="checkbox" name="{{name}}" value="{{value}}"{{attrs}}>',

  // datetime
  'dateWidget' => '{{year}}{{month}}{{day}}{{hour}}{{minute}}{{second}}{{meridian}}',

  // textarea
  'textarea' => '<textarea name="{{name}}"{{attrs}}>{{value}}</textarea>',

  // button
  'button' => '<button{{attrs}}>{{text}}</button>',

  // submit
  'submitContainer' => '<div class="submit">{{content}}</div>',
];

画面確認

という事で、ビューテンプレートの最終的なソースコードは以下のようになりました。

cakephp/src/Template/Members/add.ctp
<style>
  h1 {
    margin-left: 10px;
    font-size: 18px;
  }
  .form_wrap {
    margin: 10px;
    padding: 5px 30px;
    width: 500px;
    border:  1px solid #dcdcdc;
    box-shadow: 2px 2px 3px #f5f5f5;
  }
  .error-message {
    font-size: 12px;
    color: #ac2925;
  }
</style>
<h1>
  members add
</h1>
<div class="form_wrap">
  <?php
  $form_template = [
    'formstart' => '<form{{attrs}}>',
    'formend' => '</form>',
    'formGroup' => '{{label}}{{input}}',
    'groupContainer' => '<div class="input {{type}}{{required}}">{{content}}</div>',
    'groupContainerError' => '<div class="input {{type}}{{required}} error">{{content}}{{error}}</div>',
    'legend' => '<legend>{{text}}</legend>',
    'fieldset' => '<fieldset>{{content}}</fieldset>',
    'error' => '<div class="error-message">{{content}}</div>',
    'errorList' => '<ul>{{content}}</ul>',
    'errorItem' => '<li>{{text}}</li>',
    'label' => '<label{{attrs}}>{{text}}</label>',
    'hiddenblock' => '<div style="display:none;">{{content}}</div>',
    'input' => '<input type="{{type}}" name="{{name}}"{{attrs}}>',
    'inputsubmit' => '<input type="{{type}}"{{attrs}}>',
    'file' => '<input type="file" name="{{name}}"{{attrs}}>',
    'select' => '<select name="{{name}}"{{attrs}}>{{content}}</select>',
    'selectMultiple' => '<select name="{{name}}[]" multiple="multiple"{{attrs}}>{{content}}</select>',
    'option' => '<option value="{{value}}"{{attrs}}>{{text}}</option>',
    'optgroup' => '<optgroup label="{{label}}"{{attrs}}>{{content}}</optgroup>',
    'radio' => '<input type="radio" name="{{name}}" value="{{value}}"{{attrs}}>',
    'radioWrapper' => '{{label}}',
    'checkboxWrapper' => '<div class="checkbox">{{input}}{{label}}</div>',
    'checkboxFormGroup' => '{{input}}{{label}}',
    'checkbox' => '<input type="checkbox" name="{{name}}" value="{{value}}"{{attrs}}>',
    'dateWidget' => '{{year}}{{month}}{{day}}{{hour}}{{minute}}{{second}}{{meridian}}',
    'textarea' => '<textarea name="{{name}}"{{attrs}}>{{value}}</textarea>',
    'button' => '<button{{attrs}}>{{text}}</button>',
    'submitContainer' => '<div class="submit">{{content}}</div>',
  ];

  // フォーム開始
  echo $this->Form->create($member, [
    'type' => 'post',
    'url' => ['controller' => 'Members', 'action' => 'add'],
    'templates' => $form_template
  ]);

  // hidden の生成
  echo $this->Form->control('test', ['type' => 'hidden', 'value' => 12345]);

  // 一般的なテキスト入力
  echo $this->Form->control('name', ['label' => '一般的なテキスト']);

  // メールアドレス
  echo $this->Form->control('email', ['type' => 'email', 'label' => 'メールアドレス']);

  // パスワード
  echo $this->Form->control('password', ['label' => 'パスワード']);

  // 数値のみの入力
  echo $this->Form->control('age', ['type' => 'number', 'label' => '数値のみ', 'required' => true, 'min' => 20, 'max' => 100]);

  // ラジオボタン
  echo $this->Form->control('gender', [
    'type' => 'radio',
    'label' => 'ラジオボタン',
    'required' => true,
    'options' => [
      1 => '女性',
      2 => '男性',
      3 => 'その他'
    ],
  ]);

  // セレクトボックスで使うoptionの定義
  $list = [
    [ 'text' => '北海道', 'value' => 1 ],
    [ 'text' => '東京都', 'value' => 2 ],
    [ 'text' => '大阪府', 'value' => 3 ],
    [ 'text' => '福岡県', 'value' => 4 ],
    [ 'text' => '沖縄県', 'value' => 5 ],
  ];

  // セレクトボックス
  echo $this->Form->control('birthplace', [
    'type' => 'select',
    'label' => 'セレクトボックス',
    'required' => true,
    'options' => $list,
    'multiple' => false,
    'empty' => '選択してください'
  ]);

  // 日時入力
  echo $this->Form->control('birth_at', [
    'type' => 'datetime',
    'label' => '日時',
    'required' => true,
    'monthNames' => false,
    'minYear' => date('Y'),
    'maxYear' => date('Y')+1,
  ]);

  // チェックボックス
  echo $this->Form->control('checkbox_1', ['type' => 'checkbox', 'label' => '通知を受け取らない']);
  echo $this->Form->control('checkbox_2', ['type' => 'checkbox', 'label' => '申し込む']);

  // 送信ボタン
  echo $this->Form->submit();

  // フォーム終了
  echo $this->Form->end();
  ?>
</div>

ブラウザからアクセスして表示を確認してみます。

スタイルを変更していないのであまり見た目は変わっていませんが独自テンプレートは適用されました。

まとめ

CakePHP3 の FormHelper を使うと、直接 HTML の FORM を書かなくても必要な制約をつけた上で生成してくれるので実装漏れも防げて幸せになれます。

CakePHP は「設定より規約」だと公式にも謳われている通り、フォームヘルパーを使用する前の用意段階から規約に従うとこの先のフォーム送信などもより楽になるので是非試してみてください。

Author

rito

  • Backend Engineer
  • Tokyo, Japan
  • PHP 5 技術者認定上級試験 認定者
  • 統計検定 3 級