Моделі Eloquent задають значення атрибутів за замовчуванням через властивість $attributes. Це добре працює для фіксованих значень, але PHP дозволяє лише константні вирази у значенні властивості за замовчуванням, тому там не можна викликати config(), now() чи перевіряти feature flag. Laravel 14 додає метод defaults(), який повертає такий самий масив, але з коду, що виконується під час створення моделі.
Було і стало
Уявімо модель підписки, яка дає новим підпискам 14 днів пробного періоду, а при активному feature flag extended-trials - 30 днів. З $attributes прапорець доводиться перевіряти в конструкторі:
class Subscription extends Model
{
protected $attributes = [
'cancelled' => false,
'trial_days' => 14,
];
public function __construct(array $attributes = [])
{
if (Feature::active('extended-trials')) {
$this->attributes['trial_days'] = 30;
}
parent::__construct($attributes);
}
}
У Laravel 14 ту саму логіку можна винести в defaults():
class Subscription extends Model
{
protected function defaults(): array
{
return [
'cancelled' => false,
'trial_days' => Feature::active('extended-trials') ? 30 : 14,
'expires_at' => now()->addDays(config('billing.grace_period'))->toDateTimeString(),
];
}
}
$subscription = new Subscription;
$subscription->trial_days; // 30 when the flag is active, 14 otherwise
У Laravel 11 з касами зробили схожий крок: скелет застосунку перейшов від властивості $casts до методу casts() (Model Casts are moving to methods in Laravel 11). Тепер defaults() дає $attributes такий самий методний варіант.
Як це працює
Конструктор моделі викликає defaults() і зливає результат у $attributes. Порядок у конструкторі визначає, яке значення матиме пріоритет:
- Спершу встановлюються значення з властивості
$attributes.
- Поверх них зливається результат
defaults(), тому при збігу ключів він перемагає.
- Атрибути, передані в
new, create() або fill(), застосовуються останніми й перемагають обидва попередні джерела.
$subscription = Subscription::create(['trial_days' => 60]);
$subscription->trial_days; // 60
Дефолти зливаються до того, як модель синхронізує оригінальні атрибути, тому вони не вважаються «брудними» (dirty) змінами. Водночас вони записуються в базу даних при вставці моделі - так само, як і $attributes.
Сирі значення
Як і $attributes, значення з defaults() є сирими: вони не проходять через касти чи мутатори. Повертати потрібно те значення, яке буде збережене в базі даних:
protected function defaults(): array
{
return [
// A column cast to `array` expects a JSON string
'settings' => json_encode(['theme' => 'light']),
// A date column
'expires_at' => now()->addDays(30)->toDateTimeString(),
];
}
Якщо повернути PHP-масив для колонки з кастом array або json, помилка виникне під час читання атрибута, адже каст очікує JSON-рядок.
Коли виконується defaults()
Метод defaults() викликається щоразу при створенні екземпляра моделі, зокрема й для моделей, завантажених із бази даних. Eloquent будує кожен отриманий рядок через новий екземпляр моделі, тож defaults() виконується один раз на рядок, після чого значення з рядка замінюють дефолти.
Оновлення до Laravel 14
Метод з'являється саме в Laravel 14, бо в наявній моделі вже може бути метод із назвою defaults(). У Laravel 14 конструктор викликатиме його та зливатиме все, що він поверне, в атрибути. Перед оновленням варто пошукати такі методи у своїх моделях:
grep -rn "function defaults" app/Models
Будь-який метод, який не призначений повертати значення атрибутів за замовчуванням, слід перейменувати.
Автор і статус
Метод реалізував Jack Bayliss у pull request #61688. Зміни злито в гілку master, але до релізу Laravel 14 вони ще можуть змінитися.
Додаткове читання
Джерело: Laravel News.