在編寫表單所需的 HTML 程式碼之前,我們應該先確定來自最終使用者輸入的資料的類型,以及這些資料應符合什麼樣的規則。 模型類別可用於記錄這些資訊。 如模型章節所定義的, 模型是保存使用者輸入和驗證這些輸入的中心位置。
取決於使用使用者所輸入資料的方式,我們可以建立兩種類型的模型。 如果使用者輸入被收集、使用然後丟棄,我們應該建立一個 表單模型; 如果使用者的輸入要儲存到資料庫,我們應使用一個 Active Record 。 兩種類型的模型共享相同的基類 CModel ,它定義了表單所需的通用介面。
注意: 我們在這一節的範例中主要使用了表單模型 。然而,同樣的操作也可應用於 Active Record 模型。
下面我們建立了一個 LoginForm
模型類別用於在一個登入頁面中收集使用者的輸入。 由於登入資訊只用於驗證用戶,並不需要保存,因此我們將 LoginForm
建立為一個 表單模型。
class LoginForm extends CFormModel { public $username; public $password; public $rememberMe=false; }
LoginForm
中定義了三個屬性: $username
, $password
他們用於保存用戶輸入的用戶名和密碼,以及用戶是否想記住他的登入的選項。 由於 $rememberMe
有預設的值 false
,對應的選項在初始化顯示在登入表單中時將是未勾選狀態。
訊息:我們將這些成員變數稱為 特性(attributes) 而不是 屬性(properties),以區別於普通的屬性(properties)。 特性(attribute)是一個主要用於儲存來自使用者輸入或資料庫資料的屬性(propertiy)。
2. 聲明驗證規則
方法中指定這些驗證規則, 此方法應傳回一個規則配置陣列。
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','错误的用户名或密码。'); } }
上述程式碼指定:
username 與 password
為必填項, rules()
回傳的每個規則必須是以下格式:
array('AttributeList', 'Validator', 'on'=>'ScenarioList', ...附加选项)
其中
/** * @param string 所要验证的特性的名字 * @param array 验证规则中指定的选项 */ public function ValidatorName($attribute,$params) { ... }
其中
AttributeList// 用户名为必填项 array('username', 'required'), // 用户名必须在 3 到 12 个字符之间 array('username', 'length', 'min'=>3, 'max'=>12), // 在注册场景中,密码password必须和password2一致。 array('password', 'compare', 'compareAttribute'=>'password2', 'on'=>'register'), // 在登录场景中,密码必须接受验证。 array('password', 'authenticate', 'on'=>'login'),
Validator(驗證器) 指定要執行驗證的種類;
on 參數是可選的,它指定此規則應被應用到的場景列表; 附加選項是一個名值對數組,用於初始化對應驗證器的屬性值。
有三種方式可在驗證規則中指定
Validator
第一,
可以是模型類別中一個方法的名字,就像上面範例中的 authenticate
。驗證方法必須是下面的結構:
$model=new LoginForm; if(isset($_POST['LoginForm'])) $model->attributes=$_POST['LoginForm'];
可以是驗證器類別的名字,當此規則應用時,被驗證器類別建立以執行實際驗證。規則中的附加選項用於初始化實例的屬性值。 驗證器類別必須繼承自 CValidator
。 第三,
可以是一個預先定義的驗證器類別的別名。在上面的範例中, required
名字是CRequiredValidator 的別名,它用來確保所驗證的特性值不為空。 以下是預先定義的驗證器別名的完整清單:
exist
: CExistValidator 的别名,确保特性值可以在指定表的列中可以找到。
file
: CFileValidator 的别名,确保特性含有一个上传文件的名字。
filter
: CFilterValidator 的别名,通过一个过滤器改变此特性。
in
: CRangeValidator 的别名,确保数据在一个预先指定的值的范围之内。
length
: CStringValidator 的别名,确保数据的长度在一个指定的范围之内。
match
: CRegularExpressionValidator 的别名,确保数据可以匹配一个正则表达式。
numerical
: CNumberValidator 的别名,确保数据是一个有效的数字。
required
: CRequiredValidator 的别名,确保特性不为空。
type
: CTypeValidator 的别名,确保特性是指定的数据类型。
unique
: CUniqueValidator 的别名,确保数据在数据表的列中是唯一的。
url
: CUrlValidator 的别名,确保数据是一个有效的 URL。
下面我们列出了几个只用这些预定义验证器的示例:
// 用户名为必填项 array('username', 'required'), // 用户名必须在 3 到 12 个字符之间 array('username', 'length', 'min'=>3, 'max'=>12), // 在注册场景中,密码password必须和password2一致。 array('password', 'compare', 'compareAttribute'=>'password2', 'on'=>'register'), // 在登录场景中,密码必须接受验证。 array('password', 'authenticate', 'on'=>'login'),
在一个类的实例被创建后,我们通常需要用最终用户提交的数据填充它的特性。 这可以通过如下块赋值(massive assignment)方式轻松实现:
$model=new LoginForm; if(isset($_POST['LoginForm'])) $model->attributes=$_POST['LoginForm'];
最后的表达式被称作 块赋值(massive assignment) ,它将 $_POST['LoginForm']
中的每一项复制到相应的模型特性中。这相当于如下赋值方法:
foreach($_POST['LoginForm'] as $name=>$value) { if($name 是一个安全的特性) $model->$name=$value; }
检测特性的安全非常重要,例如,如果我们以为一个表的主键是安全的而暴露了它,那么攻击者可能就获得了一个修改记录的主键的机会, 从而篡改未授权给他的内容。
检测特性安全的策略在版本 1.0 和 1.1 中是不同的,下面我们将分别讲解:
1.1 中的安全特性
在版本 1.1 中,特性如果出现在相应场景的一个验证规则中,即被认为是安全的。 例如:
array('username, password', 'required', 'on'=>'login, register'), array('email', 'required', 'on'=>'register'),
如上所示, username
和 password
特性在 login
场景中是必填项。而 username
, password
和 email
特性在register
场景中是必填项。 于是,如果我们在 login
场景中执行块赋值,就只有 username
和 password
会被块赋值。 因为只有它们出现在 login
的验证规则中。 另一方面,如果场景是 register
,这三个特性就都可以被块赋值。
// 在登录场景中 $model=new User('login'); if(isset($_POST['User'])) $model->attributes=$_POST['User']; // 在注册场景中 $model=new User('register'); if(isset($_POST['User'])) $model->attributes=$_POST['User'];
那么为什么我们使用这样一种策略来检测特性是否安全呢? 背后的基本原理就是:如果一个特性已经有了一个或多个可检测有效性的验证规则,那我们还担心什么呢?
请记住,验证规则是用于检查用户输入的数据,而不是检查我们在代码中生成的数据(例如时间戳,自动产生的主键)。 因此,不要 为那些不接受最终用户输入的特性添加验证规则。
有时候,我们想声明一个特性是安全的,即使我们没有为它指定任何规则。 例如,一篇文章的内容可以接受用户的任何输入。我们可以使用特殊的 safe
规则实现此目的:
array('content', 'safe')
为了完成起见,还有一个用于声明一个属性为不安全的 unsafe
规则:
array('permission', 'unsafe')
unsafe
规则并不常用,它是我们之前定义的安全特性的一个例外。
1.0 中的安全特性
在版本1.0中,决定一个数据项是否是安全的,基于一个名为 safeAttributes
方法的返回值和数据项被指定的场景. 默认的,这个方法返回所有公共成员变量作为 CFormModel 的安全特性,而它也返回了除了主键外, 表中所有字段名作为 CActiveRecord的安全特性.我们可以根据场景重写这个方法来限制安全特性 .例如, 一个用户模型可以包含很多特性,但是在 login
场景.里,我们只能使用 username
和 password
特性.我们可以按照如下来指定这一限制 :
public function safeAttributes() { return array( parent::safeAttributes(), 'login' => 'username, password', ); }
safeAttributes
方法更准确的返回值应该是如下结构的 :
array( // these attributes can be massively assigned in any scenario // that is not explicitly specified below 'attr1, attr2, ...', * // these attributes can be massively assigned only in scenario 1 'scenario1' => 'attr2, attr3, ...', * // these attributes can be massively assigned only in scenario 2 'scenario2' => 'attr1, attr3, ...', )
如果模型不是场景敏感的(比如,它只在一个场景中使用,或者所有场景共享了一套同样的安全特性),返 回值可以是如下那样简单的字符串.
'attr1, attr2, ...'
而那些不安全的数据项,我们需要使用独立的赋值语句来分配它们到相应的特性.如下所示:
$model->permission='admin'; $model->id=1;
一旦模型被用户提交的数据填充,我们就可以调用 CModel::validate() 出发数据验证进程。此方法返回一个指示验证是否成功的值。 对 CActiveRecord 模型来说,验证也可以在我们调用其 CActiveRecord::save() 方法时自动触发。
我们可以使用 scenario 设置场景属性,这样,相应场景的验证规则就会被应用。
验证是基于场景执行的。 scenario 属性指定了模型当前用于的场景和当前使用的验证规则集。 例如,在 login
场景中,我们只想验证用户模型中的 username
和 password
输入; 而在 register
场景中,我们需要验证更多的输入,例如 email
, address
, 等。 下面的例子演示了如何在 register
场景中执行验证:
// 在注册场景中创建一个 User 模型。等价于: // $model=new User; // $model->scenario='register'; $model=new User('register'); // 将输入的值填充到模型 $model->attributes=$_POST['User']; // 执行验证 if($model->validate()) // if the inputs are valid ... else ...
规则关联的场景可以通过规则中的 on
选项指定。如果 on
选项未设置,则此规则会应用于所有场景。例如:
public function rules() { return array( array('username, password', 'required'), array('password_repeat', 'required', 'on'=>'register'), array('password', 'compare', 'on'=>'register'), ); }
第一个规则将应用于所有场景,而第二个将只会应用于 register
场景。
验证完成后,任何可能产生的错误将被存储在模型对象中。 我们可以通过调用 CModel::getErrors() 和CModel::getError() 提取这些错误信息。 这两个方法的不同点在于第一个方法将返回 所有 模型特性的错误信息,而第二个将只返回 第一个 错误信息。
当设计表单时,我们通常需要为每个表单域显示一个标签。 标签告诉用户他应该在此表单域中填写什么样的信息。虽然我们可以在视图中硬编码一个标签, 但如果我们在相应的模型中指定(标签),则会更加灵活方便。
默认情况下 CModel 将简单的返回特性的名字作为其标签。这可以通过覆盖 attributeLabels() 方法自定义。 正如在接下来的小节中我们将看到的,在模型中指定标签会使我们能够更快的创建出更强大的表单。
以上就是Yii框架官方指南系列17——使用表单:创建模型的内容,更多相关内容请关注PHP中文网(www.php.cn)!