模型是MVC模式中的一部分,代表業務數據、規則和邏輯的對象。
可繼承yii\base\Model或它的子類定義模型類。
基類包括的特性有:
1.屬性:代表可像普通類屬性或數組一樣被訪問的業務數據
2.屬性標簽: 指定屬性顯示出來的標簽
3.塊賦值: 支持一步給許多屬性賦值
4.驗證規則:確保輸入數據符合所申明的驗證規則
5.數據導出:允許模型數據導出為自定義格式的數組
Model類也是更多高級模型如Active Record活動記錄的基類。
屬性
模型通過屬性來代表業務邏輯,每個屬性像是模型的公有可訪問屬性。
attributes() 指定模型所擁有的屬性。
像訪問一個對象屬性一樣訪問模型的屬性:
$model = new \app\models\ContactForm; // "name" 是 ContactForm 模型的屬性 $model->name = 'example'; echo $model->name;
像訪問數組單元項一樣訪問屬性,因為yii\base\Model支持 ArrayAccess 數組訪問和 ArrayIterator 數組迭代器:
$model = new \app\models\ContactForm; // 像訪問數組單元項一樣訪問屬性 $model['name'] = 'example'; echo $model['name'];
// 迭代器遍歷模型
foreach ($model as $name => $value) {
echo "$name: $value\n";
定義屬性
所有的非靜態公有成員變量都是屬性。
ContactForm 模型類有四個屬性
name, email, subject and body, ContactForm 模型用來代表從 HTML 表單獲取的輸入數據。
namespace app\models;
use yii\base\Model;
class ContactForm extends Model
{
public $name;
public $email;
public $subject;
public $body;
}
另一種方式是覆蓋yii\base\Model::attributes()來定義屬性,該方法返回模型的屬性名,ActiveRecord返回對應數據表列名作為屬性名。
屬性標簽
當屬性顯示或獲取輸入時,經常要顯示屬性相關標簽,屬性名為firstName,在表單輸入或者錯誤提示處,可改成更友好的First Name標簽。
可以調用 [[yii\base\Model::getAttributeLabel()]] 獲取屬性的標簽,例如:
$model = new \app\models\ContactForm;
// 顯示為 "Name"
echo $model->getAttributeLabel('name')
默認情況下,屬性標簽會自動從屬性名生成,自動將駝峰式大小寫變量名轉換為多個首字母大寫的單詞。
如果你不想用自動生成的標簽,可以覆蓋 [[yii\base\Model::attributeLabels()]] 方法明確指定屬性標簽
public function attributeLabels()
{
return [
'name' => 'Your name',
'email' => 'Your email address',
'subject' => 'Subject',
'body' => 'Content',
];
}
應用支持多語言的情況下,可翻譯屬性標簽, 可在[[yii\base\Model::attributeLabels()|attributeLabels()]] 方法中定義,如下所示:
public function attributeLabels()
{
return [
'name' => \Yii::t('app', 'Your name'),
'email' => \Yii::t('app', 'Your email address'),
'subject' => \Yii::t('app', 'Subject'),
'body' => \Yii::t('app', 'Content'),
];
}
還可以根據條件定義標簽,例如通過使用模型的scenario場景,對相同的屬性返回不同的標簽。
場景
模型可能在多個場景下使用,User模塊可能在收集用戶登錄輸入,也可能會在用戶注冊時使用。在不同的場景下,模型可能會使用不同的業務規則和邏輯,例如email屬性在注冊時強制要求有,但在登錄時不需要。
模型使用yii\base\Model::scenario 屬性保持使用場景的跟蹤,默認情況下,模型支持一個名為default的場景。
設置場景的方法:
// 場景作為屬性來設置 $model = new User; $model->scenario = 'login'; // 場景通過構造初始化配置來設置 $model = new User(['scenario' => 'login']);
默認情況下,模型支持的場景由模型中申明的驗證規則來決定,但可以通過覆蓋yii\base\Model::scenarios(),來自定義行為。
namespace app\models;
use yii\db\ActiveRecord;
class User extends ActiveRecord
{
public function scenarios()
{
return [
'login' => ['username', 'password'],
'register' => ['username', 'email', 'password'],
];
}
}
scenarios() 方法返回一個數組,數組的鍵為場景名,值為對應的 active attributes 活動屬性。
活動屬性可被 塊賦值 並遵循驗證規則在上述例子中,username 和 password 在 login 場景中啟用,在 register 場景中, 除了 username and password 外 email 也被啟用。
scenarios() 方法默認實現會返回所有[[yii\base\Model::rules()]]方法申明的驗證規則中的場景, 當覆蓋 scenarios()時,如果你想在默認場景外使用新場景,可以編寫類似如下代碼:
namespace app\models;
use yii\db\ActiveRecord;
class User extends ActiveRecord
{
public function scenarios()
{
$scenarios = parent::scenarios();
$scenarios['login'] = ['username', 'password'];
$scenarios['register'] = ['username', 'email', 'password'];
return $scenarios;
}
}
場景特性主要在驗證 和 屬性塊賦值 中使用。 你也可以用於其他目的,例如可基於不同的場景定義不同的 屬性標簽。
驗證規則
當模型接收到終端用戶輸入的數據,數據應當滿足某種規則(稱為 驗證規則, 也稱為 業務規則)。
可調用 [[yii\base\Model::validate()]] 來驗證接收到的數據, 該方法使用yii\base\Model::rules()申明的驗證規則來驗證每個相關屬性, 如果沒有找到錯誤,會返回 true,否則它會將錯誤保存在 [[yii\base\Model::errors]] 屬性中並返回 false
$model = new \app\models\ContactForm;
// 用戶輸入數據賦值到模型屬性
$model->attributes = \Yii::$app->request->post('ContactForm');
if ($model->validate()) {
// 所有輸入數據都有效 all inputs are valid
} else {
// 驗證失敗:$errors 是一個包含錯誤信息的數組
$errors = $model->errors;
}
通過覆蓋 [[yii\base\Model::rules()]] 方法指定模型屬性應該滿足的規則來申明模型相關驗證規則。下述例子顯示 ContactForm 模型申明的驗證規則:
public function rules()
{
return [
// name, email, subject 和 body 屬性必須有值
[['name', 'email', 'subject', 'body'], 'required'],
// email 屬性必須是一個有效的電子郵箱地址
['email', 'email'],
];
}
一條規則可用來驗證一個或多個屬性,一個屬性可對應一條或多條規則。
有時你想一條規則只在某個 場景 下應用,為此你可以指定規則的 on 屬性,如下所示:
public function rules()
{
return [
// 在"register" 場景下 username, email 和 password 必須有值
[['username', 'email', 'password'], 'required', 'on' => 'register'],
// 在 "login" 場景下 username 和 password 必須有值
[['username', 'password'], 'required', 'on' => 'login'],
];
}
如果沒有指定 on 屬性,規則會在所有場景下應用,一個屬性只會屬於 scenarios()中定義的活動屬性且在 rules()申明對應一條或多條活動規則的情況下被驗證。
塊賦值
塊賦值只用一行代碼將用戶所有輸入填充到一個模型,非常方便, 它直接將輸入數據對應填充到yii\base\Model::attributes 屬性。
$model = new \app\models\ContactForm;
$model->attributes = \Yii::$app->request->post('ContactForm');
$model = new \app\models\ContactForm;
$data = \Yii::$app->request->post('ContactForm', []);
$model->name = isset($data['name']) ? $data['name'] : null;
$model->email = isset($data['email']) ? $data['email'] : null;
$model->subject = isset($data['subject']) ? $data['subject'] : null;
$model->body = isset($data['body']) ? $data['body'] : null;
安全屬性
塊賦值只應用在模型當前場景列出的稱之為安全屬性的屬性上,User模型申明的以下場景,當當前場景為login時候,只有username和password可被塊賦值,其他屬性不會被塊賦值。
public function scenarios()
{
return [
'login' => ['username', 'password'],
'register' => ['username', 'email', 'password'],
];
}
非安全屬性
如上所述,[[yii\base\Model::scenarios()]] 方法提供兩個用處:定義哪些屬性應被驗證,定義哪些屬性安全。 在某些情況下,你可能想驗證一個屬性但不想讓他是安全的,可在 scenarios()方法中屬性名加一個驚嘆號 !。 例如像如下的 secret 屬性。
public function scenarios()
{
return [
'login' => ['username', 'password', '!secret'],
];
}
當模型在 login 場景下,三個屬性都會被驗證,但只有 username 和 password 屬性會被塊賦值, 要對 secret 屬性賦值,必須像如下例子明確對它賦值。
$model->secret = $secret;
數據導出
將模型轉換為數組最簡單的方式是使用 yii\base\Model::attributes 屬性,例如:
$post = \app\models\Post::findOne(100); $array = $post->attributes;
yii\base\Model::attributes 屬性會返回 所有 yii\base\Model::attributes() 申明的屬性的值。
更靈活和強大的將模型轉換為數組的方式是使用 [[yii\base\Model::toArray()]] 方法, 它的行為默認和 [[yii\base\Model::attributes]] 相同, 但是它允許你選擇哪些稱之為字段的數據項放入到結果數組中並同時被格式化。
字段
字段是模型通過調用[[yii\base\Model::toArray()]]生成的數組的單元名
默認情況下,字段名對應屬性名,但是你可以通過覆蓋 [[yii\base\Model::fields()|fields()]] 和/或[[yii\base\Model::extraFields()|extraFields()]] 方法來改變這種行為, 兩個方法都返回一個字段定義列表,fields() 方法定義的字段是默認字段,表示 toArray()方法默認會返回這些字段。 extraFields()方法定義額外可用字段,通過 toArray()方法指定$expand 參數來返回這些額外可用字段。
可通過覆蓋 fields() 來增加、刪除、重命名和重定義字段,fields() 方法返回值應為數組, 數組的鍵為字段名,數組的值為對應的可為屬性名或匿名函數返回的字段定義對應的值。 特使情況下,如果字段名屬性定義名相同,可以省略數組鍵,例如:
// 明確列出每個字段,特別用於你想確保數據表或模型屬性改變不會導致你的字段改變(保證后端的 API 兼容).
public function fields()
{
return [
// 字段名和屬性名相同
'id',
// 字段名為 "email",對應屬性名為 "email_address"
'email' => 'email_address',
// 字段名為 "name", 值通過 PHP 代碼返回
'name' => function () {
return $this->first_name . ' ' . $this->last_name;
},
];
}
// 過濾掉一些字段,特別用於你想繼承父類實現並不想用一些敏感字段
public function fields()
{
$fields = parent::fields();
// 去掉一些包含敏感信息的字段
unset($fields['auth_key'], $fields['password_hash'], $fields['password_reset_token']);
return $fields;
}
由於模型的所有屬性會被包含在導出數組,最好檢查數據確保沒包含敏感數據, 如果有敏感數據,應覆蓋 fields() 方法過濾掉,在上述列子中,我們選擇過濾掉 auth_key, password_hashand password_reset_token。
•可包含屬性來展示業務數據;
•可包含驗證規則確保數據有效和完整;
•可包含方法實現業務邏輯;
•不應直接訪問請求,session 和其他環境數據,這些數據應該由控制器傳入到模型;
•應避免嵌入 HTML 或其他展示代碼,這些代碼最好在 視圖中處理;
•單個模型中避免太多的 場景.
為確保模型好維護,最好使用以下策略:
•定義可被多個 應用主體 或 模塊 共享的模型基類集合。 這些模型類應包含通用的最小規則集合和邏輯。
•在每個使用模型的 應用主體 或 模塊中, 通過繼承對應的模型基類來定義具體的模型類,具體模型類包含應用主體或模塊指定的規則和邏輯。
