Подписывайтесь на наш Telegram канал и будьте в курсе всех событий.
Будьте в курсе последних новостей!
Будьте в курсе последних новостей!

Eloquent · Мутаторы и типизация

Введение

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

Аксессоры и мутаторы (Accessors and Mutators)

Определение аксессора (Accessor)

Аксессор преобразует значение атрибута экземпляра Eloquent при обращении к нему. Чтобы определить аксессор, создайте protected метод в вашей модели, представляющий соответствующий атрибут. Имя этого метода должно соответствовать “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 вашей модели, вам нужно будет добавить их.

Создание объектов-значений из нескольких атрибутов

Иногда вашему аксессору может потребоваться преобразовать несколько атрибутов модели в один “объект-значение” (value object). Для этого ваше замыкание 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();
}

Определение мутатора (Mutator)

Мутатор преобразует значение атрибута в момент их присвоения экземпляру 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
  • AsStringable::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, не будут преобразованы. Кроме того, вы никогда не должны определять типизацию (или атрибут), имя которого совпадает с именем отношения.

Преобразование в строку

Вы можете использовать класс приведения 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 этому атрибуту автоматически десериализует атрибут модели Eloquent в массив PHP при обращении к нему:

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

Типизация ArrayObject и Collection

Хотя типизации стандартного 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 Collection:

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

Типизация даты

По умолчанию Eloquent преобразует столбцы created_at и updated_at в экземпляры Carbon, расширяющего класс DateTime PHP и предоставляющего набор полезных методов. Вы можете типизировать дополнительные атрибуты даты, определив дополнительные преобразования даты в методе вашей модели cast. Обычно даты следует приводить с использованием типизации 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. Для этого вы можете указать атрибут и перечисление, которое вы хотите преобразовать, в методе вашей моделиcasts:

use App\Enums\ServerStatus;

/**
 * Получение атрибутов, которые должны быть типизированы.
 *
 * @return array<string, string>
 */
protected function casts(): array
{
    return [
        'status' => ServerStatus::class,
    ];
}

После того как вы определили приведение в своей модели, указанный атрибут будет автоматически преобразован в перечисление и из него, когда вы взаимодействуете с атрибутом:

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

    $server->save();
}

Типизация массивов перечислений

Иногда вам может потребоваться, чтобы ваша модель сохраняла массив значений перечисления в одном столбце. Для этого вы можете воспользоваться приведением 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"

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

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

Ротация ключей

Типизация во время запроса

Иногда может потребоваться применить типизацию при выполнении запроса, например, при выборе сырого значения из таблицы. Например, рассмотрим следующий запрос:

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:

Все пользовательские классы приведения должны реализовывать интерфейс CastsAttributes. Классы, реализующие этот интерфейс, должны определять методы get и set. Метод get отвечает за преобразование “сырого” значения из базы данных к типизированному значению, а метод set – должен преобразовывать типизированное значение в “сырое” значение, которое можно сохранить в базе данных. В качестве примера мы повторно реализуем встроенный преобразователь json как пользовательский типизатор:

<?php

namespace App\Casts;

use Illuminate\Contracts\Database\Eloquent\CastsAttributes;

class Json 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\Json;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * Получение атрибутов, которые должны быть типизированы.
     *
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'options' => Json::class,
        ];
    }
}

Типизация объект-значение

Вы не ограничены приведением значений к примитивным типам. Вы также можете преобразовать значения к объектам. Определение пользовательских типизаторов, которые преобразуют значения в объекты, очень похоже на приведение к примитивным типам; однако метод set должен возвращать массив пар ключ / значение, который будет использоваться для установки сырых значений, сохраняемых в модели.

В качестве примера мы определим собственный класс типизатора, который преобразует несколько значений модели в один объект-значение Address. Предположим, что значение Address имеет два общедоступных свойства: lineOne и lineTwo:

<?php

namespace App\Casts;

use App\ValueObjects\Address as AddressValueObject;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;
use InvalidArgumentException;

class Address implements CastsAttributes
{
    /**
     * Преобразовать значение к пользовательскому типу.
     *
     * @param array<string, mixed> $attributes
     */
    public function get(Model $model, string $key, mixed $value, array $attributes): AddressValueObject
    {
        return new AddressValueObject(
            $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 AddressValueObject) {
            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 кэширует их. Таким образом, при повторном доступе к атрибуту будет возвращен тот же самый экземпляр объекта.

Если вы хотите отключить поведение кэширования объектов в вашем пользовательском классе приведения, объявите public свойство withoutObjectCaching в вашем пользовательском классе приведения:

class Address 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 Hash --inbound

Классическим примером только входящей типизации является «хеширование». Например, мы можем определить типизатор, которое хеширует входящие значения с использованием указанного алгоритма:

<?php

namespace App\Casts;

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

class Hash 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' => Hash::class.':sha256',
    ];
}

Интерфейс Castable

Вы можете разрешить объектам-значениям вашего приложения определять свои собственные классы типизаторы. Вместо указания пользовательской типизации в модели, вы можете альтернативно указать класс, который реализует интерфейс Illuminate\Contracts\Database\Eloquent\Castable:

use App\ValueObjects\Address;

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

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

<?php

namespace App\ValueObjects;

use Illuminate\Contracts\Database\Eloquent\Castable;
use App\Casts\Address as AddressCast;

class Address implements Castable
{
    /**
     * Получить имя класса типизатора для использования двустороннего преобразования.
     *
     * @param array<string, mixed> $arguments
     */
    public static function castUsing(array $arguments): string
    {
        return AddressCast::class;
    }
}

При использовании классов Castable вы все равно можете указывать аргументы в методе casts. Аргументы будут переданы методу castUsing:

use App\ValueObjects\Address;

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

Интерфейс Castable и анонимные классы типизаторов

Комбинируя castable и анонимными классами 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,
                ];
            }
        };
    }
}