透過前面的學習,我們了解了Yii Web應用的基本組成部分,也會編寫像Hangman猜單字遊戲這樣簡單的應用。在第一個例子Yii Framework 開發簡明教程(1) 第一個應用Hello World 我們介紹了Yii Web應用採用MVC模型,也說明了本教程目的是透過不同的視角(主要是透過開發Windows應用C++,C#程式設計師的角度)幫助Windows桌面應用程式或ASP.Net程式設計師較快的掌握PHP Yii Framework應用程式框架。
前面我們介紹了透過CHtml建立View(頁面視圖Form),透過CController來處理使用者提交事件,和Windows 桌面應用程式或ASP.Net做個類比, Yii 中視圖View (HTML Form) 類似WinForm或Asp .Net 的Page。 控制類別 Controller類似Windows桌面應用程式或Asp.Net的事件處理(Code-Behind)類別。不同的是Asp.Net和Windows 桌面應用程式可以為UI中各 個UI元件,例如文字框,按鈕定義Id,然後為不同的UI元件新增事件處理。 PHP應用程式或是Yii應用程式沒有對應的機制可以為定義在 HTML Form中的UI元件定義一個Id,並為UI元件定義事件處理。 然而Yii 框架提供了CFormModel 可以支援類似的功能,簡單的 說,透過CFormModel,可以為HTML Form 中的UI小元件定義變量,並且可以在其控制類別Controller中存取這些變數。每個Yii View(Form)一般都提供一個「提交」按鈕(Submit Button),使用者點擊這個「提交按鈕」觸發CController物件對應的actionXXX 方法,在actionXXX 方法中可以透過CFormModel來存取HTML Form的UI元件的值。
前面教學中說過Yii中的模 類型(Model)是 CModel 或其子類別的實例。模型用於保持資料以及與其相關的業務邏輯,
Yii 實作了兩種類型的模型:表 單一模型和 Active Record。二者均繼承於相同的基底類別 CModel。
表單模型是 CFormModel 的實例。表單模型用於保持從使用者的輸入獲取的資料。 這些數據經常被獲取,使用,然後丟棄。例如,在一個登入頁面中, 我們可以使用表單模型來表示由最終使用者提供的使用者名稱和密碼資訊。更多詳情,請參考使用表單。本篇介紹CFormModel的用法,
Active Record (AR) 是一種用於透過物件導向的風格抽象化資料庫存取的設計模式。 每個 AR 物件是一個CActiveRecord 或其子類別的實例。代表數 據表中的一行。 行中的欄位對應 AR 物件中的屬性。更多關於 AR 的細節請閱讀 Active Record. 後面介紹資料庫使用時再介紹。
本篇使用一個簡單的登入介面來介紹FormModel的用法,本例下載。
1. 定義模型類別
下面我們建立了一個 LoginForm (protected/models/LoginForm.php) 模型類別用於在一個登入頁面中收集使用者的輸入。 由於登入資訊只用於驗證用戶,並不需要儲存,因此我們將 LoginForm 建立為一個 表單模型。
class LoginForm extends CFormModel { public $username; public $password; public $rememberMe=false; }
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','错误的用户名或密码。'); } }
上述程式碼指定:username 和 password 為必填項, password 應被驗證(authenticated),rememberMe 應該是一 個布林值。
rules() 傳回的每個規則必須是以下格式:
array('AttributeList', 'Validator', 'on'=>'ScenarioList', ...附加选项)
其中AttributeList(特性列表) 是需要透過此規則驗證的特性列表字串,每個特性名字由逗號分隔器;Validator(驗證器) 指定要執行驗證的種類;on 參數是可選的,它指定此規則應被應用到的場景清單; 附加選項是一個名值對數組,用於初始化對應驗證器的屬性值。
有三種方式可在驗證規則中指定 Validator 。第一, Validator 可以是模型類別中一個方法的名字,就像上面範例中的 authenticate 。驗證方法必須是下面的結構:
/** * @param string 所要验证的特性的名字 * @param array 验证规则中指定的选项 */ public function ValidatorName($attribute,$params) { ... }
第二,Validator 可以是一个验证器类的名字,当此规则被应用时, 一个验证器类的实例将被创建以执行实际验证。规则中的附加选项用于初始化实例的属性值。 验证器类必须继 承自 CValidator。
第三,Validator 可以是一个预定义的验证器类的别名。在上面的例子中, required 名字是 CRequiredValidator 的别名,它用于确保所验证的特性值不为空。 下面是预定义的验证器别名的完整列表:
boolean: CBooleanValidator 的别名, 确保特性有一个 CBooleanValidator::trueValue 或CBooleanValidator::falseValue 值。
captcha: CCaptchaValidator 的别名,确保特性值等于 CAPTCHA 中显示的验证码。
compare: CCompareValidator 的别 名,确保特性等于另一个特性或常量。
email: CEmailValidator 的别名,确保特性是一个有效的Email地址。
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 。
下面我们列出了几个只用这些预定义验证器的示例:
// 用户名为必填项 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'),
3. 安全的特性赋值
在一个类的实例被创建后,我 们通常需要用最终用户提交的数据填充它的特性。 这可以通过如下块赋值(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;4. 触发验证
一旦模型被用户提交的数据填充,我们就可以调用 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 场景。
5. 提取验证错误
验证完成 后,任何可能产生的错误将被存储在模型对象中。 我们可以通过调用 CModel::getErrors()和CModel::getError() 提取这些错 误信息。 这两个方法的不同点在于第一个方法将返回 所有 模型特性的错误信息,而第二个将只返回 第一个 错误信息。
6. 特性标签
当设计表单时,我们通常需要为每个表单域显示一个标签。 标签告诉用户他应该在此表单域中填写 什么样的信息。虽然我们可以在视图中硬编码一个标签, 但如果我们在相应的模型中指定(标签),则会更加灵活方便。
默认情况下 CModel 将简单的返回特性的名字作为其标签。这可以通过覆盖 attributeLabels() 方法自定义。 正如在 接下来的小节中我们将看到的,在模型中指定标签会使我们能够更快的创建出更强大的表单。
7. 创建动作Action方法
创建好LoginForm 表单Model后,我们就可以为它编写用户提交后的处理代码(对应到Controller中的某个Action方法) 。本例使用缺省的SiteController,对应的action为actionLogin.
public function actionLogin() { $model=new LoginForm; // collect user input data if(isset($_POST['LoginForm'])) { $model->attributes=$_POST['LoginForm']; // validate user input and redirect to the previous page if valid if($model->validate() && $model->login()){ $this->render('index'); return; } } // display the login form $this->render('login',array('model'=>$model)); }
如上所示,我们首先创建了一个 LoginForm 模型示例; 如果请求是一个 POST 请求(意味着这个登录表单被提交了 ),我们则使用提交的数据 $_POST['LoginForm'] 填充 $model ;然后我们验证此输入,如果验证成功,则显示index 页面。 如果验证失败,或者此动作被初次访问,我们则渲染 login 视图。
注意的我们修改了SiteController 的缺省 action为login.
/** * @var string sets the default action to be 'login' */ public $defaultAction='login';
因此用户见到的第一个页面为login页面而非index页面,只有在用户输入正确的用 户名,本例使用固定的用户名和密码,参见UserIdentity类定义,实际应用可以读取数据库或是LDAP服务器。
/** * UserIdentity represents the data needed to identity a user. * It contains the authentication method that checks if the provided * data can identity the user. */ class UserIdentity extends CUserIdentity { /** * Authenticates a user. * The example implementation makes sure if the username and password * are both 'demo'. * In practical applications, this should be changed to authenticate * against some persistent user identity storage (e.g. database). * @return boolean whether authentication succeeds. */ public function authenticate() { $users=array( // username => password 'demo'=>'demo', 'admin'=>'admin', ); if(!isset($users[$this->username])) $this->errorCode=self::ERROR_USERNAME_INVALID; else if($users[$this->username]!==$this->password) $this->errorCode=self::ERROR_PASSWORD_INVALID; else $this->errorCode=self::ERROR_NONE; return !$this->errorCode; } }
让我们特别留意一下 login 动作中出现的下面的 PHP 语句:
$model->attributes=$_POST ['LoginForm'];
正如我们在 安全的特性赋值 中所讲的, 这行代码使用用户提交的数据填充模型。 attributes 属性由 CModel定义,它接受一个名值对数组并将其中的每个值赋给相应的模型特性。 因此如果 $_POST ['LoginForm'] 给了我们这样的一个数组,上面的那段代码也就等同于下面冗长的这段 (假设数组中存在所有所需的特 性):
$model->username=$_POST['LoginForm']['username']; $model->password=$_POST ['LoginForm']['password']; $model->rememberMe=$_POST['LoginForm'] ['rememberMe'];
8. 构建视图
编写 login 视图是很简单的,我们以一个 form 标记开始,它的 action 属性应该是前面讲述的 login 动作的URL。 然后我们需要为 LoginForm 类中声明的属性插入标签和表单域。最后, 我们插入 一个可由用户点击提交此表单的提交按钮。所有这些都可以用纯HTML代码完成。
Yii 提供了几个助手(helper)类简化 视图编写。例如, 要创建一个文本输入域,我们可以调用 CHtml::textField(); 要创建一个下拉列表,则调用 CHtml::dropDownList()。
信息: 你可能想知道使用助手的好处,如果它们所需的代码量和直接写纯HTML的代码量相当的 话。 答案就是助手可以提供比 HTML 代码更多的功能。例如, 如下代码将生成一个文本输入域,它可以在用户修改了其值时触 发表单提交动作。
CHtml::textField($name,$value,array('submit'=>''));
不然的话你就 需要写一大堆 JavaScript 。
下面,我们使用 CHtml 创建一个登录表单。我们假设变量 $model 是 LoginForm 的实例 。
<center class="form"> <?php echo CHtml::beginForm(); ?> <?php echo CHtml::errorSummary($model); ?> <center class="row"> <?php echo CHtml::activeLabel($model,'username'); ?> <?php echo CHtml::activeTextField($model,'username') ?> </center> <center class="row"> <?php echo CHtml::activeLabel($model,'password'); ?> <?php echo CHtml::activePasswordField($model,'password') ?> </center> <center class="row rememberMe"> <?php echo CHtml::activeCheckBox($model,'rememberMe'); ?> <?php echo CHtml::activeLabel($model,'rememberMe'); ?> </center> <center class="row submit"> <?php echo CHtml::submitButton('Login'); ?> </center> <?php echo CHtml::endForm(); ?> </center><!-- form -->
上述代码生成了一个更加动态的表单,例如, CHtml::activeLabel() 生成一个与 指定模型的特性相关的标签。 如果此特性有一个输入错误,此标签的CSS class 将变为 error,通过 CSS 样式改变了标签的外 观。 相似的,CHtml::activeTextField() 为指定模型的特性生成一个文本输入域,并会在错误发生时改变它的 CSS class。
如果我们使用由 yiic 脚本生提供的 CSS 样式文件,生成的表单就会像下面这样:
CSS 样式定义在css目录下,本例使用的为Yii缺省的样式。
从版本 1.1.1 开始,提供了一个新的小物件 CActiveForm 以简化表单创建。 这个小物件可同时提供客户端及服务器端无缝的、一致的验证。使用 CActiveForm, 上面的代 码可重写为:
<center class="form"> <?php $form=$this->beginWidget('CActiveForm'); ?> <?php echo $form->errorSummary($model); ?> <center class="row"> <?php echo $form->label($model,'username'); ?> <?php echo $form->textField($model,'username') ?> </center> <center class="row"> <?php echo $form->label($model,'password'); ?> <?php echo $form->passwordField($model,'password') ?> </center> <center class="row rememberMe"> <?php echo $form->checkBox($model,'rememberMe'); ?> <?php echo $form->label($model,'rememberMe'); ?> </center> <center class="row submit"> <?php echo CHtml::submitButton('Login'); ?> </center> <?php $this->endWidget(); ?> </center><!-- form -->
从下篇开始将逐个介绍Yii框架支持的UI组件包括CActiveForm的用法。
以上就是PHP开发框架Yii Framework教程(8) 使用FormModel的内容,更多相关内容请关注PHP中文网(www.php.cn)!