Skip to content

Eloquent: Mutators & Casting

Giới thiệu (Introduction)

Accessor, mutator và attribute casting cho phép bạn biến đổi giá trị thuộc tính Eloquent khi truy xuất hoặc thiết lập trên model instance. Ví dụ, bạn có thể mã hóa giá trị khi lưu vào database rồi tự động giải mã khi truy cập. Hoặc chuyển đổi chuỗi JSON trong database thành mảng khi truy cập qua model.

Accessors & Mutators

Định nghĩa Accessor

Accessor biến đổi giá trị thuộc tính Eloquent khi truy xuất. Tạo method protected trên model với tên camelCase tương ứng. Method phải return type-hint Illuminate\Database\Eloquent\Casts\Attribute:

php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * Accessor cho first name.
     */
    protected function firstName(): Attribute
    {
        return Attribute::make(
            get: fn (string $value) => ucfirst($value),
        );
    }
}

Truy cập giá trị:

php
use App\Models\User;

$user = User::find(1);

$firstName = $user->first_name;

Xây dựng Value Object từ nhiều thuộc tính

Accessor có thể chuyển đổi nhiều thuộc tính thành "value object":

php
use App\Support\Address;
use Illuminate\Database\Eloquent\Casts\Attribute;

protected function address(): Attribute
{
    return Attribute::make(
        get: fn (mixed $value, array $attributes) => new Address(
            $attributes['address_line_one'],
            $attributes['address_line_two'],
        ),
    );
}

Định nghĩa Mutator

Mutator biến đổi giá trị thuộc tính khi thiết lập. Cung cấp tham số set:

php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * Accessor/Mutator cho first name.
     */
    protected function firstName(): Attribute
    {
        return Attribute::make(
            get: fn (string $value) => ucfirst($value),
            set: fn (string $value) => strtolower($value),
        );
    }
}

Sử dụng mutator:

php
use App\Models\User;

$user = User::find(1);

$user->first_name = 'Sally';

Callback set nhận giá trị Sally, áp dụng strtolower rồi lưu vào mảng $attributes nội bộ.

Mutate nhiều thuộc tính

Mutator có thể trả về mảng để thiết lập nhiều thuộc tính:

php
use App\Support\Address;
use Illuminate\Database\Eloquent\Casts\Attribute;

protected function address(): Attribute
{
    return Attribute::make(
        get: fn (mixed $value, array $attributes) => new Address(
            $attributes['address_line_one'],
            $attributes['address_line_two'],
        ),
        set: fn (Address $value) => [
            'address_line_one' => $value->lineOne,
            'address_line_two' => $value->lineTwo,
        ],
    );
}

Attribute Casting

Attribute casting cung cấp chức năng tương tự accessor/mutator mà không cần định nghĩa method. Method casts trả về mảng ánh xạ thuộc tính → kiểu dữ liệu.

Các kiểu cast được hỗ trợ:

Cast TypeMô tả
arrayChuyển JSON thành mảng PHP
booleanChuyển thành boolean
collectionChuyển JSON thành Collection
dateChuyển thành Carbon date
datetimeChuyển thành Carbon datetime
immutable_dateImmutable Carbon date
immutable_datetimeImmutable Carbon datetime
decimal:<precision>Số thập phân với độ chính xác
double / float / realSố thực
encryptedMã hóa/giải mã tự động
hashedHash giá trị
integerSố nguyên
objectChuyển JSON thành object
stringChuỗi
timestampUNIX timestamp

Ví dụ cast is_admin từ integer sang boolean:

php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * Các thuộc tính cần cast.
     *
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'is_admin' => 'boolean',
        ];
    }
}

Sau khi cast, is_admin luôn là boolean:

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

if ($user->is_admin) {
    // ...
}

Thêm cast tạm thời tại runtime:

php
$user->mergeCasts([
    'is_admin' => 'integer',
    'options' => 'object',
]);

LƯU Ý

Thuộc tính null sẽ không được cast. Không bao giờ định nghĩa cast trùng tên với relationship hoặc khóa chính.

Array và JSON Casting

Cast array hữu ích khi làm việc với cột JSON:

php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    protected function casts(): array
    {
        return [
            'options' => 'array',
        ];
    }
}

Sử dụng:

php
use App\Models\User;

$user = User::find(1);

$options = $user->options;

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

$user->options = $options;

$user->save();

Cập nhật trường JSON đơn lẻ:

php
$user = User::find(1);

$user->update(['options->key' => 'value']);

JSON và Unicode

Sử dụng json:unicode để lưu ký tự Unicode không escape:

php
protected function casts(): array
{
    return [
        'options' => 'json:unicode',
    ];
}

Date Casting

Mặc định, Eloquent cast created_atupdated_at thành Carbon. Bạn có thể cast thêm các thuộc tính date khác:

php
protected function casts(): array
{
    return [
        'created_at' => 'datetime:Y-m-d',
    ];
}

Tùy chỉnh format serialization mặc định:

php
protected function serializeDate(DateTimeInterface $date): string
{
    return $date->format('Y-m-d');
}

Enum Casting

Bạn có thể cast thuộc tính thành PHP Enum:

php
use App\Enums\ServerStatus;

protected function casts(): array
{
    return [
        'status' => ServerStatus::class,
    ];
}

Sử dụng:

php
if ($server->status == ServerStatus::Provisioned) {
    $server->status = ServerStatus::Ready;

    $server->save();
}

Encrypted Casting

Cast encrypted mã hóa giá trị bằng encryption của Laravel:

php
protected function casts(): array
{
    return [
        'secret' => 'encrypted',
        'preferences' => 'encrypted:array',
        'addresses' => 'encrypted:collection',
    ];
}

Query Time Casting

Áp dụng cast khi chạy query mà không sửa model:

php
$users = User::select([
    'users.*',
    'last_posted_at' => Post::selectRaw('MAX(created_at)')
        ->whereColumn('user_id', 'users.id')
])->withCasts([
    'last_posted_at' => 'datetime'
])->get();

Custom Casts

Tạo cast tùy chỉnh bằng lệnh Artisan:

bash
php artisan make:cast AsJson

Class mới implement interface CastsAttributes:

php
<?php

namespace App\Casts;

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

class AsJson implements CastsAttributes
{
    /**
     * Cast giá trị từ database.
     *
     * @param array<string, mixed> $attributes
     * @return array<string, mixed>
     */
    public function get(
        Model $model,
        string $key,
        mixed $value,
        array $attributes,
    ): array {
        return json_decode($value, true);
    }

    /**
     * Chuẩn bị giá trị để lưu vào database.
     *
     * @param array<string, mixed> $attributes
     */
    public function set(
        Model $model,
        string $key,
        mixed $value,
        array $attributes,
    ): string {
        return json_encode($value);
    }
}

Sử dụng custom cast:

php
<?php

namespace App\Models;

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

class User extends Model
{
    protected function casts(): array
    {
        return [
            'options' => AsJson::class,
        ];
    }
}

Value Object Casting

Cast có thể biến đổi giá trị thành value object tùy chỉnh, cho phép truy cập các method trên object.

Inbound Casting

Đôi khi chỉ cần biến đổi giá trị khi lưu (không cần khi truy xuất). Implement interface CastsInboundAttributes:

php
use Illuminate\Contracts\Database\Eloquent\CastsInboundAttributes;

class Hash implements CastsInboundAttributes
{
    public function set(
        Model $model,
        string $key,
        mixed $value,
        array $attributes,
    ): string {
        return hash('sha256', $value);
    }
}

Cast Parameters

Truyền tham số cho custom cast (phân cách bằng dấu :):

php
protected function casts(): array
{
    return [
        'secret' => Hash::class . ':sha256',
    ];
}

Castables

Cho phép value object class tự định nghĩa cách cast bằng interface Castable:

php
use Illuminate\Contracts\Database\Eloquent\Castable;

class Address implements Castable
{
    public static function castUsing(array $arguments): CastsAttributes
    {
        return new AddressCast;
    }
}

Sử dụng trực tiếp class name trong cast:

php
protected function casts(): array
{
    return [
        'address' => Address::class,
    ];
}