CakePHP 複数レコードを編集する
環境
- CakePHP 1.2.3 8166
複数レコードを編集する際の注意点
- フィールド名の注意
- Model::find() の注意
- バリデーションの注意
フィールド名の注意
フィールド名は以下のように定義してはならない。
<?php foreach($products as $p){ echo $form->text($i . '.Product.name'); } ?>
以下のように定義しなくてはならない。
<?php foreach($products as $p){ echo $form->text('Product.' . $i . '.name'); } ?>
なぜ Model.$i.field としてなくてはならないのか?
FormHelper::error() でエラーメッセージを表示できなくなってしまう。
error() は Helper::tagIsInvalid() を使って、エラーメッセージを取得している。
しかし、tagIsInvalid は以下のデータ構造しか扱えない。
- Model.field
- Model.$i.field
Model::find() の注意
Model::find() が返すデータ構造は上記で説明したデータ構造と一致しない。
- Model::find() から $i.Model.field が戻ってくる。
- FormHelper::text() へ Model.$i.field を渡す必要がある。
そこで以下のような関数を定義し、Model::find() のデータ構造を変換する。
<?php function array_flip_model($ms){ $r = array(); foreach($ms as $m){ foreach($m as $k => $v){ $r[$k][] = $v; } } return $r; } ?>
そして上記 array_flip_model() を使い、Model::find() のデータ構造を変換し、$this->data へセットする。
<?php class ProductsController extends AppController { function index_edit(){ $products = $this->Product->find('all'); $this->set(compact('products')); if(empty($this->data)){ // データ構造を変換する。 $this->data = $this->array_flip_model($products); return; } } } ?>
ちなみに、上記のデータ構造の変換をせずに FormHelper の default オプションを使うと期待した動作にならない
<?php foreach($products as $p){ echo $form->text('Product.' . $i . '.name', array('default' => $p['Product']['name']); } ?>
空入力をするとフィールドの内容が初期状態に戻ってしまう。
これは CakePHP 内部の default の判定に間違いがあるためだと思われる。
バリデーションの注意
FormHelper::error() のフィールド名を、
FormHelper::text() と同じようにしておく。
理由は前述の通りで、FormHelper::errors() が内部で利用している、Helper::tagIsInvalid() が Model.$i.field というデータ構造しか扱えないため。
<?php foreach($products as $p){ echo $form->text('Product.' . $i . '.name'); echo $form->error('Product.' . $i . '.name'); } ?>
さらにコントローラのバリデーション処理も注意が必要である。
まず、複数のフィールドをまとめてバリデーションできないのでループする必要がある。
Model::validates() には引数を渡さない点に注意する。
※この動作は CakePHP 1.1 以前と 1.2 以降で動作が異なる。
<?php $errors = array(); foreach($this->data['Product'] as $p){ $this->Product->set(array('Product' => $p)); $this->Product->validates(); $errors[] = $this->Product->validationErrors; } $this->Product->validationErrors = $errors; ?>
上記では発生したエラーを配列にまとめ直している点に注意すること。
同じモデルに対して複数回のバリデーションを実行すると、最後のバリデーション結果で上書きされてしまう。
そこで、バリデーション結果を蓄積しておき、最後にモデルのバリデーション結果を蓄積した全ての結果で上書きしている。