# 修改器 > 本文件大量借鑑於 [LearnKu](https://learnku.com) 十分感謝 LearnKu 對 PHP 社群做出的貢獻。 當你在模型例項中獲取或設定某些屬性值的時候,訪問器和修改器允許你對模型屬性值進行格式化。 ## 訪問器 & 修改器 ### 定義一個訪問器 若要定義一個訪問器, 則需在模型上建立一個 `getFooAttribute` 方法,要訪問的 `Foo` 欄位需使用「駝峰式」命名。 在這個示例中,我們將為 `first_name` 屬性定義一個訪問器。當模型嘗試獲取 `first_name` 屬性時,將自動呼叫此訪問器: ```php first_name; ``` 當然,你也可以通過已有的屬性值,使用訪問器返回新的計算值: ```php namespace App; use Hyperf\DbConnection\Model\Model; class User extends Model { /** * 獲取使用者的姓名. * * @return string */ public function getFullNameAttribute() { return "{$this->first_name} {$this->last_name}"; } } ``` ### 定義一個修改器 若要定義一個修改器,則需在模型上面定義 `setFooAttribute` 方法。要訪問的 `Foo` 欄位使用「駝峰式」命名。讓我們再來定義一個 `first_name` 屬性的修改器。當我們嘗試在模式上在設定 `first_name` 屬性值時,該修改器將被自動呼叫: ```php attributes['first_name'] = strtolower($value); } } ``` 修改器會獲取屬性已經被設定的值,允許你修改並且將其值設定到模型內部的 `$attributes` 屬性上。舉個例子,如果我們嘗試將 `first_name` 屬性的值設定為 `Sally`: ```php $user = App\User::find(1); $user->first_name = 'Sally'; ``` 在這個例子中,`setFirstNameAttribute` 方法在呼叫的時候接受 `Sally` 這個值作為引數。接著修改器會應用 `strtolower` 函式並將處理的結果設定到內部的 `$attributes` 陣列。 ## 日期轉化器 預設情況下,模型會將 `created_at` 和 `updated_at` 欄位轉換為 `Carbon` 例項,它繼承了 `PHP` 原生的 `DateTime` 類並提供了各種有用的方法。你可以通過設定模型的 `$dates` 屬性來新增其他日期屬性: ```php Tip: 你可以通過將模型的公有屬性 $timestamps 值設定為 false 來禁用預設的 created_at 和 updated_at 時間戳。 當某個欄位是日期格式時,你可以將值設定為一個 `UNIX` 時間戳,日期時間 `(Y-m-d)` 字串,或者 `DateTime` / `Carbon` 例項。日期值會被正確格式化並儲存到你的資料庫中: 就如上面所說,當獲取到的屬性包含在 `$dates` 屬性中時,都會自動轉換為 `Carbon` 例項,允許你在屬性上使用任意的 `Carbon` 方法: ```php $user = App\User::find(1); return $user->deleted_at->getTimestamp(); ``` ### 時間格式 時間戳都將以 `Y-m-d H:i:s` 形式格式化。如果你需要自定義時間戳格式,可在模型中設定 `$dateFormat` 屬性。這個屬性決定了日期屬性將以何種形式儲存在資料庫中,以及當模型序列化成陣列或 `JSON` 時的格式: ```php `, `string`, `boolean`, `object`, `array`, `collection`, `date`, `datetime` 和 `timestamp`。 當需要轉換為 `decimal` 型別時,你需要定義小數位的個數,如: `decimal:2`。 示例, 讓我們把以整數( `0` 或 `1` )形式儲存在資料庫中的 `is_admin` 屬性轉成布林值: ```php 'boolean', ]; } ``` 現在當你訪問 `is_admin` 屬性時,雖然儲存在資料庫裡的值是一個整數型別,但是返回值總是會被轉換成布林值型別: ```php $user = App\User::find(1); if ($user->is_admin) { // } ``` ### 自定義型別轉換 模型內建了多種常用的型別轉換。但是,使用者偶爾會需要將資料轉換成自定義型別。現在,該需求可以通過定義一個實現 `CastsAttributes` 介面的類來完成 實現了該介面的類必須事先定義一個 `get` 和 `set` 方法。 `get` 方法負責將從資料庫中獲取的原始資料轉換成對應的型別,而 `set` 方法則是將資料轉換成對應的資料庫型別以便存入資料庫中。舉個例子,下面我們將內建的 `json` 型別轉換以自定義型別轉換的形式重新實現一遍: ```php Json::class, ]; } ``` #### 值物件型別轉換 你不僅可以將資料轉換成原生的資料型別,還可以將資料轉換成物件。兩種自定義型別轉換的定義方式非常類似。但是將資料轉換成物件的自定義轉換類中的 `set` 方法需要返回鍵值對陣列,用於設定原始、可儲存的值到對應的模型中。 舉個例子,定義一個自定義型別轉換類用於將多個模型屬性值轉換成單個 `Address` 值物件,假設 `Address` 物件有兩個公有屬性 `lineOne` 和 `lineTwo`: ```php $value->lineOne, 'address_line_two' => $value->lineTwo, ]; } } ``` 進行值物件型別轉換後,任何對值物件的資料變更將會自動在模型儲存前同步回模型當中: ```php address->lineOne = 'Updated Address Value'; $user->address->lineTwo = '#10000'; $user->save(); var_dump($user->getAttributes()); //[ // 'address_line_one' => 'Updated Address Value', // 'address_line_two' => '#10000' //]; ``` **這裡的實現與 Laravel 不同,如果出現以下用法,請需要格外注意** ```php $user = App\User::find(1); var_dump($user->getAttributes()); //[ // 'address_line_one' => 'Address Value', // 'address_line_two' => '#10000' //]; $user->address->lineOne = 'Updated Address Value'; $user->address->lineTwo = '#20000'; // 直接修改 address 的欄位後,是無法立馬再 attributes 中生效的,但可以直接通過 $user->address 拿到修改後的資料。 var_dump($user->getAttributes()); //[ // 'address_line_one' => 'Address Value', // 'address_line_two' => '#10000' //]; // 當我們儲存資料或者刪除資料後,attributes 便會改成修改後的資料。 $user->save(); var_dump($user->getAttributes()); //[ // 'address_line_one' => 'Updated Address Value', // 'address_line_two' => '#20000' //]; ``` 如果修改 `address` 後,不想要儲存,也不想通過 `address->lineOne` 獲取 `address_line_one` 的資料,還可以使用以下 方法 ```php $user = App\User::find(1); $user->address->lineOne = 'Updated Address Value'; $user->syncAttributes(); var_dump($user->getAttributes()); ``` 當然,如果您仍然需要修改對應的 `value` 後,同步修改 `attributes` 的功能,可以嘗試使用以下方式。首先,我們實現一個 `UserInfo` 並繼承 `CastsValue`。 ```php namespace App\Caster; use Hyperf\Database\Model\CastsValue; /** * @property string $name * @property int $gender */ class UserInfo extends CastsValue { } ``` 然後實現對應的 `UserInfoCaster` ```php $value->name, 'gender' => $value->gender, ]; } } ``` 當我們再使用以下方式修改 UserInfo 時,便可以同步修改到 attributes 的資料。 ```php /** @var User $user */ $user = User::query()->find(100); $user->userInfo->name = 'John1'; var_dump($user->getAttributes()); // ['name' => 'John1'] ``` #### 入站型別轉換 有時候,你可能只需要對寫入模型的屬性值進行型別轉換而不需要對從模型中獲取的屬性值進行任何處理。一個典型入站型別轉換的例子就是「hashing」。入站型別轉換類需要實現 `CastsInboundAttributes` 介面,只需要實現 `set` 方法。 ```php algorithm = $algorithm; } /** * 轉換成將要進行儲存的值 */ public function set($model, $key, $value, $attributes) { return hash($this->algorithm, $value); } } ``` #### 型別轉換引數 當將自定義型別轉換附加到模型時,可以指定傳入的型別轉換引數。傳入型別轉換引數需使用 `:` 將引數與類名分隔,多個引數之間使用逗號分隔。這些引數將會傳遞到型別轉換類的建構函式中: ```php Hash::class.':sha256', ]; } ``` ### 陣列 & `JSON` 轉換 當你在資料庫儲存序列化的 `JSON` 的資料時,`array` 型別的轉換非常有用。比如:如果你的資料庫具有被序列化為 `JSON` 的 `JSON` 或 `TEXT` 欄位型別,並且在模型中加入了 `array` 型別轉換,那麼當你訪問的時候就會自動被轉換為 `PHP` 陣列: ```php 'array', ]; } ``` 一旦定義了轉換,你訪問 `options` 屬性時他會自動從 `JSON` 型別反序列化為 `PHP` 陣列。當你設定了 `options` 屬性的值時,給定的陣列也會自動序列化為 `JSON` 型別儲存: ```php $user = App\User::find(1); $options = $user->options; $options['key'] = 'value'; $user->options = $options; $user->save(); ``` ### Date 型別轉換 當使用 `date` 或 `datetime` 屬性時,可以指定日期的格式。 這種格式會被用在模型序列化為陣列或者 `JSON`: ```php 'datetime:Y-m-d', ]; } ``` ### 查詢時型別轉換 有時候需要在查詢執行過程中對特定屬性進行型別轉換,例如需要從資料庫表中獲取資料的時候。舉個例子,請參考以下查詢: ```php use App\Post; use App\User; $users = User::select([ 'users.*', 'last_posted_at' => Post::selectRaw('MAX(created_at)') ->whereColumn('user_id', 'users.id') ])->get(); ``` 在該查詢獲取到的結果集中,`last_posted_at` 屬性將會是一個字串。假如我們在執行查詢時進行 `date` 型別轉換將更方便。你可以通過使用 `withCasts` 方法來完成上述操作: ```php $users = User::select([ 'users.*', 'last_posted_at' => Post::selectRaw('MAX(created_at)') ->whereColumn('user_id', 'users.id') ])->withCasts([ 'last_posted_at' => 'date' ])->get(); ```