22. Робимо Laravel очевиднішим. Це треба увімкнути в laravel. Проблема N+1, тести н плюс один.

Відео версія
(YouTube - для зворотнього зв'язку)

N+1

Проблема N+1 актуальна для всіх ORM. Виникає вона дуже просто: кожного разу, коли коду потрібен доступ до пов'язаного запису, виконується новий SQL запит для отримання цього запису. Це є властивістю ORM, що називається Lazy loading. Протилежністю лінивого завантаження є Eager Loading, яке слід використовувати завжди.

Заблокувати ліниве завантаження в Laravel глобально дозволяє команда:

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

Після активації, при спробі отримати незавантажену Eloquent залежність, ви отримаєте помилку замість мовчазного виконання.

Важливо! Ця команда не вирішує проблему N+1 повністю. У розробника залишається безліч варіантів зробити помилку.

Єдине автоматизоване рішення для боротьби з N+1 - автотести.

Any Laravel test
$this->expectsDatabaseQueryCount($expectedQueryCount);
Важливо! Метод рахує кількість підключень до БД, а не запитів. Тому обгортання в транзакцію методу завжди дасть 1 запит. Будьте обережні.

Доступ до незавантажених атрибутів

За замовчуванням, Eloquent повертає null при спробі отримати атрибут, який ви не завантажили. Це відбувається у випадках, коли ви не використовуєте Select *. Це може спричинити купу неявних помилок, від звичайної перевірки булевої змінної до ситуацій з віддачею клієнту null замість даних, які реально існують.

Таку поведінку можна глобально заблокувати командою:

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

Неочевидне масове призначення в Eloquent

При створенні Eloquent моделі зручно використовувати масове призначення за допомогою функцій fill або create. Для безпеки такого заповнення клас моделі вказує, які поля можна масово заповнювати, а які ні, за допомогою властивостей fillable або guarded.

Всі властивості, що не вказані у fillable або вказані в guarded, просто не збережуться в моделі при спробі їх зберегти. Це неочевидна поведінка, якої треба уникати.

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

Тепер при спробі призначити недозволену властивість ви отримаєте помилку. Зазвичай такі поля надходять від непровалідованих запитів.

Ніколи не використовуйте $request->all(). Використовйте тільки $request->validated() з описом всіх властивостей.

Eloquent strict mode в продакшені

Всі три команди можна увімкнути однією:

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

Булевим параметром можна вмикати та вимикати ці правила. Якщо ви підтримуєте вже існуючий проєкт, не вмикайте цей режим для продакшену! Користувачі не повинні отримувати помилки, які з великою вірогідністю будуть виникати. Локально розробник та QA на стейджі допоможуть відловити такі помилки.

Якщо розробляєте проєкт з нуля - сміливо вмикайте глобально.

HTTP запити в тестах

Скоріш за все, ваше рішення матиме HTTP запити до зовнішніх сервісів або до мікросервісів. Не всі вони вимагають авторизації, але можуть мати обмеження на кількість запитів і банити за DDOS. Ваші ж тести можуть викликати такі запити безліч разів.

Тому раджу глобально заборонити реальні HTTP запити для всіх тестів:

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

    Http::preventStrayRequests();
}

Всі ж запити до зовнішніх сервісів треба мокати:

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',
    ]),
]);