通過前面的學習,我們瞭解了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 extendsCFormModel { 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 的別名,確保 資料可以匹配一個Regex。
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的內容,更多相關內容請關注topic.alibabacloud.com(www.php.cn)!