RitoLabo

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

  • 公開:
  • カテゴリ: PHP CakePHP
  • タグ: PHP,CakePHP,3.5,FormHelper

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

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

アジェンダ
  1. 開発環境
  2. FormHelperを使う前に
  3. マイグレーションでテーブルの作成
    1. マイグレーションクラスの生成
    2. マイグレーション実行
  4. モデル(Table/Entity)の生成
  5. コントローラの作成
  6. FormHelperの実装
    1. ビューテンプレートの作成
    2. FormHelperでのフォーム実装
      1. create()
      2. control()
      3. submit()
      4. end()
    3. control()メソッドのオプションについて
    4. 画面確認
  7. 出力HTMLの独自テンプレート
    1. 独自のテンプレートを設定する
      1. templates()
      2. create()
    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は「設定より規約」だと公式にも謳われている通り、フォームヘルパーを使用する前の用意段階から規約に従うとこの先のフォーム送信などもより楽になるので是非試してみてください。