Laravel: Рекомендации на 2022 год. Полное руководство

Изучение фреймворка может быть ошеломляющим. Время и практика сделают вас мастером. Вот несколько рекомендаций, которые помогут вам в достижении цели.

Для большинства Laravel проектов рекомендации можно свести к двум пунктам:

Но почему вы должны применять эти рекомендации? Во-первых, напомню, что Laravel — самый используемый PHP-фреймворк в мире. Поэтому, много вакансий разработчиков на Laravel. Чтобы вас наняли не нужно быть лучшим, но вы должны показать, что не будете тратить время и деньги работодателя на написание бесполезного и неэффективного кода.

Кроме того, следуя рекомендациям Laravel, вы сможете более эффективно сотрудничать с другими разработчиками. Теперь давайте посмотрим, как вы можете улучшить любой проект.

Общие рекомендации

Держите Laravel в актуальном состоянии

Поддержание Laravel в актуальном состоянии даёт следующие преимущества:

Если вас пугают обновления Laravel, это потому, что ваша кодовая база не тестируется. Читайте дальше, что бы узнать больше об автоматическом тестировании.

Держите пакеты в актуальном состоянии

Доступ к тысячам пакетов сообщества облегчает нашу работу. Но чем больше пакетов вы используете, тем больше количеству уязвимостей вы можете подвергнуть свой проект. Регулярные запуски composer update имеют большое значение для безопасности кодовой базы.

Тестируйте свой проект

Написание автоматических тестов — обширная и малоизвестная тема среди разработчиков. Но знаете ли вы, что это единственный способ обеспечить надёжность? В чём преимущества хорошего набора тестов?

Laracast предлагает бесплатные курсы тестирования, которые помогут вам начать работу. Один с PHPUnit — отраслевым стандартом, и ещё один с Pest — лучшей средой тестирования на этой планете, которая модернизирует и упрощает тестирование в PHP.

Придерживайтесь структуры каталогов по умолчанию

Знаете ли вы почему используете фреймворк?

  1. Он обрамляет (frame) вашу работу (work) рядом рекомендаций, которым вы можете следовать, чтобы каждый член вашей команды был на одной волне.
  2. Он предоставляет множество сложных, утомительных и проверенных в боевых условиях функций бесплатно, поэтому вы можете сосредоточиться на кодировании того, что специфично для вашего проекта.

Итак, почему вы должны придерживаться стандартной структуры Laravel проекта?

  1. Удобство. Способ работы Laravel по умолчанию задокументирован. Когда вы вернётесь к проекту через несколько недель или месяцев, вы поблагодарите себя в прошлом за это.
  2. Значительно проще работать с коллегами по команде. Они знают Laravel так же, как и вы. Используйте эти общие знания, чтобы помочь проекту двигаться вперёд, вместо того, чтобы каждый раз изобретать велосипед.

Когда не следует придерживаться значений по умолчанию? Когда размер вашего проекта на самом деле требует сделать что-то по другому. Самая популярная структура пользовательского проекта Laravel — предметно-ориентированная архитектура (domain-driven architecture)

Рекомендации по Контроллерам

Используйте пользовательский Запрос Формы (Form Request)

Основные причины использования Запроса Формы:

  1. Повторное использование валидации на нескольких контроллерах.
  2. Выгрузка кода из раздутых контроллеров.

Создание пользовательских запросов формы так же просто, как запуск этой artisan-команды:

php artisan make:request StorePostRequest

Затем в контроллере, просто укажите подсказку типа:

use App\Http\Requests\StorePostRequest;

class PostController
{
function store(StorePostRequest $request)
{
$validated = $request->validated();

Post::create($validated);

//
}
}

Пользовательские запросы также могут использоваться, для авторизации, но в этом я предпочитаю полагаться на Политики

Используйте контроллеры одиночного действия

Иногда, несмотря на соблюдение всех рекомендаций, ваши контроллеры становятся слишком большими. Laravel предлагает способ исправить это: Контроллеры одиночного действия. Вместо того чтобы содержать несколько действий, как Контроллеры Ресурсов, Контроллеры одиночного действия содержать всего один.

Для создания Контроллера одиночного действия используйте команду:

php artisan make:controller ShowPostController --invokable

Это создаст контроллер только с одним действием __invoke (узнайте больше о магическом методе __invoke). Затем нужно произвести небольшое изменение в ваших маршрутах:

- use App\Http\Controllers\PostController;
+ use App\Http\Controllers\ShowPostController;

- Route::get('/posts/{post}', [PostController::class, 'show']);
+ Route::get('/posts/{post}', ShowPostController::class);

Используйте Политики

Политики существуют по нескольким причинам.

  1. Повторное использование логики авторизации на нескольких контроллерах.
  2. Выгрузка кода из раздутых контроллеров.
  3. Проверяют папку app/Policies на предмет всего связанного с авторизацией для всех.

Рекомендации по Eloquent

Предотвращение проблемы N+1 с нетерпеливой загрузкой

Нетерпеливая загрузка — отличное решение избежать проблемы N+1

Допустим вы показываете список из 30 статей с указанием их авторов:

  1. Eloquent сделает один запрос для этих 30 статей.
  2. Затем 30 запросов для каждого автора, потому что отношение пользователей использует ленивую загрузку (оно загружается каждый раз, когда вы вызываете $post->user в своём коде)

Решение простое: используйте метод with() и вы сократите количество запросов с 31 до 2-х.

Post::with('author')->get();

Чтобы убедится, что у вас нет проблемы N+1, вы можете запускать исключение всякий раз, когда происходит ленивая загрузка каких-либо отношений. Это должно применяться только в локальной среде.

Model::preventLazyLoading(
! app()->isProduction()
);

Используйте строгий режим Eloquent

Строгий режим Eloquent — это благословение для отладки. Он будет генерировать исключение, когда происходит:

Добавьте этот код в метод boot() вашего AppServiceProvider.php:

Model::shouldBeStrict(
! app()->isProduction() // Only outside of production.
);

Используйте новый способ объявления аксессоров и мутаторов

Новый способ объявления аксессоров и мутаторов был представлен в Laravel 9. Вот как вы должны объявлять их сейчас:

use IlluminateDatabaseEloquentCastsAttribute;

class Pokemon
{
function name() : Attribute
{
$locale = app()->getLocale();

return Attribute::make(
get: fn ($value) => $value[$locale],
set: fn ($value) => [$locale => $value],
);
}
}

Вы можете кэшировать ресурсоёмкое значение:

use IlluminateDatabaseEloquentCastsAttribute;

function someAttribute() : Attribute
{
return Attribute::make(
fn () => /* Do something. */
)->shouldCache();
}

Старый способ выглядит так:

class Pokemon
{
function getNameAttribute() : string
{
$locale = app()->getLocale();

return $this->attributes['name'][$locale];
}

function setNameAttribute($value) : string
{
$locale = app()->getLocale();

return $this->attributes['name'][$locale] = $value;
}
}

Рекомендации по Миграциям

Во-первых, знайте, ч то я написал статью о миграциях Laravel. Возможно, вы захотите прочитать её.

Используйте анонимные миграции (laravel 8 и выше)

Анонимные миграции — отличный способ избежать конфликтов имён классов. Laravel генерирует анонимные миграции, если вы используете Laravel 9 и выше:

php artisan make:migration CreatePostsTable

Вот так они выглядят:

<?php

use IlluminateSupportFacadesSchema;
use IlluminateDatabaseSchemaBlueprint;
use IlluminateDatabaseMigrationsMigration;

return new class extends Migration {

}

Но знаете ли вы, что также можете использовать его с Laravel 8? Просто замените имена классов и всё будет хорошо!

Правильно используйте метод down()

Метод down() (используется командой php artisan migrate:rollback) запускается, когда вам нужно отменить изменения внесённые в вашу базу данных.

Кто-то пользуется, кто-то нет.

Если вы принадлежите к людам которые его используют, вы должны убедится, что метод down() реализован правильно.

Обычно метод down() должен действовать противоположно методу up().

use IlluminateSupportFacadesSchema;
use IlluminateDatabaseSchemaBlueprint;
use IlluminateDatabaseMigrationsMigration;

return new class extends Migration {
public function up()
{
Schema::table('posts', function (Blueprint $table) {
// The column was a boolean, but we want to switch to a datetime.
$table->datetime('is_published')->nullable()->change();
});
}

public function down()
{
Schema::table('posts', function (Blueprint $table) {
// When rolling back, we have to restore the column to its previous state.
$table->boolean('is_published')->default(false)->change();
});
}
}

Рекомендации по производительности

Используйте dispatchAfterResponse() для длительных задач

Возьмём самый простой пример: у вас есть контактная форма. Отправка электронного письма может занять от одной до двух секунд, в зависимости от вашего метода.

Что если бы вы могли отложить это до тех пор, пока пользователь не получит ответ вашего сервера?

Это именно то, что делает dispatchAfterResponse():

SendContactEmail::dispatchAfterResponse($input);

Или если вы предпочитаете отправлять задания с помощью анонимных функций:

dispatch(function () {
// Do something.
})->afterResponse();

Используйте очереди для ещё более длительных задач

Представьте, что вам нужно обрабатывать изображения загруженные вашими пользователями. Если вы обрабатываете каждое изображение сразу после его отправки, произойдёт следующее:

Это не хороший UX и мы можем его изменить. В Laravel есть система очередей, которая будет запускать все эти задачи последовательно или с ограниченным параллелизмом.

Рекомендации по тестированию

Ленивое обновление базы данных

Когда вы можете обойтись без фейковых данных в вашей локальной среде, лучше всего тестировать новую базу данных каждый раз, когда вы запускаете тест. Вы можете использовать трейт Illuminate\Foundation\Testing\LazilyRefreshDatabase в своём 'tests/TestCase.php'

Также есть трейт RefreshDatabase, но он ленивее и более эффективный, так как во время тестирования не будут выполняться миграции для неиспользуемых таблиц.

Используйте Фабрики

Фабрики делают тестирование более управляемым. Вы можете создать Фабрику командой:

php artisan make:factory PostFactory

Используя их, вы можете создать все ресурсы необходимые для написания тестов.

public function test_it_shows_a_given_post()
{
$post = Post::factory()->create();

$this
->get(route('posts.show', $post))
->assertOk();
}

Тестируйте рабочий стек всякий раз, когда это возможно

При запуске веб-приложения в продакшене вы, вероятно, используете не SQLite, а что-то другое, например MySQL. Тогда почему вы запускаете свои тесты используя SQLite? Используйте MySQL. Вы сможете отлавливать ошибки связанные с MySQL в локальной среде, а не ждать пока что-то пойдёт не так в продакшене.

То же самое и для кэширования. Если вы используете Radis в продакшене, почему вы тестируете драйвер массива?

Я убеждён, что надёжность и точность важнее скорости в этом контексте.

Используйте транзакции базы данных

В одном из проектов нужно было создать базу данных, заполненную реальными данными предоставляемыми GitHub в виде CSV-файлов. Это требовало времени и я не мог обновлять базу данных перед каждым тестом. Поэтому, когда тести изменяют данные я хочу отменить изменения. Вы можете сделать это с помощью трейта Illuminate\Foundation\Testing\DatabaseTransactions в вашем tests/TestCase.php.

Не тратьте вызовы API, используйте моки

В Laravel моки можно использовать, что бы не тратить вызовы API во время тестирования и не сталкиваться с ошибками ограничения количества запросов.

Допустим, мы работаем над проектом используя Twitter API. В нашем контейнере есть класс Client, используемый для его вызова. При запуске тестового набора мы хотим избежать ненужного вызова реальных вещей, и лучший способ сделать это — подменить нашего клиента в контейнере моком.

$mock = $this->mock(Client::class);

$mock
->shouldReceive('getTweet')
->with('Some tweet ID')
->andReturn([
'data' => [
'author_id' => '2244994945',
'created_at' => '2022-12-11T10:00:55.000Z',
'id' => '1228393702244134912',
'edit_history_tweet_ids' => ['1228393702244134912'],
'text' => 'This is a tweet',
],
]);

Узнать больше об использовании моков можно из документации Laravel.

Предотвращение случайных HTTP запросов

Если хотите быть уверенным, что все HTTP-запросы, сделанные во время тестов фейковые, используйте метод preventStrayRequests(). Он вызовет исключение, если будут выполнены какие-либо HTTP-запросы, не имеющие фейкового ответа. Вы можете использовать этот метод в отдельном тесте или для всего набора.

Http::preventStrayRequests();

Рекомендации по управлению версиями

Не отслеживайте .env-файлы

Этот совет очевиден для большинства разработчиков, но я думаю, его стоит упомянуть. Ваш .env-файл содержит конфиденциальную информацию. Убедитесь, что он включён в .gitignore.

Если в какой-то момент вы заметили, что он отслеживается в Git, убедитесь, что никто имеющий доступ к истории Git, не сможет использовать учётные данные из .env-файла. Измените их!

Не отслеживайте скомпилированные CSS и javaScript

Ваши CSS и JavaScript файлы создаются из оригиналов в resources/css и resource/js. При развёртывании в продакшен вы либо компилируете их на сервере, либо заранее создаёте артефакт.

Специально для людей использующих Laravel Mix я рекомендую перестать их отслеживать. Довольно сильно раздражает то, что каждый раз, когда вы что-то меняете, создаётся новый public/css/app.css или public/js/app.js, который необходимо закоммитить. Что бы это остановить нужно добавить в .gitignore всего две строки:

public/css
public/js

Дополнительные материалы

Предыдущая Статья

PHP 8.3: Что нового. Изменения и новый функционал.

Следующая Статья

PHP 8.3: Дополнение Randomizer