Cảm ơn bạn!
Casting, Mutators và Accessors trong Laravel là gì? Tại sao Laravel developer phải hiểu và nắm rõ
Chào các bạn, chắc hẳn khi làm việc với Laravel, chúng ta đã nghe về Casting, Mutators và Accessors, trong bài viết này chúng ta sẽ cùng khám phá cơ chế của nó nhé 😁.
Casting, Mutators và Accessors trong Laravel là gì?
Khi làm việc mới Eloquent model Mutators, Accessors và Casting giúp bạn transform - biến đổi giá trị tạo ra output mong muốn. Chẳng hạn khi tạo user, bạn có field password như bên dưới:
$user = User::create([
'name' => 'Nguyen Van A',
'email' => 'homiedev@gmail.com',
'password' => Hash::make('homiedev'),
]);
// password hashed: $2y$10$gnsZjt74dQdQGLl6I0Bq7OYx/wHPXkruo6GVx5SxswDPKdzRsYt/CNhìn vào field password, chúng ta có thể thấy đang set password bằng Facade Hash, cách làm này là thủ công, chúng ta có thể làm như sau:
$user = User::create([
'name' => 'Nguyen Van A',
'email' => 'homiedev@gmail.com',
'password' => 'homiedev',
]);
// password hashed: $2y$10$gnsZjt74dQdQGLl6I0Bq7OYx/wHPXkruo6GVx5SxswDPKdzRsYt/CCác bạn thấy mình không cần hash password thủ công nữa mà Laravel đã tự xử lý cho chúng ta 👍, bây giờ hãy cùng tìm hiểu cơ chế Casting trong Laravel nhé.
Casting
Casting trong laravel có ý tưởng giống như cách ép kiểu thông thường vậy, ví dụ
$user = User::find(1);
// Tự mình ép kiểu
$user->is_active; // 1
$isActive = (bool) $user->is_active; // trueVới cách này thì chỗ nào cần sử dụng thuộc tính is_active thì cần phải cast về bool theo yêu cầu, Laravel cung cấp một cách đơn giản và dễ dàng hơn đó là khai báo cast vào trong model.
Nó sẽ tự cast về output mong muốn khi bạn truy cập thuộc tính của Model.
Để làm được thì trong model các bạn khai báo thêm thuộc tính $casts:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'is_active' => 'boolean',
];
}
}Sau khi khai báo field cần cast trong model, thuộc tính is_active sẽ được cast về bool khi bạn truy cập nó.
$user = App\Models\User::find(1);
if ($user->is_active) {
// ...
}Một số cast types mà laravel support bạn có thể tham khảo thêm tại: https://laravel.com/docs/master/eloquent-mutators#attribute-casting
Ngoài ra, Laravel còn hỗ trợ chúng ta cast trong quá trình runtime, cast tạm thời không làm ảnh hưởng đến $casts trong model với method mergeCasts:
$user = new User([
'name' => 'Guest',
'email' => 'guest@localhost',
'password' => 'homiedev',
'created_at' => now(), // Carbon instance
]);
$user->mergeCasts([
'created_at' => 'timestamp',
]);
dd($user->created_at); // 1759677481Bên dưới laravel sẽ thực hiện merge cast tùy biến với cast hiện có trong model, code sẽ có ý tưởng tương tự bên dưới ^^:
public function mergeCasts($casts)
{
$this->casts = array_merge($this->casts, $casts);
return $this;
}Tìm hiểu thêm hàm mergeCasts các bạn có thể xem trong thư mục:
\vendor\laravel\framework\src\Illuminate\Database\Eloquent\Concerns\HasAttributes.phpCác bạn cần chú ý nếu giá trị của field là null, Laravel sẽ không ép kiểu nó. Nó vẫn giữ nguyên là null, lý do là vì các field này có thể chưa được set giá trị, nếu cast giá trị có thể bị bug logic.
Ngoài ra các bạn có thể xem thêm các custom Casts trong Laravel tại: https://laravel.com/docs/master/eloquent-mutators#custom-casts
Accessors và Mutators
Accessors trong Laravel
Tiếp theo chúng ta cùng tìm hiểu về Accessors, nó được sử dụng để transform giá trị khi nó được truy xuất. Để sử dụng thì các bạn define trong model:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* Get the user's name.
*/
protected function name(): Attribute
{
return Attribute::make(
get: function(string $value) {
return strtoupper($value);
}
);
}
}Khi bạn gọi thuộc tính name, Accessor sẽ transform giúp bạn:
use App\Models\User;
$user = User::find(1); // name = homiedev
$name = $user->name; // HOMIEDEVĐể Accessor hoạt động, chúng ta cần type-hint
Illuminate\Database\Eloquent\Casts\Attributeđể Laravel xác định đây là một Accessor, kết quả của Accessor là một Attribute instance.
Laravel cũng support chúng ta truy xuất multiple attributes từ một attribute cụ thể, các bạn sử dụng tham số thứ 2 của hàm:
protected function fullName(): Attribute
{
return Attribute::make(
get: function(string $value, array $attributes) {
return $attributes['first_name'] . ' ' . $attributes['last_name'];
}
);
}Các bạn cần sử dụng camelCase làm tên function như
fullName, lý do vì laravel sẽ dựa vào cách chúng ta truy xuất thuộc tính, thường là một field dưới database, ví dụ$user->full_namesau đó tìm method có format camelCase để xem bạn có định nghĩa Accessor cho nó không và sử dụng ^^.
Mutators trong Laravel
Với Mutators chúng ta sử dụng khi muốn format giá trị khi gán giá trị cho field.
protected function name(): Attribute
{
return Attribute::make(
get: function(string $value) {
return $value;
},
set: function(string $value) {
return ucfirst($value);
}
);
}Kết quả:
$user->name = "nguyen van a";
dd($user->name); // Nguyen van aSau khi tìm hiểu Casting, Mutators và Accessors mình cũng khá tò mò, không biết Laravel làm những tính năng trong bài viết trên như thế nào ^^, sau đây là một số tìm hiểu của mình, các bạn xem qua nhé!
Laravel framework đã tùy biến Casting, Mutators và Accessors thế nào?
Laravel framework được xây dựng từ PHP, chính vì thế những tính năng của Laravel sử dụng PHP để tạo nên, trong đó có hai magic method là __get() và __set(), đây chính là 2 method cốt lõi trong PHP được Laravel sử dụng để tạo nên Casting, Mutators và Accessors.
Xem qua model User chúng ta sẽ thấy nó được kế thừa - extends lại base class là Model:
class User extends Model
{
}Class Modelcó định nghĩa hai magic methods là __get() và __set().
Method __get() giúp Laravel xử lý giá trị trong trường hợp chúng ta retrieve attributes tạm gọi là truy xuất thuộc tính.
// vendor\laravel\framework\src\Illuminate\Database\Eloquent\Model.php
/**
* Dynamically retrieve attributes on the model.
*
* @param string $key
* @return mixed
*/
public function __get($key)
{
return $this->getAttribute($key);
}Laravel xử lý $key truyền vào bằng method getAttribute():
/**
* Get an attribute from the model.
*
* @param string $key
* @return mixed
*/
public function getAttribute($key)
{
if (! $key) {
return;
}
// If the attribute exists in the attribute array or has a "get" mutator we will
// get the attribute's value. Otherwise, we will proceed as if the developers
// are asking for a relationship's value. This covers both types of values.
if ($this->hasAttribute($key)) {
return $this->getAttributeValue($key);
}
// Here we will determine if the model base class itself contains this given key
// since we don't want to treat any of those methods as relationships because
// they are all intended as helper methods and none of these are relations.
if (method_exists(self::class, $key)) {
return $this->throwMissingAttributeExceptionIfApplicable($key);
}
return $this->isRelation($key) || $this->relationLoaded($key)
? $this->getRelationValue($key)
: $this->throwMissingAttributeExceptionIfApplicable($key);
}Method __get() được gọi khi chúng ta truy xuất một thuộc tính có access modifiers là private, protected và những thuộc tính không tồn tại.
Ví dụ bạn gọi $user->is_author_homiedev nhưng chúng ta không định nghĩa thuộc tính này, vậy nên __get() sẽ được gọi, laravel sẽ sử dụng getAttribute() để xử lý tiếp. Đây chính là lý do khi các bạn $user->name như ở ví dụ trên thì nó trả kết quả in hoa giúp mình ^^.
Tương tự như vậy khi bạn set thuộc tính là private, protected hoặc không tồn tại thì __set() được gọi và laravel sẽ sử dụng method setAttribute() để xử lý:
/**
* Dynamically set attributes on the model.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function __set($key, $value)
{
$this->setAttribute($key, $value);
}Các bạn có thể đọc thêm logic xử lý trong trait HasAttributes tại:
vendor\laravel\framework\src\Illuminate\Database\Eloquent\Concerns\HasAttributes.php
Tạm kết
Trong bài viết này, mình đã giới thiệu đến các bạn cách chúng ta sử dụng Casting, Mutators và Accessors trong laravel, hi vọng các bạn sẽ hiểu được và vận dụng nó trong thực tế. Đây là một cơ chế rất hay giúp chúng ta tái sử dụng code và giúp maintain dễ dàng hơn ^^.
Cảm ơn các bạn đã đọc bài viết này, trong các bạn viết tới mình sẽ giới thiệu một số nội dung hay ho của laravel, hi vọng các bạn ủng hộ mình nhé ^^.
