Laravel: Касти, Аксесори та Мутатори
Glossary overview

Laravel: Касти, Аксесори та Мутатори

Що це таке

Всі три механізми вирішують одну задачу — перетворення даних при читанні з моделі або записі в модель. Замість ручного форматування щоразу — описуєш правило один раз, і Laravel застосовує його автоматично.

Casts — автоматичне приведення типів

Cast вказує тип — Laravel конвертує автоматично в обидва боки: при читанні з БД і при записі в БД.

class User extends Model
{
    protected $casts = [
        'is_active' => 'boolean',           // "1" з БД → true в PHP
        'settings' => 'array',              // JSON рядок з БД → масив в PHP
        'price' => 'float',                 // "19.99" → 19.99
        'birthday' => 'date',               // "2000-01-15" → Carbon обʼєкт
        'email_verified_at' => 'datetime',   // рядок → Carbon
        'metadata' => 'object',             // JSON → stdClass
        'secret' => 'encrypted',            // автоматичне шифрування/дешифрування
        'status' => OrderStatus::class,     // Enum cast (PHP 8.1)
    ];
}

Приклад використання:

$user = User::find(1);

// Без каста — "сирі" дані з БД
$user->is_active;    // "1" (рядок)
$user->settings;     // '{"theme":"dark"}' (JSON-рядок)

// З кастом — перетворені типи
$user->is_active;    // true (boolean)
$user->settings;     // ['theme' => 'dark'] (масив)
$user->settings['theme'];  // 'dark'

// Запис — конвертує назад автоматично
$user->settings = ['theme' => 'light', 'locale' => 'uk'];
$user->save();
// В БД збережеться: '{"theme":"light","locale":"uk"}'

Доступні типи кастів

КастЩо робитьПриклад
booleanРядок/число → true/false"1"true
integerРядок → ціле число"42"42
floatРядок → дробове число"19.99"19.99
stringЧисло → рядок42"42"
arrayJSON-рядок → масив'{"a":1}'['a' => 1]
objectJSON-рядок → stdClass'{"a":1}'object->a
collectionJSON → Collection'[1,2,3]'collect([1,2,3])
dateРядок → Carbon (без часу)"2025-01-15" → Carbon
datetimeРядок → Carbon (з часом)"2025-01-15 14:30" → Carbon
timestampРядок → Unix timestamp1705315200
encryptedШифрує при записі, дешифрує при читанніЧутливі дані
Enum::classРядок → PHP Enum"active"Status::Active

Accessors — перетворення при ЧИТАННІ

Accessor змінює значення коли ти його читаєш з моделі. Корисно для форматування, обчислюваних полів, дефолтних значень.

Новий синтаксис (Laravel 9+)

use Illuminate\Database\Eloquent\Casts\Attribute;

class User extends Model
{
    // Комбінує два поля в одне
    protected function fullName(): Attribute
    {
        return Attribute::make(
            get: fn () => $this->first_name . ' ' . $this->last_name,
        );
    }

    // Форматує ціну
    protected function formattedPrice(): Attribute
    {
        return Attribute::make(
            get: fn () => number_format($this->price, 2) . ' ₴',
        );
    }

    // Дефолтна аватарка якщо немає своєї
    protected function avatarUrl(): Attribute
    {
        return Attribute::make(
            get: fn () => $this->avatar
                ? Storage::url($this->avatar)
                : 'https://ui-avatars.com/api/?name=' . urlencode($this->name),
        );
    }
}

Використання:

$user->full_name;        // "Олексій Шевченко"
$user->formatted_price;  // "1 299.00 ₴"
$user->avatar_url;       // "/storage/avatars/abc.jpg" або дефолтна

Імʼя методу в camelCase (fullName) автоматично перетворюється в snake_case при зверненні (full_name).

Старий синтаксис (все ще працює)

class User extends Model
{
    public function getFullNameAttribute(): string
    {
        return $this->first_name . ' ' . $this->last_name;
    }
}

$user->full_name;  // "Олексій Шевченко"

Конвенція іменування: get + FullName + Attribute.

Mutators — перетворення при ЗАПИСІ

Mutator змінює значення коли ти його записуєш у модель. Корисно для очистки, нормалізації, хешування.

Новий синтаксис (Laravel 9+)

class User extends Model
{
    // Хешувати пароль автоматично при записі
    protected function password(): Attribute
    {
        return Attribute::make(
            set: fn (string $value) => Hash::make($value),
        );
    }

    // Завжди lowercase email
    protected function email(): Attribute
    {
        return Attribute::make(
            set: fn (string $value) => strtolower(trim($value)),
        );
    }

    // Очистити номер телефону від зайвих символів
    protected function phone(): Attribute
    {
        return Attribute::make(
            set: fn (string $value) => preg_replace('/[^\d+]/', '', $value),
        );
    }
}

Використання:

$user->password = 'secret123';
// В БД збережеться: "$2y$12$xK9mN8pQ..." (хеш)

$user->email = '  [email protected]  ';
// В БД збережеться: "[email protected]"

$user->phone = '+38 (099) 123-45-67';
// В БД збережеться: "+380991234567"

Старий синтаксис

class User extends Model
{
    public function setPasswordAttribute(string $value): void
    {
        $this->attributes['password'] = Hash::make($value);
    }

    public function setEmailAttribute(string $value): void
    {
        $this->attributes['email'] = strtolower(trim($value));
    }
}

Конвенція іменування: set + Password + Attribute.

Accessor + Mutator разом

Один метод може мати і get:, і set::

class User extends Model
{
    protected function name(): Attribute
    {
        return Attribute::make(
            get: fn (string $value) => ucfirst($value),          // при читанні: "олексій" → "Олексій"
            set: fn (string $value) => strtolower(trim($value)), // при записі: "  ОЛЕКСІЙ " → "олексій"
        );
    }
}

$user->name = '  ОЛЕКСІЙ ';  // mutator: зберіг "олексій"
echo $user->name;             // accessor: вивів "Олексій"

Custom Cast — свій клас касту

Для складної логіки можна створити окремий клас касту:

use Illuminate\Contracts\Database\Eloquent\CastsAttributes;

class MoneyCast implements CastsAttributes
{
    public function get($model, string $key, $value, array $attributes): Money
    {
        return new Money(
            amount: $value,
            currency: $attributes['currency'] ?? 'UAH'
        );
    }

    public function set($model, string $key, $value, array $attributes): int
    {
        if ($value instanceof Money) {
            return $value->amount;
        }
        return (int) $value;
    }
}

Використання:

class Order extends Model
{
    protected $casts = [
        'total' => MoneyCast::class,
    ];
}

$order->total;          // Money { amount: 1500, currency: "UAH" }
$order->total->amount;  // 1500

Практичні приклади

Модель User з усіма трьома

class User extends Model
{
    // Касти — прості перетворення типів
    protected $casts = [
        'is_active' => 'boolean',
        'settings' => 'array',
        'birthday' => 'date',
        'email_verified_at' => 'datetime',
    ];

    // Accessor — обчислюване поле (немає в БД)
    protected function fullName(): Attribute
    {
        return Attribute::make(
            get: fn () => $this->first_name . ' ' . $this->last_name,
        );
    }

    // Accessor — вік на основі дня народження
    protected function age(): Attribute
    {
        return Attribute::make(
            get: fn () => $this->birthday?->age,
        );
    }

    // Accessor — дефолтна аватарка
    protected function avatarUrl(): Attribute
    {
        return Attribute::make(
            get: fn () => $this->avatar
                ? Storage::url($this->avatar)
                : 'https://ui-avatars.com/api/?name=' . urlencode($this->name),
        );
    }

    // Mutator — хешування пароля
    protected function password(): Attribute
    {
        return Attribute::make(
            set: fn (string $value) => Hash::make($value),
        );
    }

    // Accessor + Mutator — нормалізація email
    protected function email(): Attribute
    {
        return Attribute::make(
            get: fn (string $value) => $value,
            set: fn (string $value) => strtolower(trim($value)),
        );
    }
}

Використання в Blade

<h1>{{ $user->full_name }}</h1>           {{-- accessor --}}
<p>Вік: {{ $user->age }}</p>               {{-- accessor --}}
<img src="{{ $user->avatar_url }}">        {{-- accessor --}}
<p>Активний: {{ $user->is_active ? 'Так' : 'Ні' }}</p>  {{-- cast --}}
<p>Тема: {{ $user->settings['theme'] }}</p> {{-- cast array --}}
<p>Дата: {{ $user->birthday->format('d.m.Y') }}</p>       {{-- cast date --}}

Порівняння

CastsAccessorsMutators
Коли працюєПри читанні і записіТільки при читанніТільки при записі
Де оголошуєтьсяМасив $castsМетод з Attribute::make(get:)Метод з Attribute::make(set:)
Для чогоПриведення типів (JSON→array, string→bool)Форматування, обчислювані поляОчистка, хешування, нормалізація
Віртуальні поляНі — працює з реальними колонкамиТак — full_name без колонки в БДНі
Складна логікаЧерез Custom Cast класБудь-яка логіка в get:Будь-яка логіка в set:

Коли що використовувати

Cast — простий тип, конвертація в обидва боки: 'is_active' => 'boolean', 'settings' => 'array', 'birthday' => 'date'.

Accessor — форматування при відображенні, обчислювані поля: full_name, avatar_url, formatted_price, age.

Mutator — очистка і перетворення при збереженні: хешування пароля, lowercase email, очистка телефону від зайвих символів.

Custom Cast — складна логіка перетворення в обидва боки: Money, Address, Coordinates.