Продвинутый model attribute casting в Laravel

Pavel Buchnev
3 min readJul 3, 2020

--

По долгу службы приходится знать про каждый винтик в Laravel, но бывают моменты, когда появляется новый проект и ты в него погружаешься с головой, а через пол года — год узнаешь, что ты сильно отстал от жизни, в Laravel вышло 10 релизов и ты пропустил все самое интересное. Пришлось даже начать следить за их репо, чтобы получать уведомления о всех релизах и вдумчиво вчитываться в то, что они там прикрутили. И вот только сегодня я решил рассказать об одной из фич, которая мне очень понравилась и которую я не могу пройти стороной, а именно возможность продвинутого кастинга атрибутов модели.

Раньше мы могли кастить только в этот набор типов

$primitiveCastTypes = [
'array',
'bool',
'boolean',
'collection',
'custom_datetime',
'date',
'datetime',
'decimal',
'double',
'float',
'int',
'integer',
'json',
'object',
'real',
'string',
'timestamp',
]

а для чего-то большего приходилось использовать мутаторы, что заставляло использовать магию прям в модели и иметь методы с непонятным названием, которые мозолили глаз. Сейчас же все изменилось!

Итак, начнём

Представим, что у нас интернет магазин, а в любом интернет магазине есть товары, которые имеют стоимость, в нашем случае в различной валюте.

Создадим миграцию

Schema::create('product', function (Blueprint $table) {
...
$table->decimal('amount');
$table->string('currency', 3);
...
});

Мы имеет два поля в модели, одно хранит стоимость, второе хранит код валюты. Т.е. у нас два атрибута в модели и оперировать ими уже не удобно. Первое что мы можем сделать, это подключить библиотеку, например Brick\Money

Ну и пример создания объекта из документации

use Brick\Money\Money;

$money = Money::of(50, 'USD');

Старый способ работы с ними выглядеть как то так

Использовать можно, но модели пухнут. Теперь рассмотрим один из новых вариантов. Для начала создадим новый вспомогательный класс MoneyCast , который должен реализовывать интерфейс Illuminate\Contracts\Database\Eloquent\CastsAttributes

И укажем что атрибут money должен обрабатываться этим классом в момент получения и присвоения значения

protected $casts = [
'money' => MoneyCast::class,
];

Этот вариант очень хорошо подходит в том, случае, когда мы хотим атрибуты делать объектами-значениями (ValueObjects).

Новая ситуация

В нашем интернет-магазине есть клиенты и у каждого клиента есть адрес

Вот для него схема

Schema::create('client', function (Blueprint $table) {
...
$table->string('city');
$table->string('street');
...
});

и ValueObject

class Address 
{
...
public function __construct($city, $street, ...) {
$this->city = $city;
$this->street = $street;
}
}

И, с этим ValueObject мы уже не можем использовать тот интерфейс, т.к. мы должны указать в модели, что кастинг атрибута address должен производиться этим VO, а в нем есть конструктор, который ожидает передачу города, улицы, ….

protected $casts = [
'address' => Address::class,
];

По мне, так еще один недостаток в том, чтобы использовать CastsAttributes в своих VO — это возможный конфликт названий методов get и set, которые нужно будет втащить вместе с интерфейсом. На этот случай разработчики предусмотрели план действий, а именно интерфейс Illuminate\Contracts\Database\Eloquent\Castable

И напоследок расскажу еще про один интерфейс Illuminate\Contracts\Database\Eloquent\CastsInboundAttributes , который работает только в одну сторону, а именно только в момент присвоения и, соответственно содержит он только метод set

Вспомним про наш интернет магазин. У нашего товара, помимо стоимости есть также slug, который должен сгенерироваться при изменении title

protected $casts = [
'title' => SlugifyTitleCast::class,
];

А теперь немного магии. В некоторых случая мы хотим изменить разделитель на _ , нет проблем, мы можем это сделать просто указав через двоеточие значение, которое будет передано в виде параметра в конструктор.

protected $casts = [
'title' => SlugifyTitleCast::class.':_',
];

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

P.S. Как говорится, не забывайте подписываться, ставить лайки и оставлять комментарии под видео.

Мои статьи по Laravel

--

--

Pavel Buchnev
Pavel Buchnev

Written by Pavel Buchnev

Senior PHP Developer | Contributor to Spiral Framework 🚀 | Enthusiast of RoadRunner & long-running applications | Creator of Buggregator

Responses (1)