モデルの作成

フォームに必要なHTMLコードを書く前に、エンドユーザから、どの様なタイプの データが送られてくる事を期待するか、どの様なルールが適用されるべきかを 決定します。モデルクラスはこれらの情報を記録するために 使用することができます。モデルはサブセクション Model で定義されているように、 ユーザーの入力を保持し、入力チェックを行う中心的な場所です。

ユーザーの入力をどのように扱うかによって、2つのタイプのモデルを 作成することができます。もしユーザーの入力が収集され、 使用された後に破棄される場合は、 form model を作成しましょう;もしユーザーの入力が収集され、データベースへ保存される場合 は代わりに active record を使いましょう。 両方のモデルは CModel という基底クラスを共有しており、この基底クラスが フォームによって必要とされる共通のインターフェースを定義しています。

注意: このセクションでは例として form model を主に扱いますが、 active record においても同様なことが可能です。

1. モデルクラスの定義

以下では、ログインページでユーザーの入力を収集する為に使用される LoginForm モデルクラスを作成します。ログインで扱う情報は 認証時にのみ必要で、保存される必要はありませんので、LoginForm のモデルは form model として作成します。

class LoginForm extends CFormModel
{
    public $username;
    public $password;
    public $rememberMe=false;
}

3つの属性が LoginForm の中で宣言されています: $username$password、 そして $rememberMeです。これらはユーザーの入力したユーザー名とパスワード、 そして、ユーザーがログイン状態を維持したいかどうかのオプションを保持する為に 用いられます。$rememberMe は、デフォルトで false になっているので、 ログインフォームに表示される結びついたオプションは、初期状態では チェックされてない状態となります。

情報: ここでは、これらのメンバ変数を普通のプロパティと区別する為に、 プロパティと呼ばずに 属性 (attribute) と呼びます。属性は主に、 ユーザの入力やデータベースのデータを保持する為に用いられるプロパティです。

2. バリデーションルールの宣言

ユーザがインプットを送信し、モデルが値を受け取った際、私たちは その値を使用する前に正しい値であるか検証する必要があります。 これはインプットに対して、セットになったルールのバリデーション(検証) を実行する事によって成し遂げられます。バリデーションルールは rules() メソッドの中で定義し、このメソッドはルールの構成を配列として 返さなければいけません。

class LoginForm extends CFormModel
{
    public $username;
    public $password;
    public $rememberMe=false;
 
    private $_identity;
 
    public function rules()
    {
        return array(
            array('username, password', 'required'),
            array('rememberMe', 'boolean'),
            array('password', 'authenticate'),
        );
    }
 
    public function authenticate($attribute,$params)
    {
        $this->_identity=new UserIdentity($this->username,$this->password);
        if(!$this->_identity->authenticate())
            $this->addError('password','Incorrect username or password.');
    }
}

上記のコードは usernamepassword の両方が必須であり、password は認証に掛けられなければならない事、そして rememberMe が真偽値(boolean) であるべきことを示しています。

rules() によって返される、それぞれのルールは下記の様なフォーマット でなければなりません:

array('AttributeList', 'Validator', 'on'=>'ScenarioList', ... 追加のオプション)

AttributeList の箇所は、ルールによって検証されなければならない属性 (attribute)の名前がカンマ区切りの文字列として入ります; Validator はどのようなバリデーションが適用されなければならないかを示しています; on パラメータはオプションで、ルールが適用されるシナリオのリスト を示しています; そして追加のオプション(additional option)の箇所は 名前-値 のペアで、結びついたバリデータのプロパティの値を初期化する為に 使用されます。

バリデーションルールの中で Validator を指定するには3つの方法があります。 ひとつ目に、上記の authenticate の例の様に、Validator はモデルクラス の中のメソッド名を取る事ができます。バリデータメソッドは下記の様な書式 でなければなりません:

/**
 * @param string $attribute 検証される属性の名前
 * @param array $params バリデーションルールで指定されたオプション
 */
public function ValidatorName($attribute,$params) { ... }

二つ目に、Validator はバリデータクラスの名前にする事ができます。 ルールが適用されたとき、バリデータクラスのインスタンスが作成され、 実際の検証が行われます。追加のオプションの値は、インスタンスの属性の値を 初期化する為に用いられます。バリデータクラスは CValidator を継承しなければいけません。

三つ目に、Validator は予め定義されたバリデータクラスの エイリアスを用いる事が出来ます。上記の例では、requiredCRequiredValidator のエイリアスで、これは検証される属性の値が、 空の値ではないことを確認するものです。下記は予め定義されたバリデータの エイリアスの完全なリストです:

  • boolean: CBooleanValidatorのエイリアスで、属性の値が CBooleanValidator::trueValueかまたはCBooleanValidator::falseValueである事を確認します。

  • captcha: CCaptchaValidator のエイリアスで、属性の値が  CAPTCHAで表示された ベリフィケーションコードと等しい事を確認します。

  • compare: CCompareValidator のエイリアスで、 属性の値が、 もう一つの属性、または定数と等しい事を確認します。

  • email: CEmailValidator のエイリアスで、属性の値が正式な Eメールアドレスである事を確認します。

  • date: CDateValidator のエイリアスで、属性が正当な日付(date)、時刻(time)、 または日付と時刻(datetime)を表していることを確認します。

  • default: CDefaultValueValidatorのエイリアスで、指定した属性にデフォルト値を代入します。

  • exist: CExistValidatorのエイリアスで、属性値が指定されたテーブルの列に存在することを保証します。

  • file: CFileValidator のエイリアスで、属性の値にアップロード されたファイルの名前が入っている事を確認します。

  • filter: CFilterValidator のエイリアスで、 属性の値を フィルタによって変形します。

  • in: CRangeValidator のエイリアスで、属性の値が、 予め定めた値のリストの中にある事を確認します。

  • length: CStringValidator のエイリアスで、 データの長さが特定の範囲内にある事を確認します。

  • match: CRegularExpressionValidator のエイリアスで、 データが正規表現にマッチする事を確認します。

  • numerical: CNumberValidator のエイリアスで、 データが数値として妥当であるかを確認します。

  • required: CRequiredValidator のエイリアスで、 属性が空でない事を確認します。

  • type: CTypeValidator のエイリアスでデータが 特定の型である事を確認します。

  • unique: CUniqueValidator のエイリアスで、データが データベースのテーブルのカラムで一意な値である事を確認します。

  • url: CUrlValidator のエイリアスで、データが妥当なURLである事 を確認します。

下記は予め定義されたバリデータを使用した例のリストです:

// username は必須です。
array('username', 'required'),
// username の長さが3から12の間である事を確認します。
array('username', 'length', 'min'=>3, 'max'=>12),
// 登録シナリオでは、パスワードとパスワード2は一致する必要があります。
array('password', 'compare', 'compareAttribute'=>'password2', 'on'=>'register'),
// ログインシナリオでは、パスワードは認証されなければなりません。
array('password', 'authenticate', 'on'=>'login'),

3. 属性への代入をセキュアにする

モデルのインスタンスが作成された後、多くの場合は属性値にユーザからの入力値を代入する必要があります。 これは以下のように一括代入を使うと容易に行えます。

$model=new LoginForm;
if(isset($_POST['LoginForm']))
    $model->attributes=$_POST['LoginForm'];

最後の文が 一括代入 と呼ばれるもので、$_POST['LoginForm'] のすべてのエントリーを対応するモデルの属性に代入しています。 これは、下記の代入と同等です。

foreach($_POST['LoginForm'] as $name=>$value)
{
        // $name が安全な属性であれば
    if($name is a safe attribute)
        $model->$name=$value;
}

どの属性が安全であるかの判断は、決定的に重要です。例えば、テーブルの主キーを 安全であるとしてしまうと、攻撃者は自分に与えられたレコードの主キーを修正することが出来て、 その結果、彼には権限の無いデータを改竄することが出来るようになってしまいます。

安全な属性を宣言する

属性は、与えられたシナリオにおいてバリデーションルールに出現すると、 安全であると見なされます。例えば

array('username, password', 'required', 'on'=>'login, register'),
array('email', 'required', 'on'=>'register'),

上記では、login のシナリオでは usernamepassword の属性が入力必須とされており、register のシナリオでは usernamepassword および email の属性が入力必須とされています。 この結果、login シナリオにおいて一括代入をすると、usernamepassword だけが一括代入されます。login のシナリオではこの二つの属性だけがバリデーションルールに出現するからです。その一方で、シナリオが register であるときには、三つの属性はすべて一括代入が可能になります。

// login シナリオで
$model=new User('login');
if(isset($_POST['User']))
    $model->attributes=$_POST['User'];
 
// register シナリオで
$model=new User('register');
if(isset($_POST['User']))
    $model->attributes=$_POST['User'];

それでは、なぜ、属性が安全か否かの決定について、このような方針を取るのでしょうか? その論理的根拠は、属性の正当性をチェックするためのバリデーションルールが既に一つないし複数あるときに、他に何を心配したら良いというのか、ということです。

記憶しておくべき重要なことは、バリデーションルールはユーザの入力データをチェックするために使うものであり、コードの中で生成するデータ (例えば、タイムスタンプや自動生成される主キー) をチェックするためのものではない、ということです。 従って、エンドユーザからの入力を予定しない属性については、バリデーションルールを追加してはいけません。

ときとして、何ら特定のルールが無い場合であっても、属性を安全であると宣言したいときがあります。 例えば、記事データの内容を表す属性で、どのようなユーザ入力も受け付けるようなものです。 このためには、safe という特別なルールを使うことが出来ます。

array('content', 'safe')

完全を期すために unsafe というルールも有ります。これは、属性が安全でないことを明示的に宣言するものです。

array('permission', 'unsafe')

この unsafe ルールは滅多に使用されません。そして、これは、既に述べた安全な属性の定義の例外です。

安全でないデータ項目については、下記のように、個別の代入文を使って、対応する属性に代入しなければなりません。

$model->permission='admin';
$model->id=1;

4. バリデーションの始動

モデルにユーザからの入力値をセットしたら、 バリデーションを始動する為に、CModel::validate() をコールする事 が出来ます。このメソッドは、バリデーションが成功したかどうかを示す値を返します。 CActiveRecord モデルでは、save() メソッドを呼んだ時も、バリデーションが自動的に動作します。

scenario プロパティによってシナリオをセットし、それによって、どのバリデーションルールのセットが適用されるべきかを示すことが出来ます。

バリデーションはシナリオを基準にして実行されます。scenario プロパティは、モデルがどのシナリオで使われるのか、そして、どのバリデーションルールのセットが使われるのかを決定します。 例えば、login シナリオにおいては、usernamepassword だけを検証する必要がありますが、一方、register シナリオでは、emailaddress など、もっと多くの入力を検証する必要があるでしょう。 以下の例は register シナリオにおいてバリデーションを実行する方法を示すものです。

// User モデルを register シナリオで作成する。以下と等しい
// $model=new User;
// $model->scenario='register';
$model=new User('register');
 
// 入力値をモデルに代入
$model->attributes=$_POST['User'];
 
// バリデーションを実行
if($model->validate())   // 入力が正当であれば
    ...
else
    ...

ルールが適用されるシナリオは、ルールの on オプションによって指定することが出来ます。 on オプションがセットされていない場合は、そのルールがすべてのシナリオに適用されることを意味します。例えば、

public function rules()
{
    return array(
        array('username, password', 'required'),
        array('password_repeat', 'required', 'on'=>'register'),
        array('password', 'compare', 'on'=>'register'),
    );
}

最初のルールはすべてのシナリオに適用され、残りの二つのルールは register シナリオにだけ適用されます。

5. バリデーションエラーの取得

バリデーションが実行されたとき、生じ得る検証エラーはすべてモデルオブジェクトに保存されます。このエラーメッセージは、CModel::getErrors()CMode::getError() を呼んで取得することが出来ます。 二つのメソッドの違いは、第一のメソッドが指定されたモデルの属性の すべての エラーを返すのに対して、第二のメソッドは 最初の エラーだけを返すという点です。

6. 属性のラベル

フォームをデザインする時、それぞれのインプットフィールドにラベルの表示 が必要となる事が度々あります。ラベルはユーザに、どの様な種類の情報を フィールドに入力するかを伝えます。ビューの中にラベルをハードコーディングする事も可能ですが、 対応するモデルの中でラベルを定義した方がより柔軟で便利なことが多いでしょう。

デフォルトでは、CModel は単純に属性の名前をラベルとして返します。 これは attributeLabels() メソッドを オーバーライドする事でカスタマイズ可能です。次のサブセクション で見るように、モデルの中でラベルを定義すると、より早くフォームを作成でき、 またより強力なフォームを作成することが出来るようになります。

$Id: form.model.txt 3482 2011-12-13 09:41:36Z mdomba $

Be the first person to leave a comment

Please to leave your comment.