新版本的 Laravel 提供了一種更便捷的作法來定義 accessor 和 mutator,下面將會比較新舊版本之間的差異,原有方式在新版本當中還是有作兼容,但新的寫法是相對來說更加清晰好懂。
原做法
// 假定 users 有 name:
// accessor
protected function getNameAttribute($value)
{
return Str::mask($value, '*', 2);
}
// mutator
protected function setNameAttribute($value)
{
$this->attributes['name'] = 'Mr.'.$value;
}
// 要偽裝一個不存在的欄位 accessor
protected function getFirstNameAttribute($value)
{
return ucfirst($this->name);
}
新做法
doc,將原有 accessor 和 mutator 綜合為同一個方法進行操作。
// 假定 users 有 name:
protected function name(): Attribute
{
return Attribute::make(
get: fn (string $value) => Str::mask($value, '*', 2),
set: fn(string $value) => 'Mr.'.$value
);
}
// 要偽裝一個不存在的欄位 accessor
protected function firstName(): Attribute
{
return Attribute::make(
get: fn () => ucfirst($this->name),
);
}
// 也可以透過第二參數進行組合屬性
protected function address(): Attribute
{
return Attribute::make(
get: fn (mixed $value, array $attributes) => new Address(
$attributes['address_line_one'],
$attributes['address_line_two'],
),
);
}
底層探勘
在文件後面我看到有一個方法叫做 shouldCache()
,這邊的 cache 指的應該是類似 in-memory 的那種 cache,而不是持久型(如 redis / database)這樣子,具體討論這邊有描述。
我模擬了一下範例中的 get: fn (string $value) => bcrypt(gzuncompress($value)),
,步驟如下:
- 確保自己配置 gzip 壓縮
- 創建添加欄位
hash
(blob type):
$table->binary('hash')->nullable();
- User model 內添加相關 fillable
- 寫 accessor / mutator
protected function hash(): Attribute
{
return Attribute::make(
get: fn (string $value) => bcrypt(gzuncompress($value)),
set: fn(string $value) => gzcompress($value),
)->shouldCache();
}
- 配置相關 factory 產生,或是自己 create:
'hash' => gzcompress(Str::random()),
- 取得演算數據
$users->hash
看了一下底層的配置跟設計似乎有點像是 singleton 的設計方式,具體來說就是在進程下共用同樣的屬性空間:
// Illuminate\Database\Eloquent\Casts\Attribute.php
public $withCaching = false;
public function shouldCache()
{
$this->withCaching = true;
return $this;
}
// Illuminate/Database/Eloquent/Concerns/HasAttributes.php
protected function mutateAttributeMarkedAttribute($key, $value)
{
if (array_key_exists($key, $this->attributeCastCache)) {
return $this->attributeCastCache[$key];
}
$attribute = $this->{Str::camel($key)}();
$value = call_user_func($attribute->get ?: function ($value) {
return $value;
}, $value, $this->attributes);
// 在單例中發現 $attribute->withCaching == true -> 配置 attributeCastCache
if ($attribute->withCaching || (is_object($value) && $attribute->withObjectCaching)) {
$this->attributeCastCache[$key] = $value;
} else {
unset($this->attributeCastCache[$key]);
}
return $value;
}
在沒有這個方法之前,我在網路上有看過有類似這樣子的作法: 參考,但其實邏輯是一樣的:
return Attribute::make(
get: function($value) {
if (! $this->cacheHash) {
$this->cacheHash = bcrypt(gzuncompress($value));
}
return $this->cacheHash;
},
);