Eloquent: Мутатори та Приведення типів (Кастинг)

Вступ

Аксесори, мутатори та приведення атрибутів дозволяють трансформувати значення атрибутів Eloquent, коли ви отримуєте або встановлюєте їх на екземплярах моделі. Наприклад, ви можете захотіти використовувати Laravel encrypter для шифрування значення під час його зберігання в базі даних, а потім автоматично розшифровувати атрибут, коли ви отримуєте до нього доступ на моделі Eloquent. Або, можливо, ви захочете перетворити JSON-рядок, що зберігається у вашій базі даних, на масив, коли він отримується через вашу модель Eloquent.

Аксесори та Мутатори

Визначення Аксесора

Аксесор перетворює значення атрибута Eloquent, коли до нього звертаються. Щоб визначити аксесор, створіть захищений метод у вашій моделі, щоб представляти доступний атрибут. Ім'я цього методу має відповідати "camel case" представленню справжнього базового атрибута моделі / стовпця бази даних, якщо це можливо.

У цьому прикладі ми визначимо аксесор для атрибута first_name. Аксесор буде автоматично викликаний Eloquent при спробі отримати значення атрибута first_name. Усі методи аксесорів / мутаторів атрибутів повинні оголошувати тип повернення Illuminate\Database\Eloquent\Casts\Attribute:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
 
class User extends Model
{
    /**
     * Отримати ім'я користувача.
     */
    protected function firstName(): Attribute
    {
        return Attribute::make(
            get: fn (string $value) => ucfirst($value),
        );
    }
}

Усі методи доступу повертають екземпляр Attribute, який визначає, як буде здійснюватися доступ до атрибута і, за бажанням, його зміна. У цьому прикладі ми визначаємо лише, як буде здійснюватися доступ до атрибута. Для цього ми передаємо аргумент get у конструктор класу Attribute.

Як ви можете бачити, оригінальне значення стовпця передається до аксесора, що дозволяє вам маніпулювати та повертати значення. Щоб отримати доступ до значення аксесора, ви можете просто звернутися до атрибуту first_name на екземплярі моделі:

use App\Models\User;
 
$user = User::find(1);
 
$firstName = $user->first_name;

Якщо ви хочете, щоб ці обчислені значення були додані до масиву / JSON-представлень вашої моделі, вам потрібно буде їх додати.

Створення об'єктів значень з декількох атрибутів

Іноді ваш аксесор може потребувати перетворення декількох атрибутів моделі в один "об'єкт значення". Для цього ваш замикання get може приймати другий аргумент $attributes, який буде автоматично переданий у замикання і міститиме масив усіх поточних атрибутів моделі:

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'],
        ),
    );
}

Кешування Аксесорів

Коли повертаються об'єкти значень з аксесорів, будь-які зміни, внесені до об'єкта значень, автоматично синхронізуються назад з моделлю перед тим, як модель буде збережена. Це можливо, тому що Eloquent зберігає екземпляри, повернені аксесорами, щоб він міг повертати той самий екземпляр кожного разу, коли викликається аксесор:

use App\Models\User;
 
$user = User::find(1);
 
$user->address->lineOne = 'Updated Address Line 1 Value';
$user->address->lineTwo = 'Updated Address Line 2 Value';
 
$user->save();

Однак, іноді ви можете захотіти увімкнути кешування для примітивних значень, таких як рядки та булеві значення, особливо якщо вони є обчислювально інтенсивними. Щоб досягти цього, ви можете викликати метод shouldCache при визначенні вашого аксесора:

protected function hash(): Attribute
{
    return Attribute::make(
        get: fn (string $value) => bcrypt(gzuncompress($value)),
    )->shouldCache();
}

Якщо ви хочете вимкнути кешування об'єктів для атрибутів, ви можете викликати метод withoutObjectCaching при визначенні атрибута:

/**
 * Взаємодія з адресою користувача.
 */
protected function address(): Attribute
{
    return Attribute::make(
        get: fn (mixed $value, array $attributes) => new Address(
            $attributes['address_line_one'],
            $attributes['address_line_two'],
        ),
    )->withoutObjectCaching();
}

Визначення Мутатора

Мутатор перетворює значення атрибута Eloquent, коли воно встановлюється. Щоб визначити мутатор, ви можете надати аргумент set при визначенні вашого атрибута. Давайте визначимо мутатор для атрибута first_name. Цей мутатор буде автоматично викликано, коли ми спробуємо встановити значення атрибута first_name на моделі:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
 
class User extends Model
{
    /**
     * Взаємодія з ім'ям користувача.
     */
    protected function firstName(): Attribute
    {
        return Attribute::make(
            get: fn (string $value) => ucfirst($value),
            set: fn (string $value) => strtolower($value),
        );
    }
}

Замикання мутатора отримає значення, яке встановлюється для атрибута, дозволяючи вам маніпулювати значенням і повертати змінене значення. Щоб використовувати наш мутатор, нам потрібно лише встановити атрибут first_name на моделі Eloquent:

use App\Models\User;
 
$user = User::find(1);
 
$user->first_name = 'Sally';

У цьому прикладі зворотний виклик set буде викликано зі значенням Sally. Мутатор потім застосує функцію strtolower до імені та встановить отримане значення у внутрішньому масиві $attributes моделі.

Зміна декількох атрибутів

Іноді ваш мутатор може потребувати встановлення декількох атрибутів на базовій моделі. Для цього ви можете повернути масив з замикання set. Кожен ключ у масиві повинен відповідати базовому атрибуту / стовпцю бази даних, пов'язаному з моделлю:

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,
        ],
    );
}

Кастинг Атрибутів

Атрибутивне приведення надає функціональність, схожу на аксесори та мутатори, без необхідності визначати будь-які додаткові методи у вашій моделі. Натомість, метод casts вашої моделі забезпечує зручний спосіб перетворення атрибутів у загальні типи даних.

Метод casts повинен повертати масив, де ключем є назва атрибуту, що підлягає приведенню, а значенням є тип, до якого ви бажаєте привести стовпець. Підтримувані типи приведення:

  • array
  • AsFluent::class
  • AsStringable::class
  • AsUri::class
  • boolean
  • collection
  • date
  • datetime
  • immutable_date
  • immutable_datetime
  • decimal:<precision>
  • double
  • encrypted
  • encrypted:array
  • encrypted:collection
  • encrypted:object
  • float
  • hashed
  • integer
  • object
  • real
  • string
  • timestamp

Щоб продемонструвати перетворення атрибутів, давайте перетворимо атрибут is_admin, який зберігається в нашій базі даних як ціле число (0 або 1), у булеве значення:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
 
class User extends Model
{
    /**
     * Отримати атрибути, які слід привести до типу.
     *
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'is_admin' => 'boolean',
        ];
    }
}

Після визначення перетворення атрибут is_admin завжди буде перетворено на булеве значення, коли ви отримуєте до нього доступ, навіть якщо базове значення зберігається в базі даних як ціле число:

$user = App\Models\User::find(1);
 
if ($user->is_admin) {
    // ...
}

Якщо вам потрібно додати нове, тимчасове перетворення під час виконання, ви можете використовувати метод mergeCasts. Ці визначення перетворень будуть додані до будь-яких перетворень, вже визначених на моделі:

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

Атрибути, що мають значення null, не будуть перетворені. Крім того, ніколи не слід визначати перетворення (або атрибут), що має таке ж ім'я, як і відношення, або призначати перетворення первинному ключу моделі.

Кастинг Stringable

Ви можете використовувати клас приведення Illuminate\Database\Eloquent\Casts\AsStringable для приведення атрибута моделі до об'єкта fluent Illuminate\Support\Stringable:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Casts\AsStringable;
use Illuminate\Database\Eloquent\Model;
 
class User extends Model
{
    /**
     * Отримати атрибути, які слід привести до типу.
     *
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'directory' => AsStringable::class,
        ];
    }
}

Масиви та JSON Кастинг

Каст array є особливо корисним при роботі зі стовпцями, які зберігаються як серіалізований JSON. Наприклад, якщо у вашій базі даних є поле типу JSON або TEXT, яке містить серіалізований JSON, додавання касту array до цього атрибуту автоматично десеріалізує атрибут у PHP масив, коли ви отримуєте до нього доступ у вашій Eloquent моделі:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
 
class User extends Model
{
    /**
     * Отримати атрибути, які слід привести до типу.
     *
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'options' => 'array',
        ];
    }
}

Після визначення перетворення ви можете отримати доступ до атрибуту options, і він автоматично буде десеріалізований з JSON у PHP масив. Коли ви встановлюєте значення атрибуту options, переданий масив автоматично буде серіалізований назад у JSON для зберігання:

use App\Models\User;
 
$user = User::find(1);
 
$options = $user->options;
 
$options['key'] = 'value';
 
$user->options = $options;
 
$user->save();

Щоб оновити одне поле JSON-атрибута з більш стислою синтаксисом, ви можете зробити атрибут масово призначуваним і використовувати оператор -> при виклику методу update:

$user = User::find(1);
 
$user->update(['options->key' => 'value']);

JSON і Unicode

Якщо ви хочете зберегти атрибут масиву як JSON з неекранованими символами Unicode, ви можете використовувати приведення json:unicode:

/**
 * Отримати атрибути, які слід привести до типу.
 *
 * @return array<string, string>
 */
protected function casts(): array
{
    return [
        'options' => 'json:unicode',
    ];
}

Кастинг масиву, об'єкта та колекції

Хоча стандартне приведення до типу array є достатнім для багатьох застосунків, воно має деякі недоліки. Оскільки приведення до типу array повертає примітивний тип, неможливо безпосередньо змінити елемент масиву. Наприклад, наступний код викличе помилку PHP:

$user = User::find(1);
 
$user->options['key'] = $value;

Щоб вирішити це, Laravel пропонує приведення AsArrayObject, яке перетворює ваш JSON атрибут до класу ArrayObject. Ця функція реалізована за допомогою кастомного приведення Laravel, що дозволяє Laravel інтелектуально кешувати та трансформувати змінений об'єкт так, що окремі зміщення можуть бути змінені без виклику помилки PHP. Щоб використовувати приведення AsArrayObject, просто призначте його атрибуту:

use Illuminate\Database\Eloquent\Casts\AsArrayObject;
 
/**
 * Отримати атрибути, які слід привести до типу.
 *
 * @return array<string, string>
 */
protected function casts(): array
{
    return [
        'options' => AsArrayObject::class,
    ];
}

Аналогічно, Laravel пропонує приведення AsCollection, яке перетворює ваш атрибут JSON на екземпляр Колекції Laravel:

use Illuminate\Database\Eloquent\Casts\AsCollection;
 
/**
 * Отримати атрибути, які слід привести.
 *
 * @return array<string, string>
 */
protected function casts(): array
{
    return [
        'options' => AsCollection::class,
    ];
}

Якщо ви хочете, щоб приведення AsCollection створювало екземпляр користувацького класу колекції замість базового класу колекції Laravel, ви можете вказати ім'я класу колекції як аргумент приведення:

use App\Collections\OptionCollection;
use Illuminate\Database\Eloquent\Casts\AsCollection;
 
/**
 * Отримати атрибути, які слід привести до типу.
 *
 * @return array<string, string>
 */
protected function casts(): array
{
    return [
        'options' => AsCollection::using(OptionCollection::class),
    ];
}

Метод of може бути використаний для вказівки, що елементи колекції повинні бути відображені в заданий клас через метод mapInto колекції:

use App\ValueObjects\Option;
use Illuminate\Database\Eloquent\Casts\AsCollection;
 
/**
 * Отримати атрибути, які слід привести до типу.
 *
 * @return array<string, string>
 */
protected function casts(): array
{
    return [
        'options' => AsCollection::of(Option::class)
    ];
}

Коли відображаєте колекції на об'єкти, об'єкт повинен реалізовувати інтерфейси Illuminate\Contracts\Support\Arrayable та JsonSerializable, щоб визначити, як їхні екземпляри повинні бути серіалізовані в базу даних як JSON:

<?php
 
namespace App\ValueObjects;
 
use Illuminate\Contracts\Support\Arrayable;
use JsonSerializable;
 
class Option implements Arrayable, JsonSerializable
{
    public string $name;
    public mixed $value;
    public bool $isLocked;
 
    /**
     * Створити новий екземпляр Option.
     */
    public function __construct(array $data)
    {
        $this->name = $data['name'];
        $this->value = $data['value'];
        $this->isLocked = $data['is_locked'];
    }
 
    /**
     * Отримати екземпляр як масив.
     *
     * @return array{name: string, data: string, is_locked: bool}
     */
    public function toArray(): array
    {
        return [
            'name' => $this->name,
            'value' => $this->value,
            'is_locked' => $this->isLocked,
        ];
    }
 
    /**
     * Вкажіть дані, які слід серіалізувати в JSON.
     *
     * @return array{name: string, data: string, is_locked: bool}
     */
    public function jsonSerialize(): array
    {
        return $this->toArray();
    }
}

Кастинг Дати

За замовчуванням, Eloquent буде перетворювати стовпці created_at та updated_at на екземпляри Carbon, який розширює PHP клас DateTime і надає безліч корисних методів. Ви можете перетворювати додаткові атрибути дати, визначивши додаткові перетворення дати в методі casts вашої моделі. Зазвичай, дати слід перетворювати, використовуючи типи перетворення datetime або immutable_datetime.

Коли визначаєте приведення типу date або datetime, ви також можете вказати формат дати. Цей формат буде використовуватися, коли модель серіалізується в масив або JSON:

/**
 * Отримати атрибути, які слід привести до типу.
 *
 * @return array<string, string>
 */
protected function casts(): array
{
    return [
        'created_at' => 'datetime:Y-m-d',
    ];
}

Коли стовпець приводиться до типу дати, ви можете встановити відповідне значення атрибута моделі як UNIX-мітку часу, рядок дати (Y-m-d), рядок дати-часу або екземпляр DateTime / Carbon. Значення дати буде правильно перетворено і збережено у вашій базі даних.

Ви можете налаштувати формат серіалізації за замовчуванням для всіх дат вашої моделі, визначивши метод serializeDate у вашій моделі. Цей метод не впливає на те, як ваші дати форматуються для зберігання в базі даних:

/**
 * Підготуйте дату для серіалізації в масив / JSON.
 */
protected function serializeDate(DateTimeInterface $date): string
{
    return $date->format('Y-m-d');
}

Щоб вказати формат, який слід використовувати при фактичному зберіганні дат моделі у вашій базі даних, ви повинні визначити властивість $dateFormat у вашій моделі:

/**
 * Формат зберігання стовпців дати моделі.
 *
 * @var string
 */
protected $dateFormat = 'U';

Кастинг дат, серіалізація та часові пояси

За замовчуванням, перетворення date та datetime серіалізуватимуть дати у форматі рядка дати UTC ISO-8601 (YYYY-MM-DDTHH:MM:SS.uuuuuuZ), незалежно від часового поясу, вказаного у конфігураційній опції timezone вашого застосунку. Наполегливо рекомендується завжди використовувати цей формат серіалізації, а також зберігати дати вашого застосунку в часовому поясі UTC, не змінюючи конфігураційну опцію timezone вашого застосунку з її значення за замовчуванням UTC. Постійне використання часового поясу UTC у вашому застосунку забезпечить максимальний рівень сумісності з іншими бібліотеками для маніпуляції датами, написаними на PHP та JavaScript.

Якщо до date або datetime застосовано користувацький формат, такий як datetime:Y-m-d H:i:s, внутрішній часовий пояс екземпляра Carbon буде використано під час серіалізації дати. Зазвичай це буде часовий пояс, вказаний у параметрі конфігурації timezone вашого застосунку. Однак важливо зазначити, що стовпці timestamp, такі як created_at і updated_at, не підпадають під цю поведінку і завжди форматуються в UTC, незалежно від налаштувань часового поясу застосунку.

Кастинг Enum

Eloquent також дозволяє перетворювати значення ваших атрибутів на PHP Enums. Щоб досягти цього, ви можете вказати атрибут і enum, які ви бажаєте перетворити, у методі casts вашої моделі:

use App\Enums\ServerStatus;
 
/**
 * Отримати атрибути, які слід привести до типу.
 *
 * @return array<string, string>
 */
protected function casts(): array
{
    return [
        'status' => ServerStatus::class,
    ];
}

Після того як ви визначили приведення на вашій моделі, вказаний атрибут буде автоматично приведений до і з enum, коли ви взаємодієте з атрибутом:

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

Кастинг масивів Enum

Іноді вам може знадобитися, щоб ваша модель зберігала масив значень enum в одному стовпці. Для цього ви можете використовувати приведення AsEnumArrayObject або AsEnumCollection, надані Laravel:

use App\Enums\ServerStatus;
use Illuminate\Database\Eloquent\Casts\AsEnumCollection;
 
/**
 * Отримати атрибути, які слід привести до типу.
 *
 * @return array<string, string>
 */
protected function casts(): array
{
    return [
        'statuses' => AsEnumCollection::of(ServerStatus::class),
    ];
}

Шифроване Кастування

Каст encrypted зашифрує значення атрибуту моделі, використовуючи вбудовані функції шифрування Laravel. Крім того, касти encrypted:array, encrypted:collection, encrypted:object, AsEncryptedArrayObject та AsEncryptedCollection працюють так само, як і їх незашифровані аналоги; однак, як ви можете очікувати, базове значення зашифроване при зберіганні у вашій базі даних.

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

Ротація ключів

Як вам відомо, Laravel шифрує рядки, використовуючи значення конфігурації key, вказане у файлі конфігурації app вашого застосунку. Зазвичай це значення відповідає значенню змінної середовища APP_KEY. Якщо вам потрібно змінити ключ шифрування вашого застосунку, вам потрібно буде вручну повторно зашифрувати ваші зашифровані атрибути, використовуючи новий ключ.

Кастинг під час запиту

Іноді вам може знадобитися застосувати приведення типів під час виконання запиту, наприклад, коли вибираєте необроблене значення з таблиці. Наприклад, розгляньте наступний запит:

use App\Models\Post;
use App\Models\User;
 
$users = User::select([
    'users.*',
    'last_posted_at' => Post::selectRaw('MAX(created_at)')
        ->whereColumn('user_id', 'users.id')
])->get();

Атрибут last_posted_at у результатах цього запиту буде простим рядком. Було б чудово, якби ми могли застосувати приведення до datetime для цього атрибута під час виконання запиту. На щастя, ми можемо досягти цього, використовуючи метод withCasts:

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

Користувацькі Касти

Laravel має різноманітні вбудовані, корисні типи приведення; однак, іноді вам може знадобитися визначити власні типи приведення. Щоб створити приведення, виконайте команду Artisan make:cast. Нова клас приведення буде розміщено у вашій директорії app/Casts:

php artisan make:cast AsJson

Усі класи користувацьких перетворень реалізують інтерфейс CastsAttributes. Класи, що реалізують цей інтерфейс, повинні визначити методи get та set. Метод get відповідає за перетворення сирого значення з бази даних у перетворене значення, тоді як метод set повинен перетворити перетворене значення у сире значення, яке може бути збережене в базі даних. Як приклад, ми повторно реалізуємо вбудований тип перетворення json як користувацький тип перетворення:

<?php
 
namespace App\Casts;
 
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;
 
class AsJson implements CastsAttributes
{
    /**
     * Перетворити задане значення.
     *
     * @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);
    }
 
    /**
     * Підготуйте задане значення для зберігання.
     *
     * @param  array<string, mixed>  $attributes
     */
    public function set(
        Model $model,
        string $key,
        mixed $value,
        array $attributes,
    ): string {
        return json_encode($value);
    }
}

Після того як ви визначили власний тип перетворення, ви можете прикріпити його до атрибуту моделі, використовуючи ім'я класу:

<?php
 
namespace App\Models;
 
use App\Casts\AsJson;
use Illuminate\Database\Eloquent\Model;
 
class User extends Model
{
    /**
     * Отримати атрибути, які слід привести до типу.
     *
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'options' => AsJson::class,
        ];
    }
}

Кастинг Об'єктів Значення

Ви не обмежені приведенням значень до примітивних типів. Ви також можете приводити значення до об'єктів. Визначення користувацьких приведень, які приводять значення до об'єктів, дуже схоже на приведення до примітивних типів; однак, якщо ваш об'єкт значення охоплює більше ніж одну колонку бази даних, метод set повинен повертати масив пар ключ / значення, які будуть використані для встановлення сирих, збережених значень на моделі. Якщо ваш об'єкт значення впливає лише на одну колонку, ви повинні просто повернути збережене значення.

Як приклад, ми визначимо клас користувацького перетворення, який перетворює декілька значень моделі в один об'єкт значення Address. Ми припустимо, що об'єкт значення Address має дві публічні властивості: lineOne та lineTwo:

<?php
 
namespace App\Casts;
 
use App\ValueObjects\Address;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;
use InvalidArgumentException;
 
class AsAddress implements CastsAttributes
{
    /**
     * Перетворити задане значення.
     *
     * @param  array<string, mixed>  $attributes
     */
    public function get(
        Model $model,
        string $key,
        mixed $value,
        array $attributes,
    ): Address {
        return new Address(
            $attributes['address_line_one'],
            $attributes['address_line_two']
        );
    }
 
    /**
     * Підготуйте задане значення для зберігання.
     *
     * @param  array<string, mixed>  $attributes
     * @return array<string, string>
     */
    public function set(
        Model $model,
        string $key,
        mixed $value,
        array $attributes,
    ): array {
        if (! $value instanceof Address) {
            throw new InvalidArgumentException('The given value is not an Address instance.');
        }
 
        return [
            'address_line_one' => $value->lineOne,
            'address_line_two' => $value->lineTwo,
        ];
    }
}

Коли відбувається приведення до об'єктів значень, будь-які зміни, внесені до об'єкта значення, автоматично синхронізуються назад з моделлю перед збереженням моделі:

use App\Models\User;
 
$user = User::find(1);
 
$user->address->lineOne = 'Updated Address Value';
 
$user->save();

Якщо ви плануєте серіалізувати ваші моделі Eloquent, що містять об'єкти значень, у JSON або масиви, ви повинні реалізувати інтерфейси Illuminate\Contracts\Support\Arrayable та JsonSerializable на об'єкті значень.

Кешування Об'єктів Значення

Коли атрибути, які перетворюються на об'єкти значень, розв'язуються, вони кешуються Eloquent. Тому той самий екземпляр об'єкта буде повернуто, якщо атрибут буде доступний знову.

Якщо ви хочете вимкнути поведінку кешування об'єктів у класах власних перетворень, ви можете оголосити публічну властивість withoutObjectCaching у вашому класі власного перетворення:

class AsAddress implements CastsAttributes
{
    public bool $withoutObjectCaching = true;
 
    // ...
}

Масив / JSON Серіалізація

Коли модель Eloquent перетворюється на масив або JSON за допомогою методів toArray і toJson, ваші об'єкти зі значеннями, що мають власні перетворення, зазвичай також будуть серіалізовані, якщо вони реалізують інтерфейси Illuminate\Contracts\Support\Arrayable і JsonSerializable. Однак, при використанні об'єктів зі значеннями, наданих сторонніми бібліотеками, ви можете не мати можливості додати ці інтерфейси до об'єкта.

Отже, ви можете вказати, що ваш клас власного перетворення буде відповідальним за серіалізацію об'єкта значення. Для цього ваш клас власного перетворення повинен реалізовувати інтерфейс Illuminate\Contracts\Database\Eloquent\SerializesCastableAttributes. Цей інтерфейс вказує, що ваш клас повинен містити метод serialize, який повинен повертати серіалізовану форму вашого об'єкта значення:

/**
 * Отримати серіалізоване представлення значення.
 *
 * @param  array<string, mixed>  $attributes
 */
public function serialize(
    Model $model,
    string $key,
    mixed $value,
    array $attributes,
): string {
    return (string) $value;
}

Вхідне перетворення

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

Вхідні лише користувацькі касти повинні реалізовувати інтерфейс CastsInboundAttributes, який вимагає визначення лише методу set. Команда Artisan make:cast може бути викликана з опцією --inbound для генерації класу касту лише для вхідних даних:

php artisan make:cast AsHash --inbound

Класичним прикладом одностороннього перетворення є перетворення "хешування". Наприклад, ми можемо визначити перетворення, яке хешує вхідні значення за допомогою заданого алгоритму:

<?php
 
namespace App\Casts;
 
use Illuminate\Contracts\Database\Eloquent\CastsInboundAttributes;
use Illuminate\Database\Eloquent\Model;
 
class AsHash implements CastsInboundAttributes
{
    /**
     * Створити новий екземпляр класу перетворення.
     */
    public function __construct(
        protected string|null $algorithm = null,
    ) {}
 
    /**
     * Підготуйте задане значення для зберігання.
     *
     * @param  array<string, mixed>  $attributes
     */
    public function set(
        Model $model,
        string $key,
        mixed $value,
        array $attributes,
    ): string {
        return is_null($this->algorithm)
            ? bcrypt($value)
            : hash($this->algorithm, $value);
    }
}

Кастування параметрів

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

/**
 * Отримати атрибути, які слід привести до типу.
 *
 * @return array<string, string>
 */
protected function casts(): array
{
    return [
        'secret' => AsHash::class.':sha256',
    ];
}

Порівняння Значень Кастів

Якщо ви хочете визначити, як два задані значення касту повинні порівнюватися, щоб визначити, чи були вони змінені, ваш клас касту може реалізувати інтерфейс Illuminate\Contracts\Database\Eloquent\ComparesCastableAttributes. Це дозволяє вам мати детальний контроль над тим, які значення Eloquent вважає зміненими і, таким чином, зберігає в базі даних, коли модель оновлюється.

Цей інтерфейс вказує, що ваш клас повинен містити метод compare, який повинен повертати true, якщо задані значення вважаються рівними:

/**
 * Визначте, чи є дані значення рівними.
 *
 * @param  \Illuminate\Database\Eloquent\Model  $model
 * @param  string  $key
 * @param  mixed  $firstValue
 * @param  mixed  $secondValue
 * @return bool
 */
public function compare(
    Model $model,
    string $key,
    mixed $firstValue,
    mixed $secondValue
): bool {
    return $firstValue === $secondValue;
}

Castables

Ви можете захотіти дозволити об'єктам значень вашого застосунку визначати власні класи кастомного перетворення. Замість того, щоб прикріплювати клас кастомного перетворення до вашої моделі, ви можете прикріпити клас об'єкта значення, який реалізує інтерфейс Illuminate\Contracts\Database\Eloquent\Castable:

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

Об'єкти, що реалізують інтерфейс Castable, повинні визначати метод castUsing, який повертає ім'я класу кастомного класу перетворювача, відповідального за перетворення до і з класу Castable:

<?php
 
namespace App\ValueObjects;
 
use Illuminate\Contracts\Database\Eloquent\Castable;
use App\Casts\AsAddress;
 
class Address implements Castable
{
    /**
     * Отримати ім'я класу перетворювача для використання при перетворенні з / до цієї цільової касти.
     *
     * @param  array<string, mixed>  $arguments
     */
    public static function castUsing(array $arguments): string
    {
        return AsAddress::class;
    }
}

Коли використовуєте класи Castable, ви все ще можете надавати аргументи у визначенні методу casts. Аргументи будуть передані до методу castUsing:

use App\ValueObjects\Address;
 
protected function casts(): array
{
    return [
        'address' => Address::class.':argument',
    ];
}

Кастовані & Анонімні Класи Кастування

Поєднуючи "castables" з анонімними класами PHP, ви можете визначити об'єкт значення та його логіку перетворення як єдиний об'єкт, що підлягає перетворенню. Щоб досягти цього, поверніть анонімний клас з методу castUsing вашого об'єкта значення. Анонімний клас повинен реалізовувати інтерфейс CastsAttributes:

<?php
 
namespace App\ValueObjects;
 
use Illuminate\Contracts\Database\Eloquent\Castable;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
 
class Address implements Castable
{
    // ...
 
    /**
     * Отримати клас перетворювача для використання при перетворенні з / до цієї цільової касти.
     *
     * @param  array<string, mixed>  $arguments
     */
    public static function castUsing(array $arguments): CastsAttributes
    {
        return new class implements CastsAttributes
        {
            public function get(
                Model $model,
                string $key,
                mixed $value,
                array $attributes,
            ): Address {
                return new Address(
                    $attributes['address_line_one'],
                    $attributes['address_line_two']
                );
            }
 
            public function set(
                Model $model,
                string $key,
                mixed $value,
                array $attributes,
            ): array {
                return [
                    'address_line_one' => $value->lineOne,
                    'address_line_two' => $value->lineTwo,
                ];
            }
        };
    }
}