Understand Laravel Magic: How Eloquent Models Dynamically Retrieves Attributes

I’ve been working with Laravel for almost eight years, and I think I know Eloquent pretty well. But for a long time, I didn’t fully understand what happens under the hood when accessing properties on a model instance—and I’m sure I’m not the only Laravel developer who felt that way.

Have you ever wondered how Laravel returns the correct value when calling $user->email, even though you never explicitly defined a public $email property on your Eloquent model? Or how Laravel lazy-loads the posts relationship when you call $user->posts, despite there being no posts property on your model?

In this post, I’ll explain how it dynamically retrieves attributes on models and how it leverages PHP’s magic methods to make it all work seamlessly.

PHP Overloading and the __get Magic Method

Eloquent models rely on PHP’s overloading capabilities, specifically the __get magic method. This allows to intercept property access and execute custom logic when a property isn’t explicitly defined.

When you call $user->email, if the property doesn’t exist, PHP invokes the __get magic method. In the case of Laravel, the __get method is defined on the base Model class. Laravel takes advantage of this to dynamically retrieve the correct value. The diagram below illustrates this flow:

Here, “none of the above” means that you’re trying to fetch something that does not exist. In this case, an exception will be thrown or null will be returned.

In Laravel 9.35.0, a new Model::shouldBeStrict method was introduced which adds a “strict” mode to Eloquent models. This mode, among other things, will prevent access to non-existent attributes by throwing an exception instead of returning null. If you’re not familiar with it, I suggest that you watch this free Laracasts video.

Posted in Tutorial | Leave a comment

Laravel’s PHP attributes

Happy New Year!

Lately I’ve been quite excited by Laravel’s PHP attributes.

Attributes offer the ability to add structured, machine-readable metadata information on declarations in code: Classes, methods, functions, parameters, properties and class constants can be the target of an attribute.

Source: https://www.php.net/manual/en/language.attributes.overview.php

Laravel offers some attributes out-of-the-box and I found a very good article on X that describes them all.

My favourite one (and the one I use the most) is #[Config(...)]

Before, you would have to initialize the property yourself:

<?php

class Service {
    private string $apiKey;

    public function __construct()
    {
        $this->apiKey = config('services.api_key');
    }
}

Now, you can use the Config attribute to inject the configuration value automatically and leverage PHP’s constructor property promotion at the same time.

<?php

use Illuminate\Container\Attributes\Config;

class Service {
    public function __construct(
        #[Config('services.api_key')] private string $apiKey,
    )
    {
        //
    }
}

Feel free to take a look at this blog post for a more in-depth look at all attributes Laravel (and Livewire) has to offer.

Posted in Tutorial | Leave a comment

Preventing Transaction-Related Issues in Laravel

Today, I spent a good amount of hours troubleshooting an issue involving database transactions. Thankfully, with the assistance of a teammate, the problem is now resolved.

Consider the following example code:

<?php

DB::transaction(function () {
    $user = User::factory()->create();
    dump($user->exists) // true
    dump($user->id) // displays the id as if the record was actually persisted in the DB

    // Imagine some HTTP request to an external webapp which shares the same database
    DB::table('users')->where('id', $user->id)->count() // 0
});

In this scenario, we initiate a database transaction in which a model is created. Subsequently, a call is made to another service that shares the same database, and this service attempts to query the database for the newly created user. However, the service will be unable to locate the database record.

The underlying reason is that within a transaction, the objects are not committed to the database until the transaction is completed. Therefore, although the user ID may be correctly generated and displayed, the record itself is not yet persisted in the database.

While this concept is fundamental and I am well-acquainted with how database transactions operate, I nonetheless encountered this issue. Documenting this experience will hopefully prevent me from making the same mistake in the future and maybe help others too.

Posted in Uncategorized | Leave a comment

How I test Laravel Middlewares in isolation

Not so long ago, a colleague of mine showed me a method to test middlewares that totally changed how I test them now.

For a long time, I tested middlewares by creating a fake Laravel request, instantiating the middleware, and calling the `handle method with my fake $request object. This approach was working but caused some problems, such as handling an authenticated user or adding POST data. This required a deep understanding of Laravel requests, which is beneficial but added complexity to writing tests.

Luckily for us, there’s an easier way to do it. The method consists of registering a fake route and attaching a middleware to it in the test itself.

<?php

#[Test]
public function non_admin_gets_a_forbidden_response(): void
{
    $user = User::factory()->create(['is_admin' => false]);

    Route::get('/foo', fn(): Response => response('OK'))
        ->middleware(AuthorizeAdmin::class);

    $this->actingAs($user)
        ->get('/foo')
        ->assertForbidden();
}

This way, we are still testing the middleware in isolation, and we get to use Laravel’s built-in get method instead of calling the handle method on the middleware ourselves.

Posted in Tutorial | Leave a comment

Welcome to vbergeron.dev!

Welcome to my personal website. Creating a personal website has been on my mind for ages, but I always procrastinated.

On this website, you can expect some blog posts that I’ll write as I come across interesting things while developing software at work. Hopefully, I’ll have some great things to share soon.

Posted in Uncategorized | Leave a comment