模型是?MVC?模式中的一部分, 是代表業(yè)務(wù)數(shù)據(jù)、規(guī)則和邏輯的對(duì)象。
可通過(guò)繼承 yii\base\Model 或它的子類(lèi)定義模型類(lèi),基類(lèi)yii\base\Model支持許多實(shí)用的特性:
Model
?類(lèi)也是更多高級(jí)模型如Active Record 活動(dòng)記錄的基類(lèi), 更多關(guān)于這些高級(jí)模型的詳情請(qǐng)參考相關(guān)手冊(cè)。
補(bǔ)充:模型并不強(qiáng)制一定要繼承yii\base\Model,但是由于很多組件支持yii\base\Model,最好使用它做為模型基類(lèi)。
模型通過(guò)?屬性?來(lái)代表業(yè)務(wù)數(shù)據(jù),每個(gè)屬性像是模型的公有可訪問(wèn)屬性, yii\base\Model::attributes() 指定模型所擁有的屬性。
可像訪問(wèn)一個(gè)對(duì)象屬性一樣訪問(wèn)模型的屬性:
$model = new \app\models\ContactForm;
// "name" 是ContactForm模型的屬性
$model->name = 'example';
echo $model->name;
也可像訪問(wèn)數(shù)組單元項(xiàng)一樣訪問(wèn)屬性,這要感謝yii\base\Model支持?ArrayAccess 數(shù)組訪問(wèn)?和?ArrayIterator 數(shù)組迭代器:
$model = new \app\models\ContactForm;
// 像訪問(wèn)數(shù)組單元項(xiàng)一樣訪問(wèn)屬性
$model['name'] = 'example';
echo $model['name'];
// 迭代器遍歷模型
foreach ($model as $name => $value) {
echo "$name: $value\n";
}
默認(rèn)情況下你的模型類(lèi)直接從yii\base\Model繼承,所有?non-static public非靜態(tài)公有?成員變量都是屬性。 例如,下述ContactForm
模型類(lèi)有四個(gè)屬性name
,?email
,?subject
?and?body
,?ContactForm
?模型用來(lái)代表從HTML表單獲取的輸入數(shù)據(jù)。
namespace app\models;
use yii\base\Model;
class ContactForm extends Model
{
public $name;
public $email;
public $subject;
public $body;
}
另一種方式是可覆蓋 yii\base\Model::attributes() 來(lái)定義屬性,該方法返回模型的屬性名。 例如 yii\db\ActiveRecord 返回對(duì)應(yīng)數(shù)據(jù)表列名作為它的屬性名, 注意可能需要覆蓋魔術(shù)方法如__get()
,?__set()
使屬性像普通對(duì)象屬性被訪問(wèn)。
當(dāng)屬性顯示或獲取輸入時(shí),經(jīng)常要顯示屬性相關(guān)標(biāo)簽,例如假定一個(gè)屬性名為firstName
, 在某些地方如表單輸入或錯(cuò)誤信息處,你可能想顯示對(duì)終端用戶(hù)來(lái)說(shuō)更友好的?First Name
?標(biāo)簽。
可以調(diào)用 yii\base\Model::getAttributeLabel() 獲取屬性的標(biāo)簽,例如:
$model = new \app\models\ContactForm;
// 顯示為 "Name"
echo $model->getAttributeLabel('name');
默認(rèn)情況下,屬性標(biāo)簽通過(guò)yii\base\Model::generateAttributeLabel()方法自動(dòng)從屬性名生成. 它會(huì)自動(dòng)將駝峰式大小寫(xiě)變量名轉(zhuǎn)換為多個(gè)首字母大寫(xiě)的單詞,例如?username
?轉(zhuǎn)換為?Username
,?firstName
?轉(zhuǎn)換為?First Name
。
如果你不想用自動(dòng)生成的標(biāo)簽,可以覆蓋 yii\base\Model::attributeLabels() 方法明確指定屬性標(biāo)簽,例如:
namespace app\models;
use yii\base\Model;
class ContactForm extends Model
{
public $name;
public $email;
public $subject;
public $body;
public function attributeLabels()
{
return [
'name' => 'Your name',
'email' => 'Your email address',
'subject' => 'Subject',
'body' => 'Content',
];
}
}
應(yīng)用支持多語(yǔ)言的情況下,可翻譯屬性標(biāo)簽, 可在 yii\base\Model::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'),
];
}
甚至可以根據(jù)條件定義標(biāo)簽,例如通過(guò)使用模型的?scenario場(chǎng)景, 可對(duì)相同的屬性返回不同的標(biāo)簽。
補(bǔ)充:屬性標(biāo)簽是?視圖一部分,但是在模型中申明標(biāo)簽通常非常方便,并可行程非常簡(jiǎn)潔可重用代碼。
模型可能在多個(gè)?場(chǎng)景?下使用,例如?User
?模塊可能會(huì)在收集用戶(hù)登錄輸入,也可能會(huì)在用戶(hù)注冊(cè)時(shí)使用。 在不同的場(chǎng)景下,模型可能會(huì)使用不同的業(yè)務(wù)規(guī)則和邏輯,例如?email
?屬性在注冊(cè)時(shí)強(qiáng)制要求有,但在登陸時(shí)不需要。
模型使用 yii\base\Model::scenario 屬性保持使用場(chǎng)景的跟蹤, 默認(rèn)情況下,模型支持一個(gè)名為?default
?的場(chǎng)景,如下展示兩種設(shè)置場(chǎng)景的方法:
// 場(chǎng)景作為屬性來(lái)設(shè)置
$model = new User;
$model->scenario = 'login';
// 場(chǎng)景通過(guò)構(gòu)造初始化配置來(lái)設(shè)置
$model = new User(['scenario' => 'login']);
默認(rèn)情況下,模型支持的場(chǎng)景由模型中申明的?驗(yàn)證規(guī)則?來(lái)決定, 但你可以通過(guò)覆蓋yii\base\Model::scenarios()方法來(lái)自定義行為,如下所示:
namespace app\models;
use yii\db\ActiveRecord;
class User extends ActiveRecord
{
public function scenarios()
{
return [
'login' => ['username', 'password'],
'register' => ['username', 'email', 'password'],
];
}
}
補(bǔ)充:在上述和下述的例子中,模型類(lèi)都是繼承yii\db\ActiveRecord, 因?yàn)槎鄨?chǎng)景的使用通常發(fā)生在Active Record?類(lèi)中.
scenarios()
?方法返回一個(gè)數(shù)組,數(shù)組的鍵為場(chǎng)景名,值為對(duì)應(yīng)的?active attributes活動(dòng)屬性。 活動(dòng)屬性可被?塊賦值?并遵循驗(yàn)證規(guī)則在上述例子中,username
?和?password
?在login
場(chǎng)景中啟用,在?register
?場(chǎng)景中, 除了?username
?and?password
?外?email
也被啟用。
scenarios()
?方法默認(rèn)實(shí)現(xiàn)會(huì)返回所有yii\base\Model::rules()方法申明的驗(yàn)證規(guī)則中的場(chǎng)景, 當(dāng)覆蓋scenarios()
時(shí),如果你想在默認(rèn)場(chǎng)景外使用新場(chǎng)景,可以編寫(xiě)類(lèi)似如下代碼:
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;
}
}
場(chǎng)景特性主要在驗(yàn)證?和?屬性塊賦值?中使用。 你也可以用于其他目的,例如可基于不同的場(chǎng)景定義不同的?屬性標(biāo)簽。
當(dāng)模型接收到終端用戶(hù)輸入的數(shù)據(jù),數(shù)據(jù)應(yīng)當(dāng)滿(mǎn)足某種規(guī)則(稱(chēng)為?驗(yàn)證規(guī)則, 也稱(chēng)為?業(yè)務(wù)規(guī)則)。 例如假定ContactForm
模型,你可能想確保所有屬性不為空且?email
?屬性包含一個(gè)有效的郵箱地址, 如果某個(gè)屬性的值不滿(mǎn)足對(duì)應(yīng)的業(yè)務(wù)規(guī)則,相應(yīng)的錯(cuò)誤信息應(yīng)顯示,以幫助用戶(hù)修正錯(cuò)誤。
可調(diào)用 yii\base\Model::validate() 來(lái)驗(yàn)證接收到的數(shù)據(jù), 該方法使用yii\base\Model::rules()申明的驗(yàn)證規(guī)則來(lái)驗(yàn)證每個(gè)相關(guān)屬性, 如果沒(méi)有找到錯(cuò)誤,會(huì)返回 true,否則它會(huì)將錯(cuò)誤保存在 yii\base\Model::errors 屬性中并返回false,例如:
$model = new \app\models\ContactForm;
// 用戶(hù)輸入數(shù)據(jù)賦值到模型屬性
$model->attributes = \Yii::$app->request->post('ContactForm');
if ($model->validate()) {
// 所有輸入數(shù)據(jù)都有效 all inputs are valid
} else {
// 驗(yàn)證失?。?errors 是一個(gè)包含錯(cuò)誤信息的數(shù)組
$errors = $model->errors;
}
通過(guò)覆蓋 yii\base\Model::rules() 方法指定模型屬性應(yīng)該滿(mǎn)足的規(guī)則來(lái)申明模型相關(guān)驗(yàn)證規(guī)則。 下述例子顯示ContactForm
模型申明的驗(yàn)證規(guī)則:
public function rules()
{
return [
// name, email, subject 和 body 屬性必須有值
[['name', 'email', 'subject', 'body'], 'required'],
// email 屬性必須是一個(gè)有效的電子郵箱地址
['email', 'email'],
];
}
一條規(guī)則可用來(lái)驗(yàn)證一個(gè)或多個(gè)屬性,一個(gè)屬性可對(duì)應(yīng)一條或多條規(guī)則。 更多關(guān)于如何申明驗(yàn)證規(guī)則的詳情請(qǐng)參考?驗(yàn)證輸入?一節(jié).
有時(shí)你想一條規(guī)則只在某個(gè)?場(chǎng)景?下應(yīng)用,為此你可以指定規(guī)則的?on
?屬性,如下所示:
public function rules()
{
return [
// 在"register" 場(chǎng)景下 username, email 和 password 必須有值
[['username', 'email', 'password'], 'required', 'on' => 'register'],
// 在 "login" 場(chǎng)景下 username 和 password 必須有值
[['username', 'password'], 'required', 'on' => 'login'],
];
}
如果沒(méi)有指定?on
?屬性,規(guī)則會(huì)在所有場(chǎng)景下應(yīng)用, 在當(dāng)前yii\base\Model::scenario 下應(yīng)用的規(guī)則稱(chēng)之為?active rule活動(dòng)規(guī)則。
一個(gè)屬性只會(huì)屬于scenarios()
中定義的活動(dòng)屬性且在rules()
申明對(duì)應(yīng)一條或多條活動(dòng)規(guī)則的情況下被驗(yàn)證。
塊賦值只用一行代碼將用戶(hù)所有輸入填充到一個(gè)模型,非常方便, 它直接將輸入數(shù)據(jù)對(duì)應(yīng)填充到 yii\base\Model::attributes 屬性。 以下兩段代碼效果是相同的,都是將終端用戶(hù)輸入的表單數(shù)據(jù)賦值到?ContactForm
?模型的屬性, 明顯地前一段塊賦值的代碼比后一段代碼簡(jiǎn)潔且不易出錯(cuò)。
$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;
塊賦值只應(yīng)用在模型當(dāng)前yii\base\Model::scenario場(chǎng)景yii\base\Model::scenarios()方法 列出的稱(chēng)之為?安全屬性?的屬性上,例如,如果User
模型申明以下場(chǎng)景, 當(dāng)當(dāng)前場(chǎng)景為login
時(shí)候,只有username
?and?password
?可被塊賦值,其他屬性不會(huì)被賦值。
public function scenarios()
{
return [
'login' => ['username', 'password'],
'register' => ['username', 'email', 'password'],
];
}
補(bǔ)充: 塊賦值只應(yīng)用在安全屬性上,因?yàn)槟阆肟刂颇男傩詴?huì)被終端用戶(hù)輸入數(shù)據(jù)所修改, 例如,如果?
User
?模型有一個(gè)permission
屬性對(duì)應(yīng)用戶(hù)的權(quán)限, 你可能只想讓這個(gè)屬性在后臺(tái)界面被管理員修改。
由于默認(rèn)yii\base\Model::scenarios()的實(shí)現(xiàn)會(huì)返回yii\base\Model::rules()所有屬性和數(shù)據(jù), 如果不覆蓋這個(gè)方法,表示所有只要出現(xiàn)在活動(dòng)驗(yàn)證規(guī)則中的屬性都是安全的。
為此,提供一個(gè)特別的別名為?safe
?的驗(yàn)證器來(lái)申明哪些屬性是安全的不需要被驗(yàn)證, 如下示例的規(guī)則申明?title
?和?description
都為安全屬性。
public function rules()
{
return [
[['title', 'description'], 'safe'],
];
}
如上所述,yii\base\Model::scenarios() 方法提供兩個(gè)用處:定義哪些屬性應(yīng)被驗(yàn)證,定義哪些屬性安全。 在某些情況下,你可能想驗(yàn)證一個(gè)屬性但不想讓他是安全的,可在scenarios()
方法中屬性名加一個(gè)驚嘆號(hào)?!
。 例如像如下的secret
屬性。
public function scenarios()
{
return [
'login' => ['username', 'password', '!secret'],
];
}
當(dāng)模型在?login
?場(chǎng)景下,三個(gè)屬性都會(huì)被驗(yàn)證,但只有?username
和?password
?屬性會(huì)被塊賦值, 要對(duì)secret
屬性賦值,必須像如下例子明確對(duì)它賦值。
$model->secret = $secret;
模型通常要導(dǎo)出成不同格式,例如,你可能想將模型的一個(gè)集合轉(zhuǎn)成JSON或Excel格式, 導(dǎo)出過(guò)程可分解為兩個(gè)步驟,第一步,模型轉(zhuǎn)換成數(shù)組;第二步,數(shù)組轉(zhuǎn)換成所需要的格式。 你只需要關(guān)注第一步,因?yàn)榈诙娇杀煌ㄓ玫臄?shù)據(jù)轉(zhuǎn)換器如yii\web\JsonResponseFormatter來(lái)完成。
將模型轉(zhuǎn)換為數(shù)組最簡(jiǎn)單的方式是使用 yii\base\Model::attributes 屬性,例如:
$post = \app\models\Post::findOne(100);
$array = $post->attributes;
yii\base\Model::attributes 屬性會(huì)返回?所有?yii\base\Model::attributes() 申明的屬性的值。
更靈活和強(qiáng)大的將模型轉(zhuǎn)換為數(shù)組的方式是使用 yii\base\Model::toArray() 方法, 它的行為默認(rèn)和 yii\base\Model::attributes 相同, 但是它允許你選擇哪些稱(chēng)之為字段的數(shù)據(jù)項(xiàng)放入到結(jié)果數(shù)組中并同時(shí)被格式化。 實(shí)際上,它是導(dǎo)出模型到 RESTful 網(wǎng)頁(yè)服務(wù)開(kāi)發(fā)的默認(rèn)方法,詳情請(qǐng)參閱響應(yīng)格式.
字段是模型通過(guò)調(diào)用yii\base\Model::toArray()生成的數(shù)組的單元名。
默認(rèn)情況下,字段名對(duì)應(yīng)屬性名,但是你可以通過(guò)覆蓋 yii\base\Model::fields() 和/或 yii\base\Model::extraFields() 方法來(lái)改變這種行為, 兩個(gè)方法都返回一個(gè)字段定義列表,fields()
?方法定義的字段是默認(rèn)字段,表示toArray()
方法默認(rèn)會(huì)返回這些字段。extraFields()
方法定義額外可用字段,通過(guò)toArray()
方法指定$expand
參數(shù)來(lái)返回這些額外可用字段。 例如如下代碼會(huì)返回fields()
方法定義的所有字段和extraFields()
方法定義的prettyName
?and?fullAddress
字段。
$array = $model->toArray([], ['prettyName', 'fullAddress']);
可通過(guò)覆蓋?fields()
?來(lái)增加、刪除、重命名和重定義字段,fields()
?方法返回值應(yīng)為數(shù)組, 數(shù)組的鍵為字段名,數(shù)組的值為對(duì)應(yīng)的可為屬性名或匿名函數(shù)返回的字段定義對(duì)應(yīng)的值。 特使情況下,如果字段名和屬性定義名相同,可以省略數(shù)組鍵,例如:
// 明確列出每個(gè)字段,特別用于你想確保數(shù)據(jù)表或模型屬性改變不會(huì)導(dǎo)致你的字段改變(保證后端的API兼容).
public function fields()
{
return [
// 字段名和屬性名相同
'id',
// 字段名為 "email",對(duì)應(yīng)屬性名為 "email_address"
'email' => 'email_address',
// 字段名為 "name", 值通過(guò)PHP代碼返回
'name' => function () {
return $this->first_name . ' ' . $this->last_name;
},
];
}
// 過(guò)濾掉一些字段,特別用于你想繼承父類(lèi)實(shí)現(xiàn)并不想用一些敏感字段
public function fields()
{
$fields = parent::fields();
// 去掉一些包含敏感信息的字段
unset($fields['auth_key'], $fields['password_hash'], $fields['password_reset_token']);
return $fields;
}
警告:由于模型的所有屬性會(huì)被包含在導(dǎo)出數(shù)組,最好檢查數(shù)據(jù)確保沒(méi)包含敏感數(shù)據(jù), 如果有敏感數(shù)據(jù),應(yīng)覆蓋?
fields()
?方法過(guò)濾掉,在上述列子中,我們選擇過(guò)濾掉?auth_key
,?password_hash
?and?password_reset_token
。
模型是代表業(yè)務(wù)數(shù)據(jù)、規(guī)則和邏輯的中心地方,通常在很多地方重用, 在一個(gè)設(shè)計(jì)良好的應(yīng)用中,模型通常比控制器代碼多。
歸納起來(lái),模型
在開(kāi)發(fā)大型復(fù)雜系統(tǒng)時(shí)應(yīng)經(jīng)??紤]最后一條建議, 在這些系統(tǒng)中,模型會(huì)很大并在很多地方使用,因此會(huì)包含需要規(guī)則集和業(yè)務(wù)邏輯, 最后維護(hù)這些模型代碼成為一個(gè)噩夢(mèng),因?yàn)橐粋€(gè)簡(jiǎn)單修改會(huì)影響好多地方, 為確保模型好維護(hù),最好使用以下策略:
例如,在高級(jí)應(yīng)用模板,你可以定義一個(gè)模型基類(lèi)common\models\Post
, 然后在前臺(tái)應(yīng)用中,定義并使用一個(gè)繼承common\models\Post
的具體模型類(lèi)frontend\models\Post
, 在后臺(tái)應(yīng)用中可以類(lèi)似地定義backend\models\Post
。 通過(guò)這種策略,你清楚frontend\models\Post
只對(duì)應(yīng)前臺(tái)應(yīng)用,如果你修改它,就無(wú)需擔(dān)憂(yōu)修改會(huì)影響后臺(tái)應(yīng)用。
更多建議: