Модели Eloquent задают дефолтные значения атрибутов с помощью свойства  $attributes . Это работает для фиксированных значений, но PHP позволяет использовать в дефолтных значениях свойств только константные выражения, поэтому вы не можете вызвать там  config() ,  now()  или проверить feature-флаг. В Laravel 14 добавлен метод  defaults() , который возвращает тот же массив из кода, выполняющегося при создании модели.

#До и после

Возьмем модель подписки, которая дает новым подпискам 14-дневный пробный период, и 30 дней, когда активен feature-флаг  extended-trials . В  $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  к методу  casts()  (Приведение типов моделей переходит на методы в Laravel 11).  defaults()  дает  $attributes  версию в виде метода.

#Как это работает

Конструктор модели вызывает  defaults()  и объединяет результат с  $attributes . Порядок в конструкторе определяет, какое значение имеет приоритет:

  1. Сначала устанавливаются значения из свойства  $attributes 
  2. Поверх них накладывается  defaults() , поэтому он побеждает, если оба определяют один и тот же ключ
  3. Атрибуты, которые вы передаете в  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, поскольку в существующей модели уже может быть метод с именем  defaults() . В Laravel 14 конструктор вызовет его и объединит все, что он возвращает, с атрибутами. Перед обновлением выполните поиск такого метода в ваших моделях:

grep -rn "function defaults" app/Models
 

Переименуйте любой метод, который не предназначен для возврата дефолтных значений атрибутов.

Jack Bayliss добавил этот метод в пулл-реквесте #61688. Он слит в ветку  master  и может измениться до релиза Laravel 14.

#Что еще почитать