22. Make Laravel explicit. Enabled this in laravel. Laravel prevent N+1. N plus one testing.

Video version
(Leave your feedback on YouTube)

N+1

The N+1 problem is relevant for all ORMs. It arises very simply: every time the code needs access to a related record, a new SQL query is executed to retrieve that record. This is a feature of ORMs called Lazy loading. The opposite of lazy loading is Eager Loading, which should always be used.

You can globally block lazy loading in Laravel with the following command:

app/Providers/AppServiceProvider.php
Model::preventLazyLoading();

After activation, if you attempt to access an unloaded Eloquent relationship, you will get an error instead of silent execution.

Important! This command does not completely solve the N+1 problem. Developers can still make mistakes.

The only automated solution for combating N+1 is using automated tests.

Any Laravel test
$this->expectsDatabaseQueryCount($expectedQueryCount);
Important! This method counts the number of database connections, not queries. Therefore, wrapping the method in a transaction will always result in 1 query. Be careful.

Accessing Unloaded Attributes

By default, Eloquent returns null when trying to access an attribute that you did not load. This happens in cases where you do not use Select *. This can cause many implicit errors, from simple boolean variable checks to situations where null is returned to the client instead of data that actually exists.

You can globally block this behavior with the following command:

app/Providers/AppServiceProvider.php
Model::preventAccessingMissingAttributes();

Unobvious Mass Assignment in Eloquent

When creating an Eloquent model, it is convenient to use mass assignment via the fill or create functions. To ensure the security of such filling, the model class specifies which fields can be mass-assigned and which cannot, using the fillable or guarded properties.

All properties that are not specified in fillable or are specified in guarded will not be saved to the model when you attempt to save them. This is unobvious behavior that should be avoided.

app/Providers/AppServiceProvider.php
Model::preventSilentlyDiscardingAttributes();

Now, when you try to assign a prohibited property, you will get an error. Typically, such fields come from unvalidated requests.

Never use $request->all(). Only use $request->validated() with a description of all properties.

Eloquent Strict Mode in Production

You can enable all three commands with one:

app/Providers/AppServiceProvider.php
public function boot(): void
{
    Model::shouldBeStrict();
}

A boolean parameter can be used to enable or disable these rules. If you are supporting an existing project, do not enable this mode for production! Users should not encounter errors that are likely to occur. Locally, developers and QA on staging can help catch such errors.

If you are developing a project from scratch, feel free to enable it globally.

HTTP Requests in Tests

Most likely, your solution will have HTTP requests to external services or microservices. Not all of them require authorization but may have limits on the number of requests and can ban for DDOS. Your tests might make these requests multiple times.

Therefore, I recommend globally disabling real HTTP requests for all tests:

tests/TestCase.php
protected function setUp(): void
{
    parent::setUp();

    Http::preventStrayRequests();
}

All requests to external services should be mocked:

Some test
Http::fake([
    'http://ip-api.com/json/*' => Http::response([
        'status' => 'success',
        'continent' => 'Europe',
        'continentCode' => 'EU',
        'country' => 'Ukraine',
        'countryCode' => 'UA',
        'regionName' => 'Sumy',
        'city' => 'Sumy',
        'district' => '',
        'zip' => '40009',
    ]),
]);