モデルフォーム | Form & Validation

モデルフォームはFormオブジェクトを拡張し、モデルに対応したものです。基本は通常のFormオブジェクトなのでまずはこちらを読んでください。

通常のフォームで扱ったサンプルのフォームを、ここではモデルフォームで実装してみます。まず、userテーブル作成用のマイグレーションファイルを作成し、データベースにテーブルを作成します。マイグレーションに関する詳細はこちらを参照してください。
migration/default/1_User_create.php

<?php

$create->column("id")->type(_INT)->primary(true)->increment(true);
$create->column("name")->type(_STRING)->length(24)->nullable(false);
$create->column("email")->type(_STRING)->nullable(false);
$create->column("age")->type(_SMALLINT);
$create->column("sex")->type(_SMALLINT)->nullable(false);

$create->unique("email");
$ sakle Migration development head
開発中とはいえ、テーブルスキーマ(メタデータ)を引っ張ってくるのが重くて耐えられない場合はスキーマをファイルに出力してください。
$ sakle Schema development -t user
次に、(モデル)フォームクラスを作成します。モデルが関係しないフォームの場合は Form_Object を継承しますが、モデルフォームの場合は Form_Model を継承します。なお、Form_Model は Form_Object を継承しています。

app/forms/User.php

class Forms_User extends Form_Model
{
  
}
通常のフォームと同様、入力名に対応する表示名を設定します。
class Forms_User extends Form_Model
{
  protected $displayNames = array(
     "name" => "名前",
     "email" => "メールアドレス",
     "age" => "年齢",
     "sex" => "性別",
  );
}
次も同様にバリデーションメソッドを各入力に充てていきますが、先ほどと異なるのは、いくつかのバリデーションメソッドの登録を省略できる点です。その理由は、テーブルスキーマからバリデーション設定を生成しているからです。
なので、先ほどと同じバリデーションを行うには、下記の設定だけで十分です。
class Forms_User extends Form_Model
{
  ...
  
  protected $validators = array(
    "email" => array("validateEmail"),
    "age" => array("min(18)", "max(60)"),
    "sex" => array("validateSex"),
  );
  
  public function validateEmail($name, $value)
  {
    ...
  }
  
  public function validateSex($name, $value)
  {
    ...
  }
}
省略できる(自動で設定される)バリデーション設定は下記の通りです。
  • 必須
  • 形式
    • 整数 - _SMALLINT, _INT, _BIGINTの場合のみ
    • 数値 - _FLOAT, _DOUBLEの場合のみ
    • 真偽値 - _BOOLの場合のみ
    • 日付 - _DATEの場合のみ
    • 日付時刻 - _DATETIMEの場合のみ
  • 最大値, 最小値 - _SMALLINT, _INT, _BIGINT, _FLOAT, _DOUBLEの場合のみ
  • 文字数(文字幅) - _STRINGの場合のみ
このうち、最大値・最小値はそれほど意味のあるものではありません。というのも、例えば_SMALLINTのカラムは最小値が-32768、最大値が37676に設定されてしまい、実用的ではないためです。そのため、フォームクラス(Forms_User)で age の最大値・最小値を上書き設定しています。

フォームの値をデータベースに反映(保存)するのは簡単です。
バリデートの結果に問題がなければ、save()メソッドをコールしてください。
class Index_Controllers_Index extends Sabel_Controller_Page
{
  ...
  
  public function post()
  {
    $form = new Forms_User();
    $form->submit($this->request->fetchPostValues(), array(
      "name", "email", "age", "sex"
    ));
    
    if ($form->validate()) {
      $form->save();
    } else {
      $this->userForm = $form;
      $this->view->setName("index");
    }
  }
}
モデルフォームからモデルを受け取りたい時もあるかもしれません。その場合は getModel() を使用してください。しかし、モデルにフォームの値がセットされているわけではないため、それはそれでコーディングする必要があります。
$aUser = $form->getModel();

// フォームの値をモデルにセット
$aUser->setValues($form->getValues());
なお、モデルフォームの場合はユニーク制約違反も自動で検出してくれます。この userテーブル は emailカラム がユニークとして定義されています。そのため、1度 "test@example.com" で登録した後にもう1度同じメールアドレスでPOSTすると、下記のようなエラーメッセージが出力されます。
* メールアドレス "test@example.com" は既に登録されています

モデルフォームを用いた更新

モデルフォームを用いた更新はとても簡単です。インスタンス生成時に更新(編集)対象のモデルのプライマリキーの値を渡して下さい。
class Index_Controllers_MyController extends Sabel_Controller_Page
{
  public function myAction()
  {
    $this->userForm = new Forms_User(1);  // id = 1
  }
}
そうすると、フォームの各入力要素にデータベースに保存されているモデルの値が反映されています。保存する時も同様、インスタンス生成時にプライマリキーの値を渡します。
class Index_Controllers_MyController extends Sabel_Controller_Page
{
  ...
  
  public function postAction()
  {
    $form = new Forms_User(1);  // id = 1
    ...
    
    if ($form->validate()) {
      $form->save();  // Update
    } else {
      ...
    }
  }
}
通常、このプライマリキーの値はセッションに格納されているログインユーザのIDであったり、扱うものや場面によってはhiddenなどで渡されるパラメータを使用するでしょう。

更新対象のモデルのプライマリキーを渡すのではなく、データベースから取得したモデルをコンストラクタや setModel() に渡すこともできます。例えば自分の投稿した記事を更新する場合、その記事IDの記事が本当に自身のものかを事前に確認する必要があるでしょう。(そうでなければパラメータを変えれば他ユーザの記事を更新できてしまいます)
class Index_Controllers_MyController extends Sabel_Controller_Page
{
  ...
  
  public function updateArticle()
  {
    $anArticle = new Article($this->request->fetchPostValue("article_id"));  // hidden value
    
    if (!$anArticle->isSelected()) {  // 記事が存在しない
      return $this->response->getStatus()->setCode(Sabel_Response::NOT_FOUND);
    }
    
    if ($anArticle->user_id !== $this->session->read("user_id")) {  // 自身の記事ではない
      return $this->response->getStatus()->setCode(Sabel_Response::BAD_REQUEST);
    }
    
    $form = new Forms_Article($anArticle);  // 更新対象のモデルをセット
    ...
    
    if ($form->validate()) {
      $form->save();  // Update
    } else {
      ...
    }
  }
}