您好,登錄后才能下訂單哦!
小編給大家分享一下Yii2中場景scenario和驗(yàn)證規(guī)則rule的示例分析,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
前言
場景,顧名思義,就是一個(gè)情景,一種場面。在yii2中也有場景,這個(gè)場景跟你所理解的場景含義差不多。
和用戶有交互的系統(tǒng)必不可少的功能包括收集用戶數(shù)據(jù)、校驗(yàn)和處理。實(shí)際業(yè)務(wù)中,往往還需要將數(shù)據(jù)進(jìn)行持久化存儲(chǔ)。出于安全考慮,開發(fā)人員應(yīng)當(dāng)牢牢把握“客戶端的輸入都是不可信”的準(zhǔn)則,客戶端傳過來的數(shù)據(jù)先進(jìn)行過濾和清洗后再存儲(chǔ)或傳遞到內(nèi)部系統(tǒng)。
Yii2推薦使用Model類來收集和校驗(yàn)用戶數(shù)據(jù),持久化的ActiveRecord類是其子類。Model類的load和validate兩個(gè)方法,分別用來收集和校驗(yàn)客戶端數(shù)據(jù)。哪些數(shù)據(jù)應(yīng)該被收集,哪些數(shù)據(jù)需要在什么場景下驗(yàn)證,便是本文的主題:場景(scenario)和驗(yàn)證規(guī)則(rule)。
系統(tǒng)結(jié)構(gòu)
先引入一個(gè)簡單的業(yè)務(wù)系統(tǒng):系統(tǒng)中存在學(xué)生和教師兩種角色,數(shù)據(jù)庫中使用了三張表保存角色信息:
user: [id, username, password, status, 其他通用屬性]
student: [id, user_id, student_no, grade, class, 其他學(xué)生屬性]
teacher: [id, user_id, work_no, title, telphone, 其他教師屬性]
實(shí)際業(yè)務(wù)不限于對這三張表的增刪查改操作。為了簡化問題,后續(xù)僅討論user和student兩張表的數(shù)據(jù)變更(給出teacher表是為了讓讀者不認(rèn)為設(shè)計(jì)數(shù)據(jù)庫的人是腦殘:明明可以放到一張表的,為什么要拆開?。?/p>
學(xué)生報(bào)名
學(xué)生報(bào)名是典型的增刪查改操作,送分題。學(xué)生報(bào)名的簡要代碼示例如下:
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使用經(jīng)驗(yàn)的人都能根據(jù)數(shù)據(jù)庫的字段約束快速的把User和Student類的rules方法寫出來。例如User類文件內(nèi)容可能如下:
namespace app\models; class User extends \yii\db\ActiveRecord { public function rules() { return [ [["username", "password", "status",], "required"], ["username", "unique"], // other rules ]; } // other method }
定義數(shù)據(jù)的驗(yàn)證規(guī)則,這是大多數(shù)人對rules的第一印象,并且是一個(gè)很好的印象:它打回非法的數(shù)據(jù),讓正常的數(shù)據(jù)進(jìn)入系統(tǒng)中。安全的實(shí)踐應(yīng)該盡量定義完整的規(guī)則,充分驗(yàn)證數(shù)據(jù)。也建議每一個(gè)Yii2開發(fā)人員對內(nèi)置的核心校驗(yàn)器熟悉。
修改信息
修改信息,也是典型的增刪查改操作。實(shí)現(xiàn)代碼和報(bào)名差別不大,這里僅討論兩點(diǎn):
1、用戶密碼的驗(yàn)證
注冊時(shí)會(huì)校驗(yàn)用戶密碼是否8-16位,密碼的規(guī)則可能是: ["password", "string", "length" => [8, 16]]
。明文保存密碼是不可取的,插入數(shù)據(jù)庫時(shí)至少會(huì)做MD5加密,password變成32位。假設(shè)用戶修改信息時(shí)未修改密碼,再次保存時(shí)密碼規(guī)則校驗(yàn)出錯(cuò)(長度不符合),無法保存!
怎么解決這個(gè)問題呢?翻閱Yii文檔,發(fā)現(xiàn)了規(guī)則中的when屬性可以救場。一種可能的驗(yàn)證規(guī)則是:
public function rules() { return [ ["password", "string", "length" => [8, 16], 'when' => function ($model) { return $model->isNewRecord; }], // other rules ];
只有在注冊(新增數(shù)據(jù))時(shí)才校驗(yàn)密碼字段。問題解決,完美!
2、防止用戶私自改密碼
假設(shè)有個(gè)小聰明的家伙(例如湯姆),發(fā)現(xiàn)系統(tǒng)是用Yii框架做的,想搞點(diǎn)小破壞炫耀一下水平。在發(fā)送修改信息的表單時(shí),湯姆增加&password=12345678這一段數(shù)據(jù)。系統(tǒng)使用$user->load($data)
收集用戶輸入,更新password字段,帶來如下后果:rules設(shè)置更新時(shí)不校驗(yàn)密碼字段,12345678直接作為password的值保存到數(shù)據(jù)庫中。這個(gè)操作帶來連鎖反應(yīng):用戶再次登錄時(shí),加密過后的密碼與數(shù)據(jù)庫中的明文密碼不匹配,導(dǎo)致湯姆無法登錄系統(tǒng)。煩人的是湯姆是個(gè)刺頭,登錄不上后整天騷擾客服,不省心!
怎么樣防止這種情況出現(xiàn)呢?一種解決的方法是阻止修改密碼:
unset($data["password"]); $user->load($data); // 或者 $password = $user->password; $user->load($data); $user->password = $password;
把用戶輸入的密碼過濾掉,私自修改密碼的問題就解決了。
但是問題還沒有結(jié)束:湯姆可以轉(zhuǎn)向修改其他字段,比如說性別,身份證等。更嚴(yán)重情況是修改student中user_id,就可以更改任意學(xué)生的信息。事情十分嚴(yán)重,需要馬上修復(fù)漏洞。
可以按照密碼的方法,逐個(gè)屏蔽受保護(hù)屬性,但顯得啰嗦難看(雖然好使)。如果受保護(hù)屬性多,可以僅允許白名單進(jìn)入,具體操作為:新增一個(gè)UpdateInfoForm類繼承Model,屬性是白名單屬性合計(jì)。用UpdateInfoForm類過濾用戶數(shù)據(jù),校驗(yàn)通過后再更新到user和student中:
$form = new UpdateInfoForm(); $form->load($data); if ($form->validate()) { $user->load($form->attributes); $student->load($form->attributes); // next biz }
這種方式更優(yōu)雅,但仔細(xì)一想代價(jià)不?。簩傩院万?yàn)證規(guī)則要重復(fù)寫一遍;user和student保存時(shí)又重復(fù)校驗(yàn)屬性。這種方式看起來優(yōu)雅,實(shí)際上卻冗余又低效。
scenario的登場,完美的解決解決上述問題。
場景(scenario)
分析上面問題,會(huì)發(fā)現(xiàn)關(guān)鍵點(diǎn)是批量賦值(massive assignment)和數(shù)據(jù)校驗(yàn)(validate)兩個(gè)方法。如果對不同的場景指定賦值字段和檢驗(yàn)規(guī)則,問題就迎刃而解。
Yii中的scenario有 安全屬性 和 活躍屬性 兩個(gè)概念。安全屬性用在批量賦值的load方法,只有安全屬性才能被賦值;活躍屬性用在規(guī)則校驗(yàn)的validate方法,在活躍屬性集中并且定義了校驗(yàn)規(guī)則的屬性才會(huì)被校驗(yàn)。活躍屬性和安全屬性的關(guān)系是,安全屬性是活躍屬性的子集。
\yii\base\Model類定義了默認(rèn)場景:SCENARIO_DEFAULT(值為default)。默認(rèn)場景下,出現(xiàn)在rules方法中的屬性既是活躍屬性,又是安全屬性(這句話基本正確,看后續(xù)解釋)。為不同場景指定活躍屬性、安全屬性以及校驗(yàn)器,可以通過覆蓋senarios或rules兩個(gè)方法實(shí)現(xiàn)(幾乎每個(gè)Model類都會(huì)重寫rules方法,senarios用得少)。
rules
先看rules方法。默認(rèn)的屬性加校驗(yàn)器定義方式,讓每個(gè)屬性既是安全屬性,也是活躍屬性。如果想讓某個(gè)屬性不是安全屬性(不能通過load批量賦值),在屬性名前加感嘆號(hào)!即可。例如student中的user_id字段:
public function rules() { return [ ["!user_od", "required"], ["!user_id", "integer"], ["!user_od", "unique"], // other rules ]; }
user_id是活躍屬性,在寫入數(shù)據(jù)庫時(shí)會(huì)被校驗(yàn)。但它不是安全屬性,不能通過load方法進(jìn)行賦值,解決了安全隱患。
再看rules方法按場景區(qū)分校驗(yàn)器規(guī)則的做法:定義校驗(yàn)器時(shí)on屬性指定規(guī)則在哪些場景下生效,except屬性則排除一些場景(如果不指定on和except,規(guī)則對所有場景生效)。例如:
public function rules() { return [ ["password", "string", "length" => [8, 16], "on" => ["signup"]], // 僅在signup場景時(shí)才被驗(yàn)證 ["status", "integer", "except" => ["signup"], // 除了signup場景,其他情況都校驗(yàn) // other rules ]; }
在原來基礎(chǔ)上新增感嘆號(hào)和on/except屬性,非常簡便的就定義了非安全屬性以及分場景指定校驗(yàn)規(guī)則。
scenarios
另外一種更清晰定義安全屬性和活躍屬性的做法是重寫scenarios方法。scenarios方法返回一個(gè)數(shù)組,數(shù)組的鍵是場景名稱,值是活躍屬性集合(包飯安全屬性)。例如student表的可能實(shí)現(xiàn)如下:
public function scenarios() { return [ self::SCENARIO_DEFAULT => ["!user_id", "grade", "class", xxxx], "update" => ["grade", "class", xxxx], ]; }
默認(rèn)情形下(學(xué)生報(bào)名),年級(jí)、班級(jí)這些信息是安全屬性,但user_id不是,只能在程序內(nèi)部賦值,并在插入數(shù)據(jù)時(shí)被校驗(yàn);在修改信息時(shí),user_id不是活躍屬性,既不能被批量賦值,也不需要校驗(yàn)(事實(shí)上它不應(yīng)該改變)。
scenarios方法只能定義活躍屬性和安全屬性,無法定義校驗(yàn)規(guī)則,需要和rules配合使用。
以上是“Yii2中場景scenario和驗(yàn)證規(guī)則rule的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。