CakePHP Formヘルパーを利用して複数レコードの一括編集と保存

データの編集で、同じモデルの複数レコードを一括で編集表示し保存する、ような処理を行ないたいことがよくある。レコード1件1件をその都度編集、保存する手間を省きたい訳だ。

CakePHPのFormヘルパーは、inputエレメントをlabelエレメントやdivエレメントを付けて出力してくれるので、 fieldsetエレメントやlegendエレメントを利用して編集ブロックとしてまとめるなど、CSSを利用して統一したデザインにすることができる。 デザインについては、sdozonoさんの以下の記事が大変参考になった。

FormHelperとCSS

Formヘルパーでは、createメソッドにモデル名を設定すれば、後はinputメソッドにテーブルのカラム名を指定し、最後にendメソッド を置いて<form>ブロックの出来上がりである。サーバーにデータがsubmitされた際のactionで は、$this->dataにモデル名とカラム名の配列で入力を取得できるため、そのままモデルのvalidateを利用できるし、saveメソッ ドで保存もできる。

であるわけだが、<input>にid属性を与えるので、あくまでもデータレコード1件では使い勝手が非常に良いのだが、さて同じモデルの複数レコードを一覧で編集したいときはどうすればいいのだろうか?

以下のような使い方ができたので、次回参考のため記録する。

ユーザーのIDと名前の一括編集登録ビューの作成、および保存するアクションを例にする。

1.一括表示編集のアクション
先ずはデータの取得部分、保存については以降で検討する。

function multiedit() {
    $data = $this->User->find('all');
    $this->set(compact('data'));
}
2.一括表示編集のビュー
CakePHPで実際に吐き出されたコードを参照すると良い。<input>でname属性を「data[User][0][name]」のように、モデル名の後ろがインクリメントされた番号になるようにするのがキモである。

multiedit.ctp

<?php
    echo $form->create('User', array('action'=>'multiedit'));
    foreach($data['User'] as $key => $user) {
        echo $form->input("{$key}.id", array('type'=>'hidden', 'value'=>$user['id']));
        echo $form->input("{$key}.name", array('value'=>$user['name']));
        echo $form->input("{$key}.shimei", array('value'=>$user['shimei']));
    }
    echo $form->end("保存");
?>
3.アクションでのデータ保存の部分
$this->dataでアクセスできる配列データは、モデルのfindメソッドで複数件数のデータを取得した時と同様な扱い方ができる。アクションに保存する命令を加える。

function multiedit() {
    if (!empty($this->data)) {
        foreach ($this->data['User'] as $data) {
            $this->User->save($data);
        }
        $this->redirect('index');
    }
    $data = $this->User->find('all');
    $this->set(compact('data'));
}

今までは、と言うと、ちょっと強引なやり方をしていたようだ。

(CakePHP 1.2.3.8166)


  • トラックバック 停止中
  • コメント (12)
    • sumioturtk
    • 2012年 2月22日 12:28pm

    とても参考になりました.

  1. @sumioturtk さん
    コメントありがとうございます。

    書いた自分がすっかり忘れてました。
    自分で読み返して「おー、そうか!」と思わずうなってました(´Д`;)

    改めて備忘録って結構大切かも、と思いました。

    • Clearwing
    • 2012年 2月24日 11:03pm

    なるほど!実際にフォームを作ってみて納得できました。
    とても参考になりました。ありがとうございます。

    ところで、関連して1つ質問させて下さい。
    複数レコードを一気に編集するviewとして、例えば

    ——————————
    作成者 [     ]
    作成日 [  ]年[  ]月[  ]日

     タイトル [    ]
     用件 [              ]
     —-
     タイトル [    ]
     用件 [              ]
     —-
     タイトル [    ]
     用件 [              ]

    [Submit]
    ——————————

    というようなフォームを作るとして、タイトルと用件を複数個一気に
    書き込んでSubmitするときに、作成者と作成日は3件のレコードに共通
    だったとすると、

    1) あらかじめ作成者と作成日を変数に格納
    2) タイトルと用件のみの配列を作る
    3) 配列をループさせて作成者と作成日を各ループでarray_mergeしてからsave

    とすべきなのでしょうか。

  2. @Clearwing さん、コメントありがとうございます。

    入力していただいた通りで良いと思います。

    それ以外にサーバー側の処理を簡素化するため、作成者と作成日をhidden属性にした入力要素を各個に付加しておき、JavaScriptを利用してフロントでセットしてしまう、という方法もあります。

    ところでCakePHP2のドキュメントを見ると、「Model::saveMany()」という便利なメソッドが追加されていますので、サーバー側でループさせずに保存が可能になったようです。

    • Clearwing
    • 2012年 2月25日 1:02pm

    早速のご返信ありがとうございます。
    hidden+JSであらかじめ放り込んでしまった方が楽そうですね。
    ソースは汚くなりますが、(Cakeなので)もともと汚いから今更ですし。

    Model::saveMany()は便利そうで素敵です。が、いま扱っているのが1.3で、2対応にする手間を考えると、hidden+JSか、配列振り回すか、どちらかにしておきます。

  3. 実際はJavaScriptを使う方が面倒臭いですよね。
    サーバーの処理を軽減するなど目的が無ければ、コントローラーで処理して良いと思います。

    自分としては「処理をモデルに持って行きたいなぁ」と思うこの頃です。と言うのは、モジュールの再利用がし易い気がするからです。あまりしてませんが(´Д`;)。

    • Clearwing
    • 2012年 2月25日 2:29pm

    複数レコードの新規作成と保存は何とか実装できました。ありがとうございました。

    ところで、validationに引っかかった場合のエラー処理はどうされていますでしょうか?
    レコードが1つの場合は、bakeされたそのままの、

    if ($this->Progress->save($this->data)) {
    $this->Session->setFlash(__(‘The progress has been saved’, true));
    $this->redirect(array(‘action’ => ‘index’));
    } else {
    $this->Session->setFlash(__(‘The progress could not be saved. Please, try again.’, true));
    }

    というコードを残しておりますが、複数レコードの場合は?と考えて止まってしまいました。

  4. Validationエラーは、管理者が操作する管理画面上ではCakePHPが持つデフォルトの機能をそのまま利用する事が多いです。

    「$this->Form->input()」を利用していれば、Validationエラーが発生するとinput要素の後ろに、「<div class=”error-message”>エラーメッセージ</div>」を吐き出してくれます。

    項目ごとにエラーメッセージが表示できて重宝してます。

    では複数レコードの場合はどうなるのでしょうか?う~ん、やった事無いので分かりません。近いうちに試して見るようにします(^_^)。

    • Clearwing
    • 2012年 2月28日 9:14pm

    気になって色々やってみてますが、どうも「よく分からない」挙動を示します。
    そもそもが単純に全フィールドを反復している訳じゃないのでアレなのですが。
    もうちょっと悪戦苦闘してみて、予定通り!な動きをする方法があったら報告します。

    • yama
    • 2012年 6月13日 11:47am

    cakephpを初めて1ヶ月で複数の表示と編集と登録で悩んでてとても参考になりました。

    ですが、編集レコードが多いのでpaginateでページを変えて表示を少なくしようとしたのですが、表示画面のデータしか保存されません何かいい対処方法ないでしょうか?

  5. @yama さん、コメントありがとうございます。

    サーバーへの転送は表示されているフォームデータだけなので、保存前にページを切り替えてしまうと編集した内容は保存されずに新しいページが表示されてしまいます。

    ですが、「保存(submit)」ボタンを用意してページ切り替え前に保存を実行する、という手順は妥当だと思います。

    ページ切り替えが必要で、しかも全部一度に実行したいのであれば、CakePHPによるサーバー上でのページ切り替え動作を止めて、JavaScriptを利用してブラウザ上でページ切り替えの見せかけ機能を作るしかないと思います。

    あるいはJavaScriptでページ切り替えのイベントを拾って切替前に自動的に送るか、または確認ダイアログの表示でもいいかも知れません。

    • yama
    • 2012年 6月13日 6:54pm

    返答ありがとうございました。

    Web系の仕事を初めて1ヶ月弱で複数の言語を扱うのは大変ですがページ切り替えはほしいのでJavaScriptに挑戦してみます。

コメント 停止中