Laravel 13.35 New Published on

Computed default values with defaults()

The HasDefaultAttributes trait gives the model a defaults() method: default values that can read the configuration or the clock, as soon as new Abonnement runs.

Topics covered: Eloquent

New in Laravel 13.35. An Eloquent model declares its default values in the $attributes property. But a PHP property only accepts constants: you cannot write now()->addDays(14) or config('abonnements.default_plan') there. We fell back on a creating listener, which only runs when the model is saved: until then, the model does not know the value.

Before

protected $attributes = ['plan' => 'starter'];

protected static function booted(): void
{
    static::creating(fn (Abonnement $subscription) => $subscription->trial_ends_at ??= now()->addDays(14));
}

Since Laravel 13.35

The HasDefaultAttributes trait gives the model a defaults() method, called on every new:

use Illuminate\Database\Eloquent\Concerns\HasDefaultAttributes;

class Abonnement extends Model
{
    use HasDefaultAttributes;

    protected function defaults(): array
    {
        return [
            'plan' => config('abonnements.default_plan'),
            'trial_ends_at' => now()->addDays(14),
        ];
    }
}

The values are set by the constructor, before fill(): the ones passed to new Abonnement([...]) or to create() replace them. They are also set as is, without going through mutators.

Your task

GET /api/subscriptions/preview shows customers the subscription they are about to sign up for, before anything is saved. They see no end of trial, and the suggested plan ignores config/abonnements.php. The requests in the "Requests" tab show it.

Move the default values of the Abonnement model into defaults(): the plan and the trial length come from the configuration, and cancelled is false. The creating listener has no reason to exist anymore.

Where the feature comes from: the original pull request.