[自分用メモ] CakePHPでチェックボックスの取り扱いをときどき忘れてしまうので備忘録として書き残しておく

私はとにかく忘れっぽい。仕事でCakePHPを使うことがあるが、しばらく使わないでいると細かい部分を忘れてしまうことがある。Formヘルパーのチェックボックスなどはそのいい例なのだが、チェックボックスやラジオボタンはマニュアルを見ながらでも勘が戻らないといまいちピンとこない場合も多いので、自分用の即効性ドキュメントとしてこの記事を残しておくつもりである。人様向けに書いているように読めるだろうが「己が昨日書いたコードは他人のもの」というのが長くこの業界で生きてきて自負できる名(迷)言だと思っているので、これはもはや明日以降の自分に向けて書いているのだ。


ラジオボタンは選択項目のうち1つのみを選ぶことができるが、チェックボックスは選択項目から複数項目を選ぶことができる要素だ。それゆえ、プログラム側でも若干めんどくさい処理を伴う場合がある。
要するに、どの項目がチェックされているのかを把握するという処理だ。PHPの場合は配列に入ってくることになる。

以下のAPP/Controller/FormtestsController.phpをAPP/View/Formtests/checkbox.ctpという2つのファイルで確認していきたい。
HTML側、この場合はFormヘルパーの実装側から確認して意向。

[php]
<html>
<head>
<meta charset="utf-8">
<meta name="Generator" content="Powered by Vim7">
<title>radio test</title>
</head>
<body>
<?php
if (isset($msg))
echo ‘<p>’ . $msg . ‘</p>’;
echo $this->Form->create(‘Form’, array(‘url’ => array(‘controller’ => ‘formtests’, ‘action’ => ‘checkbox’)))."\n";
echo $this->Form->input(‘checkbox.’, array(‘type’ => ‘checkbox’, ‘id’ => ‘chk1’, ‘value’ => 1, ‘checked’ => $checked[1], ‘div’ => false, ‘label’ => false, ‘hiddenField’ => false)) . "チェック1<br>\n";
echo $this->Form->input(‘checkbox.’, array(‘type’ => ‘checkbox’, ‘id’ => ‘chk2’, ‘value’ => 2, ‘checked’ => $checked[2], ‘div’ => false, ‘label’ => false, ‘hiddenField’ => false)) . "チェック2<br>\n";
echo $this->Form->input(‘checkbox.’, array(‘type’ => ‘checkbox’, ‘id’ => ‘chk3’, ‘value’ => 3, ‘checked’ => $checked[3], ‘div’ => false, ‘label’ => false, ‘hiddenField’ => false)) . "チェック3<br>\n";
echo $this->Form->input(‘checkbox.’, array(‘type’ => ‘checkbox’, ‘id’ => ‘chk4’, ‘value’ => 4, ‘checked’ => $checked[4], ‘div’ => false, ‘label’ => false, ‘hiddenField’ => false)) . "チェック4<br>\n";
echo $this->Form->end(‘submit’)."\n";
?>
</body>
</html>
[/php]

HTML部分は割愛するとして、PHPの実装部分となる9~16行目を解説。
9、10行目は、出力メッセージ変数があった場合にpタグで表示するという処理だ。これは単なる目視確認用に入れているだけで本質ではないため、後まわしにしておく。

本質的なコードは、11~16行目である。
11行目はFormヘルパーのcreate()メソッドを呼び出している。これはformタグを生成するものだ。第1引数ではController側で受け取るPOSTパラメータ連想配列$this->dataの1次元目の添字となる。このフォーム内のPOSTパラメータは、Controller側では$this->data[‘Form’]を起点に参照されることになる。
第2引数は配列でformタグへの属性などを指示することができる。ここではaction時のURLを指定していて、array(‘url’ => array(‘controller’ => ‘formtests’, ‘action’ => ‘checkbox’)のように指定している。CakePHPであればおなじみのURL表記法なのでいまさら説明もいらないだろうけれど、controllerにformtests、actionにcheckboxを指定している。つまり、このページを最初に開いた時と同じactionにPOSTすることになる。
16行目はsubmitボタンの実装だ。これも特段の説明は不要だろう。

[Sponsored Link]


肝心のチェックボックスは、Formヘルパーのcheckbox()メソッドでも実装できるが、ここではinputを使っている。基本的には第2引数で「’type’ => ‘checkbox’」を指定するかしないかぐらいで大差がないはずだが、詳細はマニュアルを参考に。

チェックボックスは全部で4個出していて、その実装が12~15行目である。これらは文字列の中身が若干違うだけで、やっていることは同じだ。なので解説は12行目のみに絞る。
あと、あらかじめどういうHTMLが生成されるかを以下に示しておく。

[html]
<input type="checkbox" name="data[Form][checkbox][]" id="chk1" value="1" />チェック1<br>
<input type="checkbox" name="data[Form][checkbox][]" id="chk2" value="2" />チェック2<br>
<input type="checkbox" name="data[Form][checkbox][]" id="chk3" value="3" />チェック3<br>
<input type="checkbox" name="data[Form][checkbox][]" id="chk4" value="4" />チェック4<br>
[/html]

input()の第1引数に指定した文字列は、Controller側でPOSTパラメータを受け取った際の2次元目の連想配列の添字になる。上記HTMLのname属性を見ると、さきほどのcreate()の第1引数で指定した文字列と、input()で指定した第1引数の関連が見えるだろう。その後ろの「[]」は、PHPの配列の文法に従って添字0から開始され自動的にインクリメントされる添字になる。
Controller側では$this->data[‘Form’][‘checkbox’][0]~[3]として利用できる。

第2引数の配列には、チェックボックスへの属性指定や、input()メソッドへの指示オプションを指定する。
複数あるが、簡単なところから手早く解説すると、’type’ => ‘checkbox’は、まさにチェックボックスタイプを使うための指示、’id’ => ‘chk1’はid属性の指示(なので4行とも値は異なることに注意)、’value’ => 1はvalue属性(なので4行とも値は異なることに注意)、’div’ => falseと’label’ => falseは、それぞれCakePHPのinput()メソッドへの指示で、チェックボックスタグに付帯させるdivタグやlabelタグなどの「余計だな」と思われるタグを出力しないように指示している(使い方によっては便利だと思うが今回は不要)。

もうひとつ、input()メソッドへの指示として’hiddenField’ => falseがあるが、これはcheckboxとradioの2つのHTML要素にのみ影響するオプションだ。checkboxもradioも、チェックされていない場合はPOSTされる要素が存在しないため、Controller側で$this->data[‘Form’][‘checkbox’]といったふうに配列が存在しなくなるか、値がまったく入っていないパラメータが存在することになってしまう。これを回避するためにhidden属性のinputタグを自動的に生成してくれる。が、これが余計な生成となる場合もある(便利な場合もある)。
ドキュメントを読むと「複数あるチェックボックスの最初の行以外はfalseにしたまえ」的なことが書かれている。試して見たい人は、16行目だけ「’hiddenField’ => 100」などと指定して見れば、状況が分かるだろう(Controllerの動きを理解した上で最後に確認するほうがいい。レンダリングされたHTMLソースも確認すべし)。

さて、残された’checked’ => $checked[1]~[4]であるが、この配列変数は何モノかと言うと、「すでにチェックされているかどうか」の状態が保存されている配列である。この「チェックされているかどうか」というのは、要するに

(1) 最初にページを開く(Formtests.ctp)
(2) チェックを入れる(何でもいい)
(3) submitボタンを押す
(4) FormtestsController::checkbox()アクションでPOSTパラメータ処理
(5) ふたたび同じチェックボックス画面(Formtests.ctp)に戻ってくるが、(2)の状態がそのまま残されている

という意味である。(4)では(2)の状態をFormtestsController側で処理し、ふたたび(5)のView側に処理したパラメータを渡してレンダリングさせることになる。その際にFormtestsControllerから渡されるパラメータが$checked配列である。FormtestsControllerはあとで説明するので、現時点では「たとえば”チェック1″と”チェック3″にチェックが入っている場合は、$checked[1]と$checked[3]に”checked”という文字列が入っている」という前提で考えればいい。

つまり、$checked[1]~[4]にはそれぞれ前のページ遷移からのチェック状態が含まれているので、その情報を’checked’オプションで指定することで、checkboxタグのchecked属性の制御をすることができる。
たとえば1と3をチェックした場合に生成されたcheckboxタグは、以下のような感じになる。

[html]
<input type="checkbox" name="data[Form][checkbox][]" id="chk1" value="1" checked="checked"/>チェック1<br>
<input type="checkbox" name="data[Form][checkbox][]" id="chk2" value="2" />チェック2<br>
<input type="checkbox" name="data[Form][checkbox][]" id="chk3" value="3" checked="checked"/>チェック3<br>
<input type="checkbox" name="data[Form][checkbox][]" id="chk4" value="4" />チェック4<br>
[/html]

では、その$checked[]はいったいどこで設定されるのかというのが、いよいよ処理部のController側の話になってくる。
FormtestsController.phpを見てみることにする。

[php]
<?php
App::uses("AppController", "Controller");

class FormtestsController extends AppController {

public function checkbox()
{
$this->autoLayout = false;

$checked = array(1=>”, ”, ”, ”);
if (isset($this->data[‘Form’][‘checkbox’])) {
$msg = ”;
foreach ($this->data[‘Form’][‘checkbox’] as $index) {
if ($index >= 1 && $index <= 4) {
$checked[$index] = ‘checked’;
$msg .= ‘[‘ . $val . ‘]’;
}
}
$this->set(‘msg’, $msg);
}
$this->set(‘checked’, $checked);
}
}
?>
[/php]

ところでCakePHPのFormHelperは、$this->request->dataというPOSTパラメータを格納する変数を自動的に解析してフォームのデフォルト値に反映させるようにしている。たとえばテキスト入力などの値が$this->request->data[‘Form’][‘text’]などとして入っている場合、この配列が保持している値を$this->Form->input(‘text’, array(‘type’ => ‘text’))が認識してデフォルト値としてHTMLに記述する。
しかしなぜかcheckboxの場合には、それがうまく機能しない。したがって、Controller側で実装をしてやる必要がありそうなのだ。
このあたりはぶっちゃけgithubのコードなんかを追いかけてみたけれど、あまり入念に追いかけられず(時間もなかったし疲れた)、どういうからくりになっているのかまでは理解できなかったのが残念であった。


ということで、Controller側の実装である。

6~22行目がcheckbox()アクションである。
ここで行っていることを大まかにいうと、

(1) チェック状態を格納する配列の初期化(10行目)
(2) チェック状態情報の存在確認(11行目)
(3) チェック状態の反映(13~18行目)
(4) View変数へのセット(21行目)

である。

今回はレイアウトを使わないので、8行目でレイアウトの自動処理をオフ(false)にしている。
10行目は配列の初期化である。配列添字の開始番号は1からにしている。$checked[1]~[4]を、ここでは空文字(”)で設定している。falseを設定してもよい。

なぜこれをやっているかというと、次の11行目では、submitボタンが押されてPOSTパラメータが送られてきているかどうかを確認していて、もしsubmitされていない(ページの初期表示の)場合には、12~19行目は処理されず21行目が実行される。このときに$checked配列がないとエラー(Undefined variable)になる。そのため、10行目で初期化をしておいて、何もチェックがされていない状態にしておく必要がある。

11行目では、チェックボックスのチェック状態パラメータがPOSTされているかどうかの確認をしている。これがない場合はページの初期表示もしくはチェックボックスが1つもチェックされていないものとみなす。チェックボックスがどれか1つでもチェックが入った状態でsubmitボタンを押して遷移してきた場合は、この行はtrue(真)となり、12~19行目を実行することになる。

実は12、16、19行目は、単にチェック状態の目視確認のために入れた、いわばデバッグコードみたいなものなので、チェックボックス本来の実装には不要である。12行目で$msg変数に空文字を入れて初期化しておいてから、16行目ではチェックされたチェックボックスの番号を記録し、19行目でView変数にセットしている。思い出して頂きたい。APP/View/Formtests/checkbox.ctpの9、10行目を。この$msgが設定されていたら$msgを出力するという処理。この場面で利用しているのである(したがってAPP/View/Formtests/checkbox.ctpの9、10行目も本来の実装には不要)。

本質は16行目を除く13~18行目であるが、ここでチェックボックスがチェックされた(されていない)をどのように判断するのかを確認しておきたい。
チェックボックスのチェック状態を事前にvar_dump()で確認して見た(11行目と12行目の間に「echo ‘<pre>’;var_dump($this->data[‘Form’][‘checkbox’]);echo ‘</pre>’;」などのようなコードを入れることで確認できる)。さきほどと同様に1と3がチェックされた場合、$this->data[‘Form’][‘checkbox’]配列(POSTパラメータ)には以下のような値が入っている。

[text]
array(2) {
[0]=>
string(1) "1"
[1]=>
string(1) "3"
}
[/text]

デバッグ慣れしていない人は、単純なvar_dump()の出力なので見づらいかもしれない。$this->data[‘Form’][‘checkbox’]配列には2つの配列要素が含まれていて、添字0には文字列で”1″が、添字1には文字列で”3″が含まれている、と読める。
すなわち、

$this->data[‘Form’][‘checkbox’][0] には “1” が、
$this->data[‘Form’][‘checkbox’][1] には “3” が、

それぞれ入っていることになる。

これはどういうことかというと、POSTパラメータで送られてくるチェック状態情報は「押されたチェックボックスのvalue属性の値が単純に配列に順番に突っ込まれている」というだけである。
さらに注意点を言うのであれば、この例では1、3の順番で入っているが、送られてくる状態としては単にHTML的に先のチェックボックスから順に配列に格納されるということだ。試しにvalue属性の値を逆転したり、ランダム値にしてみれば分かるだろう。

とりあえずデータの格納パターンが分かったので、あとは$checked[1]と$checked[3]に’checked’を代入する処理を実装するだけだ。
これを処理しているのが13~18行目である。
13行目では、$this->data[‘Form’][‘checkbox’]配列を順番に処理するようforeach文でループさせる。変数$indexには、配列の中身が順番に格納されることになる。つまり最初のループでは$this->data[‘Form’][‘checkbox’][0]の値(この例では”1″)が$indexに格納され、2回目のループでは$this->data[‘Form’][‘checkbox’][1]の値(この例では”3″)が$indexに格納される。上記の例では2回でループが終わることになるが、チェックボックスの数だけループすることになる。たとえば100個のチェックボックスがチェックされていれば100回ループを繰り返すことになる。

14行目では、$indexの値をチェックしている。今回の例では不正な値が含まれていても何ら痛い目にあうことはないが、実際の場面ではこの値が大きな意味を持つことになるので、値のチェックなどは何らかの方法で入れておくべきであろう。もっと適したバリデーションの方法があれば、そちらに託すことも必要だ。この例では、チェックボックスのvalue属性が1~4の範囲であることが既知であるため、このようなチェックをとっている。この値を逸脱していたら15行目は実行されない。

15行目で、とうとう’checked’を代入している。
$indexが意味する値とは「チェックされたチェックボックスの番号」なので、その番号のchecked属性にcheckedを入れる必要がある。1回目のループでは、ここでは$checked[1] = ‘checked’が実行され、2回目のループでは$checked[3] = ‘checked’が実行される。

こうしてチェック状態を$checked配列にすべて反映させ終えたら、21行目でView変数に渡す。
View側で受け取ったこの変数は、さきに見たようにタグの属性値として使用している。

たかがチェックボックスの実装で長々とした記事になってしまったが、通常のinputタグに比べるとチェックボックスは若干手間のかかる処理が入ってくる。CakePHPの場合は特にそうだが、パラメータがどのように配列に入ってくるのかを把握しないと処理ができない。忘れてしまった場合はvar_dump($this->data)などのように、適宜どういう形式でパラメータが渡されるのかを確認しておくべきだろう。

Webアプリ開発を加速する CakePHP2定番レシピ119詳解CakePHP辞典―2.0/2.1/2.2/2.3対応オープンソース徹底活用 CakePHP 2.1によるWebアプリケーション開発北海道産 牛生レバー お一人様用 (約85g-115g) ×5袋 (真空パック冷凍)カルビー 輪切りバナナとざく切りイチゴ 340gサントリー いちごミルク 190g×30本

コメントを残す

メールアドレスが公開されることはありません。