← Back to list

Laravel 13 Rewrites How You Configure Models, Jobs, and Commands.

Laravel now treats PHP Attributes as a core feature. However, you need at least PHP 8.3 to use it. The problem is that almost half of PHP…

Andi | Laravel Developer · 2026-02-23 07:42 · 38 claps · 4.2 min read paywalled
#laravel #web-development #php
Open on Medium ↗
Wiki topics: 🌐 · Web Development 🥊 · Combat Sports

Laravel Stories

Laravel 13 Rewrites How You Configure Models, Jobs, and Commands.

Photo by Mohammad Rahmani on Unsplash

Photo by Mohammad Rahmani on Unsplash

Laravel now treats PHP Attributes as a core feature. However, you need at least PHP 8.3 to use it. The problem is that almost half of PHP users are still on versions below 8.3. So before the new release, you may need to upgrade your PHP version and check if your project is ready.

packagist.org/php-statistics

packagist.org/php-statistics

🎁 For free access, you can click **here**.

Last quarter I was reviewing a pull request from a junior developer on my team.

Five new model files. All written correctly. protected $fillable, protected $hidden, protected $table - textbook Laravel. I approved it without hesitation.

A few weeks later, I found out about PR #58578.

By March 2026, I’ll be asking him to rewrite those files using a pattern that didn’t exist when he submitted them. That’s on me for not giving him better context earlier.

This article is that context for him, and for anyone maintaining a Laravel application right now.

Why Taylor Built This

“Over the last few years more attributes have been added, but we still document properties for various things, leading to an inconsistent state where we use attributes for some things and properties for others.”

What Laravel 13 PHP Attributes Actually Change

The headline feature of Laravel 13 is first-class support for PHP Attributes, offering a cleaner and more modern way to configure models, jobs, commands, and other classes.

Here’s a simple before-and-after comparison using a basic model:

Before (Laravel 12 and earlier):

class User extends Model
{
    protected $table = 'users';
    protected $primaryKey = 'user_id';
    protected $keyType = 'string';
    public $incrementing = false;
    protected $hidden = ['password'];
    protected $fillable = ['name', 'email'];
}

After (Laravel 13) — introduced via PR #58578:

#[Table('users', key: 'user_id', keyType: 'string', incrementing: false)]
#[Hidden(['password'])]
#[Fillable(['name', 'email'])]
class User extends Model {}

Existing protected $fillable syntax remains fully supported. Laravel 13 does not require you to migrate. However, Attributes are now the recommended and idiomatic approach moving forward.

One notable design decision is that PrimaryKey, Incrementing, Timestamps, and KeyType are all managed through the #[Table] attribute instead of separate attributes.

Full list of available model attributes:

  • #[Appends] #[Connection] #[Fillable] #[Guarded]
  • #[Hidden] #[Table] #[Touches] #[Unguarded] #[Visible]

Laravel 13 PHP Attributes for Queue Jobs, Listeners, and Notifications

Models may get most of the attention, but the same pattern now applies across your entire application, and that is where the true scale of this change becomes clear.

Before:

class ProcessPodcast implements ShouldQueue
{
    public $connection = 'redis';
    public $queue = 'podcasts';
    public $tries = 3;
    public $timeout = 120;
}

After:

#[Connection('redis')]
#[Queue('podcasts')]
#[Tries(3)]
#[Timeout(120)]
class ProcessPodcast implements ShouldQueue {}

This applies to jobs, listeners, notifications, mailables, and broadcast events, all covered in a single change.

Here is the complete list of available queue attributes:

  • #[Backoff] #[Connection] #[DeleteWhenMissingModels]
  • #[FailOnTimeout] #[MaxExceptions] #[Queue]
  • #[Timeout] #[Tries] #[UniqueFor]

In a typical mid-size Laravel application with 15 jobs, 10 listeners, and 12 notifications, that means 37 or more classes where the configuration pattern has changed. Not immediately urgent, but definitely something that will need attention.

Console Commands, Form Requests, and Beyond

The attribute system covers more ground than most articles mention. Here’s the complete picture from the PR:

Console Commands:

#[Signature('mail:send {user} {--queue}')]
#[Description('Send a marketing email to a user')]
class SendMailCommand extends Command {}

Form Requests:

#[RedirectTo('/posts/create')]
#[StopOnFirstFailure]
class StorePostRequest extends FormRequest {}

Test Seeders:

#[Seeder(OrderSeeder::class)]
class ExampleTest extends TestCase
{
    use RefreshDatabase;

    public function test_example(): void
    {
        // database seeded automatically via attribute
    }
}

Factories:

#[UseModel(User::class)]
class UserFactory extends Factory
{
    public function definition(): array
    {
        return ['name' => $this->faker->name()];
    }
}

API Resources:

#[Collects(UserResource::class)]
class UserCollection extends ResourceCollection {}

#[PreserveKeys]
class UserResource extends JsonResource
{
    public function toArray(Request $request): array
    {
        return ['id' => $this->id, 'name' => $this->name];
    }
}

“Does this hurt performance?”

The reflection cost is negligible because attributes are instantiated only once per class during a request.

Taylor also introduced a dedicated optimization to cache heavy attribute access during the PR process, so the framework already accounts for potential performance concerns.

At worst, there may be a very slight slowdown compared to using raw properties, but it is so small that it would not be noticeable in a real production environment.

“Do attributes work with inheritance?”

If a parent class defined $guarded = ['id'] and a child class added #[Guarded(['id', 'name'])], the result was incorrect. The child class ended up with ['*'], which means all fields were guarded, instead of just id and name.

That was not the intended behavior.

The issue was later fixed in the same PR. Now, child classes properly inherit and combine attribute configurations from their parent classes, so inheritance works as expected.

The PHP 8.3 Upgrade

Laravel 13 requires PHP 8.3 as the minimum version, up from PHP 8.2 in Laravel 12.

This has practical consequences:

  • Shared hosting that hasn’t moved past 8.2 cannot run Laravel 13 at all
  • Docker base images pinned to php:8.2-fpm need to be updated before upgrading
  • Enterprise environments with quarterly OS patching cycles may not have 8.3 available in time
  • Client projects on managed stacks — check before they find out at deployment

The JetBrains State of PHP 2025 report shows that although 89% of developers are using PHP 8 overall, many are still spread across different minor versions like 8.0, 8.1, and 8.2. Because Laravel 13 requires at least PHP 8.3, a noticeable portion of current installations will not be able to upgrade without updating their PHP version first.

Are you migrating to PHP Attributes from day one, or waiting until it’s the clear community standard? And is your stack actually ready for the PHP 8.3 requirement? Drop it in the comments — I’m particularly curious how many teams are still below 8.3 in production.

If this was useful, I write about Laravel, PHP architecture, and practical backend patterns. Follow to catch the next one.

References


메타데이터
post_id
de69705bb045
slug
laravel-13-rewrites-how-you-configure-models-jobs-and-commands-de69705bb045
url
https://medium.com/@andipyk/laravel-13-rewrites-how-you-configure-models-jobs-and-commands-de69705bb045
canonical_url
https://medium.com/@andipyk/laravel-13-rewrites-how-you-configure-models-jobs-and-commands-de69705bb045
author_url
https://medium.com/@andipyk
status
ok
fetched_at
2026-07-13 06:23:13