首頁  >  文章  >  後端開發  >  Yii2中的場景(scenario)和驗證規則(rule)的詳解

Yii2中的場景(scenario)和驗證規則(rule)的詳解

jacklove
jacklove原創
2018-06-30 18:04:433899瀏覽

Yii2的rule用於對模型屬性進行驗證,scenario用戶定義不同場景下需要驗證的模型,以下這篇文章主要給大家介紹了關於Yii2中場景(scenario)和驗證規則(rule)的相關資料,文中透過範例程式碼介紹的非常詳細,需要的朋友可以參考下。

前言

場景,顧名思義,就是一個情景,一種場面。在yii2中也有場景,這個場景跟你所理解的場景意義差不多。

和使用者有互動的系統必不可少的功能包括收集使用者資料、校驗和處理。在實際業務中,往往還需要將資料持久化儲存。出於安全考慮,開發人員應牢牢掌握「客戶端的輸入都是不可信」的準則,客戶端傳過來的資料先進行過濾和清洗後再儲存或傳遞到內部系統。

Yii2建議使用Model類別來收集和校驗使用者數據,持久化的ActiveRecord類別是其子類別。 Model類別的load和validate兩個方法,分別用來收集和校驗客戶端資料。哪些資料應該被收集,哪些資料需要在什麼場景下驗證,便是本文的主題:場景(scenario)和驗證規則(rule)。

下面話不多說了,來隨著小編一起看看詳細的介紹吧。

系統結構

先引入一個簡單的業務系統:系統中存在學生和教師兩種角色,在資料庫中使用了三張表保存角色資訊:

user: [id, username, password, status, 其他通用屬性]

student: [id, user_id, student_no, grade, class, 其他學生屬性]

teacher: [id, user_id, work_no, title, telphone, 其他教師屬性]

實際業務不限於對這三張表的增刪查改操作。為了簡化問題,後續只討論user和student兩張表的資料變更(給出teacher表是為了讓讀者不認為設計資料庫的人是腦殘:明明可以放到一張表的,為什麼要拆開!)。

學生報名

學生報名是​​典型的增刪查改操作,送分題。學生報名的簡要程式碼範例如下:

public function actionSignup()
{
 $data = Yii::$app->request->post();
 $user = new User();
 $user->load($data);
 if ($user->save()) {
  $student = new Student([
   "user_id" => $user->id,
  ]);
  $student->load($data);
  if ($student->save()) {
   // redirect to success page
  } else {
   $user->delete();
  }
 }
 // render error page
}

#相信有Yii2使用經驗的人都能根據資料庫的欄位約束快速的把User和Student類別的rules方法寫出來。例如User類別文件內容可能如下:

namespace app\models;
class User extends \yii\db\ActiveRecord
{
 public function rules()
 {
  return [   [["username", "password", "status",], "required"],
   ["username", "unique"],
   // other rules
  ];
 }
 // other method
}

定義資料的驗證規則,這是大多數人對rules的第一印象,而且是一個很好的印象:它打回非法的數據,讓正常的數據進入系統。安全的實務應該盡量定義完整的規則,充分驗證資料。也建議每一個Yii2開發人員對內建的核心校驗器熟悉。

修改訊息

修改訊息,也是典型的增刪查改操作。實作程式碼和報名差異不大,這裡只討論兩點:

1、使用者密碼的驗證

註冊時會校驗使用者密碼是否8-16位,密碼的規則可能是: ["password", "string", "length" => [8, 16]] 。明文保存密碼是不可取的,插入資料庫時至少會做MD5加密,password變成32位元。假設使用者修改資訊時未修改密碼,再次儲存時密碼規則校驗出錯(長度不符合),無法儲存!

怎麼解決這個問題呢?翻閱Yii文檔,發現了規則中的when屬性可以救場。一個可能的驗證規則是:

public function rules()
{
 return [
   ["password", "string", "length" => [8, 16], 'when' => function ($model) {
    return $model->isNewRecord;
   }],
   // other rules
  ];

只有在註冊(新增資料)時才校驗密碼欄位。問題解決,完美!

2、防止用戶私自改密碼

假設有個小聰明的傢伙(例如湯姆),發現系統是用Yii框架做的,想搞點小破壞炫耀一下水平。在發送修改訊息的表單時,湯姆增加&password=12345678這一段資料。系統使用$user->load($data)收集使用者輸入,更新password字段,帶來以下後果:rules設定更新時不校驗密碼字段,12345678直接作為password的值儲存到資料庫中。這個操作帶來連鎖反應:當使用者再次登入時,加密過後的密碼與資料庫中的明文密碼不匹配,導致湯姆無法登入系統。煩人的是湯姆是個刺頭,登入不上後整天騷擾客服,不省心!

怎麼樣防止這種情況出現呢?一個解決的方法是阻止修改密碼:

unset($data["password"]); 
$user->load($data);
// 或者
$password = $user->password;
$user->load($data);
$user->password = $password;

#把使用者輸入的密碼過濾掉,私自修改密碼的問題就解決了。

但是問題還沒結束:湯姆可以轉向修改其他字段,比如說性別,身份證等。更嚴重情況是修改student中user_id,就可以更改任意學生的資訊。事情十分嚴重,需要馬上修復漏洞。

可以按照密码的方法,逐个屏蔽受保护属性,但显得啰嗦难看(虽然好使)。如果受保护属性多,可以仅允许白名单进入,具体操作为:新增一个UpdateInfoForm类继承Model,属性是白名单属性合计。用UpdateInfoForm类过滤用户数据,校验通过后再更新到user和student中:

$form = new UpdateInfoForm();
$form->load($data);
if ($form->validate()) {
 $user->load($form->attributes);
 $student->load($form->attributes);
 // next biz
}

这种方式更优雅,但仔细一想代价不小:属性和验证规则要重复写一遍;user和student保存时又重复校验属性。这种方式看起来优雅,实际上却冗余又低效。

scenario的登场,完美的解决解决上述问题。

场景(scenario)

分析上面问题,会发现关键点是批量赋值(massive assignment)和数据校验(validate)两个方法。如果对不同的场景指定赋值字段和检验规则,问题就迎刃而解。

Yii中的scenario有 安全属性 和 活跃属性 两个概念。安全属性用在批量赋值的load方法,只有安全属性才能被赋值;活跃属性用在规则校验的validate方法,在活跃属性集中并且定义了校验规则的属性才会被校验。活跃属性和安全属性的关系是,安全属性是活跃属性的子集。

\yii\base\Model类定义了默认场景:SCENARIO_DEFAULT(值为default)。默认场景下,出现在rules方法中的属性既是活跃属性,又是安全属性(这句话基本正确,看后续解释)。为不同场景指定活跃属性、安全属性以及校验器,可以通过覆盖senarios或rules两个方法实现(几乎每个Model类都会重写rules方法,senarios用得少)。

rules

先看rules方法。默认的属性加校验器定义方式,让每个属性既是安全属性,也是活跃属性。如果想让某个属性不是安全属性(不能通过load批量赋值),在属性名前加感叹号!即可。例如student中的user_id字段:

public function rules()
{
 return [
  ["!user_od", "required"],
  ["!user_id", "integer"],
  ["!user_od", "unique"],
  // other rules
 ];
}

user_id是活跃属性,在写入数据库时会被校验。但它不是安全属性,不能通过load方法进行赋值,解决了安全隐患。

再看rules方法按场景区分校验器规则的做法:定义校验器时on属性指定规则在哪些场景下生效,except属性则排除一些场景(如果不指定on和except,规则对所有场景生效)。例如:

public function rules()
{
 return [
  ["password", "string", "length" => [8, 16], "on" => ["signup"]], // 仅在signup场景时才被验证
  ["status", "integer", "except" => ["signup"], // 除了signup场景,其他情况都校验
  // other rules
 ];
}

在原来基础上新增感叹号和on/except属性,非常简便的就定义了非安全属性以及分场景指定校验规则。

scenarios

另外一种更清晰定义安全属性和活跃属性的做法是重写scenarios方法。scenarios方法返回一个数组,数组的键是场景名称,值是活跃属性集合(包饭安全属性)。例如student表的可能实现如下:

public function scenarios()
{
 return [
  self::SCENARIO_DEFAULT => ["!user_id", "grade", "class", xxxx],
  "update" => ["grade", "class", xxxx],
 ];
}

默认情形下(学生报名),年级、班级这些信息是安全属性,但user_id不是,只能在程序内部赋值,并在插入数据时被校验;在修改信息时,user_id不是活跃属性,既不能被批量赋值,也不需要校验(事实上它不应该改变)。

scenarios方法只能定义活跃属性和安全属性,无法定义校验规则,需要和rules配合使用。

总结

金肯定义完善的数据校验规则

业务复杂时定义多个场景,仔细为每个场景定义安全属性和校验规则

优先使用rules;属性较多、rules复杂时,可以配合scenarios方法迅速理清安全属性和活跃属性

参考

http://www.yiiframework.com/doc-2.0/guide-input-validation.html

您可能感兴趣的文章:

MixPHP、Yii和CodeIgniter的并发压力测试的小结

PHP基于非递归算法实现先序、中序及后序遍历二叉树操作的示例

PHP使用两个栈实现队列功能的方法的讲解

以上是Yii2中的場景(scenario)和驗證規則(rule)的詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

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