首頁  >  文章  >  後端開發  >  Yii框架官方指南系列17—使用表單:建立模型

Yii框架官方指南系列17—使用表單:建立模型

黄舟
黄舟原創
2017-02-13 09:10:581278瀏覽



在編寫表單所需的 HTML 程式碼之前,我們應該先確定來自最終使用者輸入的資料的類型,以及這些資料應符合什麼樣的規則。 模型類別可用於記錄這些資訊。 如模型章節所定義的, 模型是保存使用者輸入和驗證這些輸入的中心位置。

取決於使用使用者所輸入資料的方式,我們可以建立兩種類型的模型。 如果使用者輸入被收集、使用然後丟棄,我們應該建立一個 表單模型; 如果使用者的輸入要儲存到資料庫,我們應使用一個 Active Record 。 兩種類型的模型共享相同的基類 CModel ,它定義了表單所需的通用介面。

注意: 我們在這一節的範例中主要使用了表單模型 。然而,同樣的操作也可應用於 Active Record 模型。

1. 定義模型類別

下面我們建立了一個 LoginForm 模型類別用於在一個登入頁面中收集使用者的輸入。 由於登入資訊只用於驗證用戶,並不需要保存,因此我們將 LoginForm 建立為一個 表單模型。


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

LoginForm 中定義了三個屬性: $username$password他們用於保存用戶輸入的用戶名和密碼,以及用戶是否想記住他的登入的選項。 由於 $rememberMe 有預設的值 false,對應的選項在初始化顯示在登入表單中時將是未勾選狀態。

訊息:

 我們將這些成員變數稱為 特性(attributes) 而不是 屬性(properties),以區別於普通的屬性(properties)。 特性(attribute)是一個主要用於儲存來自使用者輸入或資料庫資料的屬性(propertiy)。

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 為必填項, rules() 回傳的每個規則必須是以下格式:

array('AttributeList', 'Validator', 'on'=>'ScenarioList', ...附加选项)


其中 

AttributeList
/**
 * @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

 。

第一, 

Validator

 可以是模型類別中一個方法的名字,就像上面範例中的 authenticate 。驗證方法必須是下面的結構:


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

第二,

Validator

 可以是驗證器類別的名字,當此規則應用時,被驗證器類別建立以執行實際驗證。規則中的附加選項用於初始化實例的屬性值。 驗證器類別必須繼承自 CValidator第三,

Validator

 可以是一個預先定義的驗證器類別的別名。在上面的範例中, required 名字是CRequiredValidator 的別名,它用來確保所驗證的特性值不為空。 以下是預先定義的驗證器別名的完整清單:

    boolean
  • : CBool​​eanValidator 的別名, 確保特性有一個 CBool​​eanValidator::trueValue 或CBool​​eanValidator::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 场景中是必填项。而 usernamepassword 和 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 场景中,我们需要验证更多的输入,例如 emailaddress, 等。 下面的例子演示了如何在 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() 方法自定义。 正如在接下来的小节中我们将看到的,在模型中指定标签会使我们能够更快的创建出更强大的表单。

以上就是Yii框架官方指南系列17——使用表单:创建模型的内容,更多相关内容请关注PHP中文网(www.php.cn)!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn