Laravel - Eloquent ORM - Mutators

# 前言

學習一個框架, Ray 的想法是, 在深入理解底層實作的原理之前, 應該先知道這個框架的 使用方法; 先學習怎麼使用這個前人造的輪子, 再學習怎麼樣一個輪子。
所以本篇文章重點在於細讀官方文件, 並將內容理解後以 Q&A 的方式記錄下來, 加速學習以及查詢。



# Accessors & Mutators

# Defining An Accessor

以下的 Laravel example code 的意思是?
  • Example:
    <?php

    namespace App\Models;

    use Illuminate\Database\Eloquent\Model;

    class User extends Model
    {
    public function getFirstNameAttribute($value)
    {
    return ucfirst($value);
    }
    }
  • Answer:
    定義一個 accessor
    當我使用 Eloquent Model 取得 first_name attribute 時,將 string 的第一個字符轉為大寫, 所以完成定義後, 當我使用 $user->first_name;, 會自動將原本的 value 第一個字符轉為大寫
以下的 Laravel example code 的意思是?
  • Example:
    <?php
    public function getFullNameAttribute()
    {
    return "{$this->first_name} {$this->last_name}";
    }
  • Answer:
    定義一個 accessor
    full_name 是一個 computed value, 來源來自 first_name 以及 last_name column, 所以當我使用 $user->full_name 時, 會自動取得 accessor 中定義的值
    由於 full_name 並不存在於 table 中, 因此當我回傳整個 model 時並不會出現 full_name 這個 attribute, 可透過 append() 達成

# Defining A Mutator

以下的 Laravel example code 的意思是?
  • Example:
    <?php

    namespace App\Models;

    use Illuminate\Database\Eloquent\Model;

    class User extends Model
    {
    public function setFirstNameAttribute($value)
    {
    $this->attributes['first_name'] = strtolower($value);
    }
    }
  • Answer:
    定義一個 mutator
    當我們定義一個 model 的 attribute 時, mutator 會自動地呼叫 mutator, 對 attribute 進行操控
    以此 example 來說, 如果我使用 $user->first_name = 'Sally';, 那就會自動將 Sally 轉成小寫, 這實在 Eloquent Model 中 first_name 會是 sally


# Date Mutators

Laravel 中, 預設 Eloquent 會將 created_at 以及 updated_at 轉成哪一個 class 的 instance?

Carbon

以下的 Laravel example code 的意思是?
  • Example:
    <?php

    namespace App\Models;

    use Illuminate\Database\Eloquent\Model;

    class User extends Model
    {
    protected $dates = [
    'seen_at',
    ];
    }
  • Answer:
    定義一個 date mutator, 會將 seen_at column 預設轉換為 Carbon instance
以下的 Laravel example code 的意思是?
  • Example:
    <?php
    $user = App\Models\User::find(1);

    $user->deleted_at = now();

    $user->save();
  • Answer:
    將 deleted_at 的時間變為現在時間, 預設時區使用 Laravel 時區
以下的 Laravel example code 的意思是?
  • Example:
    <?php
    $user = App\Models\User::find(1);

    return $user->deleted_at->getTimestamp();
  • Answer:
    將 deleted_at 使用 Carbon class 的 getTimestamp() 轉成 timestamp 格式

# Data Formats

以下的 Laravel example code 的意思是?
  • Example:
    <?php

    namespace App\Models;

    use Illuminate\Database\Eloquent\Model;

    class Flight extends Model
    {
    protected $dateFormat = 'U';
    }
  • Answer:
    Laravel 預設的 timestamps 格式為 'Y-m-d H:i:s, 這也是會存在資料庫中的格式, 如果要自定義的話, 可以再 model 中的 $dateFormat property 自定義


# Attribute Casting

Laravel Model Attribute Casting 支援哪些 type?
  • integer
  • real
  • float
  • double
  • decimal:
  • string
  • boolean
  • object
  • array
  • collection
  • date
  • datetime
  • timestamp
  • encrypted
  • encrypted:object
  • encrypted:array
  • encrypted:collection
以下的 Laravel example code 的意思是?
  • Example:
    <?php

    namespace App\Models;

    use Illuminate\Database\Eloquent\Model;

    class User extends Model
    {
    protected $casts = [
    'is_admin' => 'boolean',
    ];
    }
  • Answer:
    假如資料庫存的是 0,1, $cast method 會在取得資料後自動將其轉成 boolean
Laravel Model Attribute Casting 可以 cast null 嗎?

不可


# Custom Casts

以下的 Laravel example code 的意思是?
  • Example:
    <?php

    namespace App\Casts;

    use Illuminate\Contracts\Database\Eloquent\CastsAttributes;

    class Json implements CastsAttributes
    {
    public function get($model, $key, $value, $attributes)
    {
    return json_decode($value, true);
    }

    public function set($model, $key, $value, $attributes)
    {
    return json_encode($value);
    }
    }
  • Answer:
    <?php

    namespace App\Casts;

    use Illuminate\Contracts\Database\Eloquent\CastsAttributes;

    // 定義一個 custom cast
    class Json implements CastsAttributes
    {
    // get 代表如何將資料庫中取得的資料進行轉換
    public function get($model, $key, $value, $attributes)
    {
    // 將資料庫取得的資料從 json 轉成 associatibe array
    return json_decode($value, true);
    }

    // set 代表如何將資料轉換成適合存進資料庫的形式
    public function set($model, $key, $value, $attributes)
    {
    // 將要存進資料庫的資料轉成 json 格式
    return json_encode($value);
    }
    }
以下的 Laravel example code 的意思是?
  • Example:
    <?php
    namespace App\Models;

    use App\Casts\Json;
    use Illuminate\Database\Eloquent\Model;

    class User extends Model
    {
    protected $casts = [
    'options' => Json::class,
    ];
    }

  • Answer:
    在定義了 custom cast class 之後, 使用 User model 的 $cast property 來使用這個 custom cast class

# Value Object Casting

以下的 Laravel example code 的意思是?
  • Example:
    <?php
    namespace App\Casts;

    use App\Models\Address as AddressModel;
    use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
    use InvalidArgumentException;

    class Address implements CastsAttributes
    {
    public function get($model, $key, $value, $attributes)
    {
    return new AddressModel(
    $attributes['address_line_one'],
    $attributes['address_line_two']
    );
    }

    public function set($model, $key, $value, $attributes)
    {
    if (! $value instanceof AddressModel) {
    throw new InvalidArgumentException('The given value is not an Address instance.');
    }

    return [
    'address_line_one' => $value->lineOne,
    'address_line_two' => $value->lineTwo,
    ];
    }
    }
  • Answer:
    <?php
    namespace App\Casts;

    use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
    use InvalidArgumentException;

    // 此為 value object casting
    // 比方說, 我們在 User model 的 $cast property 定義 ['address' => App\Casts\Address], 再到 App\Models 下建立一個 Address model
    // App\Models\Address class 為我們實際上要操作的 value object, 而當前這個 class 是這個 value object 與 User model 之間的 caster
    // 當我使用 $user->address 時, 實際上便會呼叫當前 class 的 get(), 從 DB 中將資料帶入 App\Models\Address 這個 class 中
    // 而當使用 $user->address->lineOne = '123', $user->save() 時, 則會呼叫 set(), 將 object 中的 property insert 到資料庫中的欄位
    class Address implements CastsAttributes
    {
    // $model 代表呼叫 Address 的 model, $user->address, model 就是 $user
    // $key 代表 address
    // $value 代表 address 的 value
    // $attribute 代表 raw data, 即 DB 中的 column
    public function get($model, $key, $value, $attributes)
    {
    // 從 DB 中取出 'address_line_one', 'address_line_two', 並 assign 到 Address object 中的 lineOne, 及 lineTwo property
    // 若 property 數量大於 attribute, 則會噴錯
    return new Address(
    $attributes['address_line_one'],
    $attributes['address_line_two']
    );
    }

    public function set($model, $key, $value, $attributes)
    {
    if (! $value instanceof Address) {
    throw new InvalidArgumentException('The given value is not an Address instance.');
    }

    // 將 $value->lineOne 以及 lineTwo 儲存到資料庫的 address_line_one, address_line_two column
    return [
    'address_line_one' => $value->lineOne,
    'address_line_two' => $value->lineTwo,
    ];
    }
    }
以下的 Laravel example code 的意思是?
  • Example:
    <?php
    namespace App\Casts;

    use App\Models\Address as AddressModel;
    use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
    use InvalidArgumentException;

    class Address implements CastsAttributes, SerializesCastableAttributes;
    {
    public function serialize($model, string $key, $value, array $attributes)
    {
    return (string) $value;
    }
    }
  • Answer:

short: 讓 custom cast class 可以 serialize object 或 JSON
long: 當使用 toArray method 來將 Eloquent model 轉成 array 或 JSON 時, custom cast value objects 將會被 serialize 如果有 implement Arrayable 或 JsonSerializable interface 的話。 然而, 當使用第三方 library 提供的 value objects 時, 可能無法在這些 object 上 implement 這些 interface, 因此可在 custom cast class implement SerializesCastableAttributes interface, 並新增 serialize method


# Inbound Casting

以下的 Laravel example code 的意思是?
  • Example:
    <?php

    namespace App\Casts;

    use Illuminate\Contracts\Database\Eloquent\CastsInboundAttributes;

    class Hash implements CastsInboundAttributes
    {
    protected $algorithm;

    public function __construct($algorithm = null)
    {
    $this->algorithm = $algorithm;
    }

    public function set($model, $key, $value, $attributes)
    {
    return is_null($this->algorithm)
    ? bcrypt($value)
    : hash($this->algorithm, $value);
    }
    }
  • Answer:
    如果 custom cast 的作用範圍只限定於將要存進資料庫的資料作轉換, 但從資料庫取出資料時並不另外轉換, 在這樣的情況話, 就可以使用 inbound custom cast
    需 implement CastsInboundAttributes interface, 以及只需 set method 即可

# Cast Parameters

以下的 Laravel example code 的意思是?
  • Example:
    <?php
    class User extends Authenticatable;
    {
    protected $casts = [
    'secret' => Hash::class.':sha256',
    ];
    }
  • Answer:
    將 parameter “sha256” 帶入 custom cast class “Hash”

# Castables

以下的 Laravel example code 的意思是?
  • Example:
    <?php
    class User extends Authenticatable;
    {
    protected $casts = [
    'address' => \App\Models\Address::class.':argument',
    ];
    }

    class Address implements Castable
    {
    public static function castUsing(array $arguments)
    {
    return AddressCast::class;
    }
    }
  • Answer:
    <?php
    // 這是實作 cast value object 的另外一種方式, 個人覺得這種方式比較直觀
    class User extends Authenticatable;
    {
    // 除了指定為 custom cast class 之外, 也可以直接指定要操作的 cast value object class
    // 需注意的是, 後面的 argument 會被 pass 到 custom cast class, 而不是 cast value object class
    protected $casts = [
    'address' => \App\Models\Address::class.':argument',
    ];
    }

    // 而在 cast value object class 中, 要 implement Castable interface
    class Address implements Castable
    {
    // 並且, 要定義一個 castUsing method, 指定使用哪一個 custom cast class
    public static function castUsing(array $arguments)
    {
    return AddressCast::class;
    }
    }

# Array & JSON Casting

以下的 Laravel example code 的意思是?
  • Example:
    <?php

    namespace App\Models;

    use Illuminate\Database\Eloquent\Model;

    class User extends Model
    {
    protected $casts = [
    'options' => 'array',
    ];
    }

    $user = App\Models\User::find(1);

    $options = $user->options;

    $options['key'] = 'value';

    $user->options = $options;

    $user->save();
  • Answer:
    如果 DB 內的內容是 JSON 或 TEXT 的話, 可以使用 array type, 當從資料庫取出資料時, 會自動轉成 PHP array, 而將 array 存進資料庫時, 則會自動轉成 JSON
以下的 Laravel example code 的意思是?
  • Example:
    <?php
    $user = App\Models\User::find(1);

    $user->update(['options->testKey' => 'testValue']);
  • Answer:
    更新 JSON column 中的 key 為 ‘testKey’ 的 value 更新為 ‘testValue’

# Date Casting

以下的 Laravel example code 的意思是?
  • Example:
    <?php
    protected $casts = [
    'created_at' => 'datetime:Y-m-d',
    ];
  • Answer:
    雖說 created_at 預設就會被 cast 成 datetime type, 但還可以特別指定格式
    當 model 被 serialized 成 array 或 JSON 時, 會使用這個格式

# Query Time Casting

以下的 Laravel example code 的意思是?
  • Example:
    <?php
    $users = User::select([
    'users.*',
    'last_posted_at' => Post::selectRaw('MAX(created_at)')
    ->whereColumn('user_id', 'users.id')
    ])->withCasts([
    'last_posted_at' => 'datetime'
    ])->get();
  • Answer:
    在 query 的過程中, 將 ‘last_posted_at’ 轉成 datetime type, 即 carbon instance, 若不使用 withCasts, 則 last_posted_at 會是一個 raw string, 如下 example
    <?php
    $users = User::select([
    'users.*',
    'last_posted_at' => Post::selectRaw('MAX(created_at)')
    ->whereColumn('user_id', 'users.id')
    ])->withCasts([
    'last_posted_at' => 'datetime'
    ])->get();

    $last_posted_at = $users->first()->last_posted_at;
    // 若不使用 cast, 是無法使用 Carbon class year property 的
    dd($last_posted_at->year);
Laravel - 完全杜絕 N+1 issue 的可能 Insertion Sort (插入排序法) In PHP

留言

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×