Trong các bài CRUD trước, chúng ta đã làm việc trực tiếp với dữ liệu:
$user->name
$user->email
$user->role
Nhưng trong dự án thực tế, đôi khi chúng ta muốn biến đổi dữ liệu trước khi hiển thị hoặc xử lý dữ liệu trước khi lưu vào database.
Ví dụ:
Database lưu
nguyen van aWebsite muốn hiển thị
Nguyen Van A
Hoặc:
Người dùng nhập
NGUYEN VAN ADatabase chỉ lưu
nguyen van a
Đây chính là lúc Accessor và Mutator phát huy tác dụng.
Laravel 13 sử dụng cách khai báo hiện đại thông qua Illuminate\Database\Eloquent\Casts\Attribute.
📑 MỤC LỤC
Accessor không phải là column database
Đưa Accessor vào JSON
Một ví dụ thực tế với User
Accessor cho Role
Accessor và Mutator khác
$castsnhư thế nào?Khi nào dùng Accessor?
Khi nào dùng Mutator?
Accessor & Mutator trong CRUD
Không nên nhồi mọi thứ vào Accessor
Accessor & Mutator hiện đại
Ví dụ hoàn chỉnh cho User Model
Kết quả
Tóm tắt
Bài tập thực hành
🎯 Ghi nhớ Bài 31
1. Accessor & Mutator là gì?
Có thể hiểu đơn giản:
ELOQUENT MODEL
│
┌────────────┴────────────┐
│ │
ACCESSOR MUTATOR
│ │
Database → PHP PHP → Database
│ │
Khi đọc dữ liệu Khi ghi dữ liệu
│ │
▼ ▼
Biến đổi để hiển thị Biến đổi trước khi lưu
Accessor
Accessor dùng để thay đổi giá trị khi chúng ta lấy dữ liệu ra.
Database
↓
Accessor
↓
PHP / Blade
Mutator
Mutator dùng để thay đổi giá trị khi chúng ta gán dữ liệu vào Model.
PHP / Form
↓
Mutator
↓
Database
2. Ví dụ thực tế
Giả sử bảng users có:
id | name
1 | nguyen van a
2 | tran van b
3 | le van c
Khi hiển thị trên website, chúng ta muốn:
Nguyen Van A
Tran Van B
Le Van C
Thay vì phải viết:
ucwords($user->name)
ở rất nhiều nơi:
{{ ucwords($user->name) }}
Chúng ta có thể xử lý ngay trong Model bằng Accessor.
Khi đó Blade chỉ cần:
{{ $user->name }}
Eloquent sẽ tự động xử lý giá trị khi đọc thuộc tính.
3. Attribute trong Laravel 13
Trong Laravel hiện đại, chúng ta sử dụng:
use Illuminate\Database\Eloquent\Casts\Attribute;
Sau đó định nghĩa một method trong Model:
protected function name(): Attribute
{
return Attribute::make(
get: fn (string $value) => ucwords($value),
);
}
Đây là cách khai báo Accessor hiện đại được Laravel hỗ trợ.
4. Tạo Accessor cho User
Mở:
app/Models/User.php
Ví dụ:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
protected function name(): Attribute
{
return Attribute::make(
get: fn (string $value) => ucwords($value),
);
}
}
Bây giờ database có:
nguyen van a
Nhưng:
$user->name
sẽ trả về:
Nguyen Van A
Trong Blade:
{{ $user->name }}
cũng sẽ hiển thị:
Nguyen Van A
5. Accessor hoạt động như thế nào?
Khi chúng ta viết:
$user->name
Eloquent nhìn thấy Accessor:
protected function name(): Attribute
và thực hiện phần:
get: fn (string $value) => ucwords($value)
Có thể hình dung:
Database
────────────────────
name = "nguyen van a"
│
▼
Accessor
│
▼
ucwords()
│
▼
"Nguyen Van A"
Điều quan trọng:
Accessor không thay đổi dữ liệu trong database.
Database vẫn là:
nguyen van a
Chỉ khi lấy dữ liệu ra thì nó mới được biến đổi.
6. Mutator là gì?
Mutator hoạt động theo chiều ngược lại.
Ví dụ người dùng nhập:
NGUYEN VAN A
Chúng ta muốn database lưu:
nguyen van a
Có thể sử dụng:
protected function name(): Attribute
{
return Attribute::make(
set: fn (string $value) => strtolower(trim($value)),
);
}
Khi thực hiện:
$user->name = ' NGUYEN VAN A ';
Mutator sẽ xử lý:
" NGUYEN VAN A "
│
▼
trim()
│
▼
"NGUYEN VAN A"
│
▼
strtolower()
│
▼
"nguyen van a"
│
▼
Database
Laravel gọi Mutator khi giá trị thuộc tính được gán.
7. Kết hợp Accessor và Mutator
Đây mới là cách sử dụng rất hay.
Chúng ta có thể vừa xử lý khi ghi vừa xử lý khi đọc.
protected function name(): Attribute
{
return Attribute::make(
get: fn (string $value) => ucwords($value),
set: fn (string $value) => strtolower(trim($value)),
);
}
Khi lưu:
$user->name = ' NGUYEN VAN A ';
Database:
nguyen van a
Khi đọc:
$user->name
Kết quả:
Nguyen Van A
Như vậy:
User nhập dữ liệu
│
▼
MUTATOR
│
▼
Database
"nguyen van a"
│
▼
ACCESSOR
│
▼
"Nguyen Van A"
│
▼
Website
8. Ví dụ với Email
Một trường hợp rất thực tế là email.
Người dùng có thể nhập:
ADMIN@GMAIL.COM
Chúng ta muốn database lưu:
admin@gmail.com
Có thể viết:
protected function email(): Attribute
{
return Attribute::make(
set: fn (string $value) => strtolower(trim($value)),
);
}
Khi:
$user->email = ' ADMIN@GMAIL.COM ';
Database sẽ nhận:
admin@gmail.com
Điều này giúp dữ liệu trong hệ thống thống nhất hơn.
9. Accessor cho dữ liệu tính toán
Accessor không nhất thiết phải xử lý một column có sẵn.
Chúng ta có thể tạo ra một thuộc tính ảo.
Ví dụ User có:
first_name
last_name
Database:
first_name = Nguyễn
last_name = Văn A
Chúng ta muốn:
$user->full_name
trả về:
Nguyễn Văn A
Có thể viết:
protected function fullName(): Attribute
{
return Attribute::make(
get: fn ($value, array $attributes) =>
$attributes['first_name'] . ' ' . $attributes['last_name'],
);
}
Sau đó:
{{ $user->full_name }}
Laravel sẽ trả về:
Nguyễn Văn A
full_name không cần tồn tại trong database.
10. Tại sao cần $attributes?
Trong Accessor:
get: fn ($value, array $attributes) => ...
Laravel cung cấp:
$attributes
chứa các giá trị gốc của Model.
Ví dụ:
$attributes['first_name']
$attributes['last_name']
Do đó chúng ta có thể tạo dữ liệu mới từ nhiều column.
Ví dụ:
protected function fullName(): Attribute
{
return Attribute::make(
get: fn ($value, array $attributes) =>
trim(
$attributes['first_name'] . ' ' .
$attributes['last_name']
),
);
}
11. Hiển thị Accessor trong Blade
Sau khi định nghĩa:
protected function fullName(): Attribute
{
return Attribute::make(
get: fn ($value, array $attributes) =>
$attributes['first_name'] . ' ' .
$attributes['last_name'],
);
}
Trong Blade:
<h2>{{ $user->full_name }}</h2>
Không cần:
<h2>
{{ $user->first_name }}
{{ $user->last_name }}
</h2>
Model sẽ chịu trách nhiệm xử lý dữ liệu.
12. Accessor không phải là column database
Ví dụ:
protected function fullName(): Attribute
{
return Attribute::make(
get: fn ($value, array $attributes) =>
$attributes['first_name'] . ' ' .
$attributes['last_name'],
);
}
Chúng ta có:
$user->full_name
nhưng bảng users không nhất thiết phải có:
full_name
Đây là computed attribute.
Database
──────────────────────
first_name
last_name
│
▼
Accessor
│
▼
full_name
Rất hữu ích khi xây dựng API hoặc giao diện.
13. Đưa Accessor vào JSON
Mặc định, một Accessor tính toán không nhất thiết xuất hiện trong kết quả toArray() hoặc JSON.
Ví dụ:
return $user->toArray();
Nếu muốn thêm thuộc tính:
full_name
vào dữ liệu JSON/array, Laravel 13 hỗ trợ khai báo #[Appends(...)].
Ví dụ:
use Illuminate\Database\Eloquent\Attributes\Appends;
#[Appends(['full_name'])]
class User extends Authenticatable
{
protected function fullName(): Attribute
{
return Attribute::make(
get: fn ($value, array $attributes) =>
$attributes['first_name'] . ' ' .
$attributes['last_name'],
);
}
}
Khi chuyển Model sang JSON, full_name sẽ được thêm vào.
14. Một ví dụ thực tế với User
Giả sử users có:
name
email
role
is_active
Chúng ta có thể tạo Accessor cho trạng thái:
protected function statusText(): Attribute
{
return Attribute::make(
get: fn ($value, array $attributes) =>
$attributes['is_active']
? 'Đang hoạt động'
: 'Đã khóa',
);
}
Trong Blade:
{{ $user->status_text }}
Nếu:
is_active = 1
sẽ hiển thị:
Đang hoạt động
Nếu:
is_active = 0
sẽ hiển thị:
Đã khóa
15. Accessor cho Role
Chúng ta cũng có thể biến:
admin
user
editor
thành:
Quản trị viên
Người dùng
Biên tập viên
Ví dụ:
protected function roleText(): Attribute
{
return Attribute::make(
get: fn ($value, array $attributes) => match ($attributes['role']) {
'admin' => 'Quản trị viên',
'editor' => 'Biên tập viên',
'user' => 'Người dùng',
default => 'Không xác định',
},
);
}
Blade:
{{ $user->role_text }}
Kết quả:
admin → Quản trị viên
editor → Biên tập viên
user → Người dùng
16. Accessor và Mutator khác $casts như thế nào?
Đây là điểm rất quan trọng.
Laravel có một cơ chế khác là Attribute Casting.
Ví dụ:
protected function casts(): array
{
return [
'is_active' => 'boolean',
];
}
Khi đó:
$user->is_active
sẽ được chuyển thành kiểu:
true
hoặc:
false
Laravel cung cấp rất nhiều kiểu cast như:
boolean
integer
string
array
json
date
datetime
decimal
encrypted
hashed
enum
và nhiều kiểu khác.
17. Khi nào dùng Accessor?
Dùng Accessor khi cần:
Database
↓
Biến đổi
↓
Hiển thị / sử dụng
Ví dụ:
name
full_name
role_text
status_text
price_formatted
avatar_url
Ví dụ:
$user->status_text
hoặc:
$post->formatted_price
18. Khi nào dùng Mutator?
Dùng Mutator khi muốn chuẩn hóa dữ liệu trước khi lưu.
Ví dụ:
trim()
strtolower()
strtoupper()
chuẩn hóa dữ liệu
Ví dụ:
protected function email(): Attribute
{
return Attribute::make(
set: fn (string $value) => strtolower(trim($value)),
);
}
Khi người dùng nhập:
ADMIN@GMAIL.COM
Database lưu:
admin@gmail.com
19. Accessor & Mutator trong CRUD
Trong CRUD Users ở Bài 28, Controller có thể vẫn rất đơn giản:
$user->update($request->validated());
Không cần xử lý:
trim()
strtolower()
ucwords()
trong Controller nếu những quy tắc đó thuộc về Model.
Ví dụ:
$user->email = $request->email;
Mutator tự xử lý:
trim()
strtolower()
Sau đó:
$user->save();
Điều này giúp Controller sạch hơn.
20. Không nên nhồi mọi thứ vào Accessor
Accessor rất tiện nhưng không nên lạm dụng.
Ví dụ không nên đưa những logic quá nặng:
protected function something(): Attribute
{
return Attribute::make(
get: fn () => DB::table(...)->get(...),
);
}
Nếu Accessor thực hiện query database phức tạp, khi hiển thị danh sách hàng trăm User có thể gây ra vấn đề hiệu năng.
Accessor nên ưu tiên:
Xử lý đơn giản
Tính toán đơn giản
Format dữ liệu
Tạo giá trị hiển thị
21. Accessor & Mutator hiện đại
Trong Laravel hiện đại, chúng ta ưu tiên:
protected function name(): Attribute
{
return Attribute::make(
get: fn (string $value) => ucwords($value),
set: fn (string $value) => strtolower(trim($value)),
);
}
Thay vì kiểu cú pháp cũ:
public function getNameAttribute($value)
{
return ucwords($value);
}
public function setNameAttribute($value)
{
$this->attributes['name'] = strtolower(trim($value));
}
Cú pháp Attribute::make() rõ ràng hơn và phù hợp với Laravel hiện đại.
22. Ví dụ hoàn chỉnh cho User Model
Một Model có thể viết:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
protected $fillable = [
'name',
'email',
'role',
'is_active',
];
protected function name(): Attribute
{
return Attribute::make(
get: fn (string $value) => ucwords($value),
set: fn (string $value) =>
strtolower(trim($value)),
);
}
protected function email(): Attribute
{
return Attribute::make(
set: fn (string $value) =>
strtolower(trim($value)),
);
}
protected function roleText(): Attribute
{
return Attribute::make(
get: fn ($value, array $attributes) => match ($attributes['role']) {
'admin' => 'Quản trị viên',
'editor' => 'Biên tập viên',
'user' => 'Người dùng',
default => 'Không xác định',
},
);
}
protected function statusText(): Attribute
{
return Attribute::make(
get: fn ($value, array $attributes) =>
$attributes['is_active']
? 'Đang hoạt động'
: 'Đã khóa',
);
}
}
Trong Blade:
{{ $user->name }}
{{ $user->email }}
{{ $user->role_text }}
{{ $user->status_text }}
23. Kết quả
Giả sử database:
name = "nguyen van a"
email = " ADMIN@GMAIL.COM "
role = "admin"
is_active = 1
Khi đọc:
$user->name
Kết quả:
Nguyen Van A
Khi đọc:
$user->email
Kết quả:
ADMIN@GMAIL.COM
Nếu chỉ khai báo Mutator cho email thì dữ liệu sẽ được chuẩn hóa khi lưu. Sau khi lưu, giá trị database sẽ là:
admin@gmail.com
Role:
$user->role_text
Kết quả:
Quản trị viên
Status:
$user->status_text
Kết quả:
Đang hoạt động
24. Tóm tắt
Có thể nhớ Accessor và Mutator bằng một câu:
ACCESSOR = Đọc dữ liệu → biến đổi → sử dụng
MUTATOR = Ghi dữ liệu → biến đổi → lưu
| Thành phần | Thời điểm | Mục đích |
|---|---|---|
| Accessor | Khi đọc | Format / tính toán dữ liệu |
| Mutator | Khi ghi | Chuẩn hóa dữ liệu |
| Cast | Khi đọc/ghi | Chuyển đổi kiểu dữ liệu |
Ví dụ:
MODEL
│
┌───────────┴───────────┐
│ │
ACCESSOR MUTATOR
│ │
▼ ▼
Khi đọc Khi ghi
│ │
▼ ▼
Format dữ liệu Chuẩn hóa dữ liệu
│ │
▼ ▼
Blade Database
25. Bài tập thực hành
Trong Model User, hãy tạo:
Bài tập 1
Mutator cho email:
ADMIN@GMAIL.COM
→
admin@gmail.com
Bài tập 2
Accessor cho name:
nguyen van a
→
Nguyen Van A
Bài tập 3
Accessor:
$user->role_text
Hiển thị:
admin → Quản trị viên
user → Người dùng
editor → Biên tập viên
Bài tập 4
Accessor:
$user->status_text
Hiển thị:
is_active = 1 → Đang hoạt động
is_active = 0 → Đã khóa
Bài tập 5
Tạo Accessor:
$user->display_name
Nếu name có dữ liệu thì hiển thị name, nếu không thì hiển thị:
Chưa cập nhật tên
🎯 GHI NHỚ BÀI 31
Accessor
↓
Xử lý khi lấy dữ liệu
Mutator
↓
Xử lý khi gán dữ liệu
Attribute::make()
↓
Cú pháp hiện đại trong Laravel
$casts
↓
Chuyển đổi kiểu dữ liệu
Model
↓
Có thể chịu trách nhiệm format
và chuẩn hóa dữ liệu
Sau Bài 31, chúng ta đã biết cách để đưa logic xử lý thuộc tính vào Eloquent Model thay vì nhồi tất cả vào Controller hoặc Blade.
Đây là một bước quan trọng để source Laravel bắt đầu sạch và có cấu trúc hơn.
Bài tiếp theo — Bài 32: Local Scope & Global Scope.




Không có nhận xét nào:
Đăng nhận xét