Laravel

Laravel Gotchas

Harry 4 min read
Laravel Gotchas Image
Photo by Ivan Rudoy / Unsplash
Table of Contents

When working with Laravel for the majority of your working day. You tend to come across a gotcha or two that either trips yourself or your fellow colleagues up from time to time.

I've put together a list of the Laravel gotchas that I have encountered and some of these have either tripped me up once or even twice!

Gotcha! Query Chunking

Let's run with the idea that we have initialised a new email column on the users table and we want to initialise each user's email to {user-id}@example.com.

We can do that with the following query:

User::query()
    ->whereNull('email')
    ->each(function (User $user) {
        dump($user->id);
        $user->update(['email' => "$user->[email protected]"]);
    }, 10);

In theory - we should be setting every single user in our database that has a null email address with the new placeholder email. But there's a problem here.

There's a bunch of data that hasn't actually been touched at all. The reason for this is because chunk will call a database query for a bulk of rows at a time using LIMIT/OFFSET.

Since we are manipulating the data we are performing a conditional query on, we really just want to get the first 10 rows every time since the list is shrinking.

A little less elegant of a solution but a do/while loop solves this problem.

do {
    $users = User::query()
        ->whereNull('email')
        ->limit(10)
        ->get();

    foreach ($users as $user) {
        dump($user->id);
        $user->update(['email' => "$user->[email protected]"]);
    }
} while ($users->count());

Gotcha! Array::get

This is one that has caught me out a few times. Take the following array and run it through Arr::get / array_get with null specified as the key parameter. What do you expect to receive?

$animals = [
    'dog' => 'woof',
    'cat' => 'meow',
];

Arr::get($animals, null, 'some default value');

Arr::get above will return the full array of $animals, ignoring the default return value that you may otherwise expect to receive.

Gotcha! Caching

Consider the following code snippet:

return Cache::rememberForever('user', function () {
    return User::query()
        ->where('name', 'Bobettello')
        ->first();
});

The idea is we're caching the user 'Bobettello' to save us from performing a database query every time we want this user. In practice, we would be caching more expensive operations here but to highlight an issue it's been kept to the minimum.

What do you expect to happen when there's no user found? It returns null in this particular scenario. Fine - okay. But it's cached so we don't have to worry about calling our super expensive query again - or do we?

Returning null here doesn't actually cause the cache to well, cache. The next time this code runs it'll be running the super expensive extreme query again. This is also known as a cache miss.

The solution? Ensure null is never returned if you expect the cache to stick.

Gotcha! Env/Config Caching

In this example, I've gone ahead and set up a variable in my .env file for:

MY_ENV_VAR = "Hello!"

Then thrown into a controller to return this value on my index page like so:

public function index()
{
    return env('MY_ENV_VAR', "WHERE MY VAR?!");
}

And I navigate to this page in my browser and behold: "Hello!"

Hello! image

Right, so we're going to production next and we're running all the optimisation things that we do. Including php artisan config:cache

We deploy annnnnd... Eh?

Config has been cached, ignoring env

When the config file is cached in Laravel, the env helper no longer loads the .env file. To otherwise handle this it's just not a very good idea to be using env() at all. Define all your configuration variables within a config file and access them using config() instead.

public function index()
{
    return config('my_config.my_var', "WHERE MY VAR?!");
}

The Laravel documentation explains this issue as well and suggests only calling env() from within your config files.

Gotcha! Route Model Binding

I have a route for FormSubmissionController@show where within the controller I want to load in FormSubmission model by its id and return its data, or something like that.

In my routes file:

Route::get('/form-submission/{formSubmission}', [FormSubmissionController::class, 'show']);

Next, is the controller. Binding the FormSubmission parameter and returning its data as an array:

public function show(FormSubmission $submission)
{
    return $submission->toArray();
}

Then nothing happens...

Form submission route

Let's dd the $submission to see what's happening:

Form submission route with placeholder model

It appears we are receiving a FormSubmission model as expected, but... Why is it empty?

Further debugging tells me that the model does not exist, yet it's given me a model.

dd($submission->exists); // returns false

The issue is all down to how we've named the variable in both the route and controller. In reality, if the model cannot be found we expect Laravel to give us a 404 error but this assumes the model is bound correctly.

To resolve this, make sure the variable name in the method signature matches the bound name in the route. {formSubmission} => $formSubmission

Route::get('/form-submission/{formSubmission}', [FormSubmissionController::class, 'show']);
public function show(FormSubmission $formSubmission)
{
    return $formSubmission->toArray();
}

All good now!

Form submission with correctly binded model

Conclusion

This concludes the list of Laravel gotchas that I can recall for the time being.

If you have any of your own Laravel gotchas that have tripped you up. Feel free to reply in the comments with your own!

Share
Comments
More from Harrk Blog

Harrk Blog

Programming nonsense, tutorials, and random musings about games and technology.

Great! You’ve successfully signed up.

Welcome back! You've successfully signed in.

You've successfully subscribed to Harrk Blog.

Success! Check your email for magic link to sign-in.

Success! Your billing info has been updated.

Your billing was not updated.