### 2018 年 10 月 27 日 發布
修改器是模型的三大利「器」之一,本篇我們來總結下修改器的用法,以及一些注意事項。
## 定義修改器
修改器的作用是在模型對象數據寫入數據庫之前進行一些必要的數據處理,修改器的標準定義如下:
```
public function setFieldNameAttr($value, $data)
{
// 對value值進行處理 data參數是當前全部數據
// 返回值就是實際要寫入數據庫的值
return $value;
}
```
其中`FieldName`對應數據表的`field_name`字段(注意數據表字段的規范和修改器方法定義規范,否則會導致錯誤)。
原則上,每個修改器應當僅處理對應字段的數據,但在必要的情況下允許同時處理多個字段。
下面是一個例子
```
public function setBirthdayAttr($value, $data)
{
// 格式化生日數據
$birthday = strtotime($value);
// 根據生日判斷年齡
$age = getAgeByBirthday($birthday);
// 賦值年齡數據
$this->setAttr('age', $age);
return $birthday;
}
public function setAgeAttr($value,$data)
{
return floor($value);
}
```
之所以使用`setAttr`方法是確保年齡賦值操作仍然可以走單獨的修改器。如果你沒有額外的修改器,那么也可以寫成
```
public function setBirthdayAttr($value, $data)
{
// 格式化生日數據
$birthday = strtotime($value);
// 根據生日判斷年齡
$age = getAgeByBirthday($birthday);
// 賦值年齡數據
$this->data['age'] = $age;
return $birthday;
}
```
注意一定不能寫成
```
$this->age = $age;
```
因為在模型內部進行數據對象的賦值,會因為和模型內部屬性混淆而導致不可預知的后果。
>[danger] 如果你在某個修改器中可能會對其它字段進行修改,務必記得你需要額外修改的字段修改器必須已經經過賦值操作(或者已經觸發過修改器)。
## 如何調用
修改器方法不需要手動調用,按照定義規范定義好后,系統會在下面的情況下自動調用:
* 模型對象賦值;
* 調用模型的`data`方法,并且第二個參數傳入true;
* 調用模型的`save`方法,并且傳入數組數據;
* 顯式調用模型的`setAttr`方法;
* 定義了該字段的自動完成;
例如`User`模型定義了`setPasswordAttr`修改器方法。
```
public function setPasswordAttr($value, $data)
{
return md5($value);
}
```
當下面這樣使用的時候,保存到數據庫的`password`字段的值就會變成`md5('think')`后的值。
```
$user = User::get(1);
$user->password = 'think';
$user->save();
```
如果你在一些情況下,不希望使用修改器而是想要手動控制數據,可以嘗試使用下面的方法。
```
$user = User::get(1);
$user->data('password', md5('think'));
$user->save();
```
這個時候就不會經過修改器處理。
## 避免沖突
很多開發者喜歡給修改器定義自動完成`auto`(包括`insert`和`update`)。
```
protected $auto = ['password'];
```
這在`V5.1.27`版本之前是一個看似聰明卻非常致命的錯誤,要盡量避免,因為根據我們之前給出的修改器觸發條件,會導致該修改器被執行兩次。這會是一個災難性的錯誤,將導致所有的用戶注冊后都無法正常登錄。
解決辦法取消`password`字段的自動完成設置,因為修改器會在每次賦值的時候自動觸發,如果沒有賦值說明密碼沒有被修改,也談不上自動完成。
>[danger] 自動完成的字段通常是不在表單里面的字段,一般是由系統自動處理的字段。
`V5.1.27`版本改進了這個問題,所有的修改器只允許執行一次,上面的問題就不復存在了。但好像又帶來了一個新的問題,很多時候,你也許想在模型的事件中對數據進行修改。
```
User::beforeUpdate(function($user) {
$user->password = md5('think');
});
```
會發現,在模型`beforeUpdate`事件中,數據的值怎么都修改不了,原因是模型的修改器之前在第一次賦值的時候已經執行了,第二次再賦值的時候已經無效了(不會再執行)。
解決辦法就是我前面提過的使用`data`方法不調用修改器進行數據賦值操作。
```
User::beforeUpdate(function($user) {
$user->data('password', md5('think'));
});
```
當然,更好的建議是規劃好修改器、自動完成和模型事件的數據處理機制,不要對一個字段同時使用多重機制修改數據,并且寫入數據庫的數據**應該并且只有**修改器這一個途徑進行數據**修改**操作。
## 類型自動轉化
如果你的修改器僅僅是對數據做類型轉換處理的話,可以無需定義修改器,而是直接定義字段類型就可以了。
```
public function setScoreAttr($value, $data)
{
return (float) $score;
}
```
上面的修改器方法可以直接改成
```
protected $type = [
'score' => 'float',
];
```
如果你同時對一個字段定義了修改器和類型的話,修改器是優先的。
>[info] 類型定義不僅能定義簡單的數據類型,還有一些額外的用途,例如:`json` 類型、`array`類型和`object` 類型會進行`JSON`序列化,`serialize`類型則會把數據進行`serialize`序列化。
- 值得升級到5.1的18個理由
- 5.1.7版本新特性
- JSON字段類型在ORM中的使用
- 文件下載響應對象
- 教你使用5.1的數組對象查詢
- 模型三大利器之一:搜索器
- 在ThinkPHP中使用Yaconf
- 掌握命令行的表格輸出
- 5.1.25查詢參數綁定的改進
- ThinkPHP安全規范指引
- 巧用數據集的排序功能實現統計排序
- think-orm ——基于5.1的獨立ORM庫
- think-template——基于ThinkPHP的獨立模板引擎
- ThinkPHP5.1.26版本發布——修正版本,包含安全更新
- ThinkPHP5.0和3.2再發安全更新
- 官宣:ThinkPHP發布首個LTS版本
- 你真的了解Db類和模型的正確使用姿勢么?
- 如何更有效的記錄和管理日志
- 模型三大利器之二:修改器
- ThinkPHP5.1.28版本發布——修正上一版本問題,改進關聯查詢
- 模型三大利器之三:獲取器
- API版本控制的幾種思路
- ThinkPHP5.2第一個Beta版本發布測試
- 讓你少犯錯的數據查詢基本原則
- ThinkPHP發布5.1.29版本——常規更新
- 這15個好習慣讓你更容易升級到5.2
- 如何有效提高ThinkPHP的應用性能
- 讓你提高開發效率的查詢技巧
- 模型關聯查詢不完全指南
- 5.2發布Beta2版本——統一和精簡大量用法
- ThinkPHP發布5.1.30版本——支持微秒時間字段寫入
- ThinkPHP的數據緩存使用
- ThinkPHP5.2安裝及入口文件
- ThinkPHP榮獲2018 年度最受歡迎中國開源開發框架第1名
- 5.1路由使用心得技巧
- ThinkPHP5.*版本發布安全更新
- ThinkPHP項目及代碼規范指北
- 5.2版本的設計規范指導
- ThinkPHP5.1.32版本發布——圣誕快樂
- 利用Trait特性給模型增加樂觀鎖功能
- 5.2數據庫和模型的變化(摘要)
- ThinkPHP模板引擎實現和常見問題
- ThinkPHP5.0.24版本發布——安全更新
- 不忘初心,方得始終——ThinkPHP十三周年報告
- ThinkPHP5+相關資源匯總
- 異步社區ThinkPHP周年慶專享優惠活動
- 5.2路由的調整和改進
- ThinkPHP發布5.1.33版本——包含安全更新
- ThinkPHP擴展開發指南
- ThinkPHP發布5.2Beta3版本
- ThinkPHP發布5.1.34版本——喜迎新年
- ThinkPHP發布5.2RC1版本
- ThinkPHP發布5.1.35版本——常規更新
- 5.2配置類的調整
- 5.2時間查詢的改進和優化
- 5.2RC版本升級不完全指導(僅供學習參考)
- ThinkPHP5.2版本正式變更為6.0版本
- ThinkPHP百度云云虛擬主機專享免費活動
- 事件系統以及查詢事件、模型事件的使用
- ThinkPHP6.0RC2版本發布——架構升級、精簡核心
- ThinkPHP5.1.36LTS版本發布——常規更新
- 新版Session和Cookie設計變化
- ThinkPHP5.1.37版本發布——常規更新
- ThinkPHP6.0RC3版本發布——細節完善,體驗優化
- 6.0中間件使用詳解
- Composer各大廠商鏡像地址
- ThinkPHP6.0發布計劃公告
- 「ThinkPHP開發者周刊」招募志愿者
- ThinkPHP6.0日志變化
- ThinkPHP5.1.38版本發布——常規更新
- ThinkPHP6.0RC4版本發布——ORM獨立,日志多通道支持
- ThinkORM2.0開發指南上線
- ThinkPHP6.0RC5版本發布——多應用模式獨立,中間件機制調整
- ThinkPHP6.0版本發布——程序員節福利
- ThinkPHP5.1.39LTS版本發布——常規更新
- ThinkPHP6.0.1版本發布——圣誕快樂!
- 回顧2019,展望2020!
- ThinkPHPV6.0.2版本發布——2020新春快樂!
- 周年福利系列:Swoole合作優惠
- 億速云成為ThinkPHPV6.0獨家贊助發布商??
- 新冠疫情工具和限免資源專題(保持更新中)
- 周年福利系列:創宇信用認證合作優惠
- 周年福利系列:碼云企業版限時10%優惠
- 周年福利系列:想天短說抵現優惠
- think-swoole直播:從零開始掌握swoole開發
- 周年福利系列:B2C開源電商ShopXO授權8折優惠
- 周年福利系列:LayuiAdmin 永久授權限時優惠
- ThinkPHP資源導航站上線——構建生態 服務未來
- ThinkPHP官方技術支持服務和應用服務市場上線公測
- ThinkPHP市場精選——推廣基本要素
- ThinkPHP市場精選——客服聊天專題
- ThinkPHPV6.0.3版本發布——端午安康
- ThinkPHP開發者扶持計劃
- 6.0.3版本關鍵更新及升級事項
- 「ThinkPHP開發者周刊」改版重啟
- ThinkPHP市場精選——企業建站專題
- ThinkPHP 提供統一API接口服務
- ThinkPHP市場精選——直播電商專題
- ThinkAPI服務SDK發布
- 官方服務市場啟用獨立子域名
- ThinkPHP市場精選——刷臉支付專題
- ThinkAPI推出會員服務計劃
- ThinkPHPV6.0.4版本發布——中秋國慶雙節快樂
- ThinkPHPV5.1.40版本發布——常規更新
- 1024程序員節福利走一波
- ThinkPHP V6.0.5版本發布——兼容Composer2.0
- 知識圖譜應用場景——源論技術沙龍
- ThinkPHP5.*版本改進Composer2.0的兼容
- 官方市場雙十一精選推薦
- 技術人做產品有機會么(文末送課程)
- 本周秒殺——古德云售后獲客營銷系統
- ThinkAPI服務更新——支持接口分組和PHP版本依賴調整
- PHP8新特性盤點
- PHP8新特性系列:構造器屬性提升使用及注意事項
- ThinkPHP2021新年寄語
- ThinkPHP V6.0.6&V5.1.41版本發布——兼容PHP8.0
- PHP如何更優雅地調用API接口
- ThinkPHP V6.0.7發布——修正版本
- ThinkAPI服務更新——IP白名單
- 最新版ThinkORM對于時間字段的調整
- ThinkAPI短信接口正式上線
- ThinkPHP V6.0.8版本發布——多環境變量配置支持
- 頂想云寫作服務開啟第一次公測
- ThinkSSL上線——官方SSL/TLS證書服務
- MDBootstrap國內用戶福利——ThinkPHP官方市場首發
- ThinkPHP V6.0.9版本發布——常規更新
- ThinkORM功能盤點——虛擬模型
- 全面支持主流GIT版本庫——云寫作服務第二次公測
- 云寫作服務私有化部署方案之:版本庫私有化
- 看云雙十一活動
- ThinkPHP V6.0.10LTS發布——兼容PHP8.1
- ThinkPHP V6.0.12發布——命令行兼容8.1
- 頂想云知識管理上線公測——構建企業文檔中心和知識庫
- 頂想云上線——助力生態數字化建設
- 618活動進行中——官方市場迎來一波更新
- 頂想云知識管理正式上線——看云文檔啟動遷移服務
- ThinkPHP V6.0.13發布——常規更新
- 頂想云網站助理服務上線——構建產品支持服務
- ThinkPHP發布6.1.0&6.0.14版本——安全更新
- ThinkPHP新版社區上線試運營
- ThinkAPI上架人臉核身接口——助力網站實名認證
- 辭舊迎新——舊版社區停止注冊及發帖
- ThinkPHP6.1.2版本發布——兼容PHP8.2