Laravel: Девять типичных ошибок новичков
Не все из них действительно серьёзные недостатки, большинство просто не самые эффективные способы написания кода. Тогда остаётся открытым вопрос, зачем использовать такой фреймворк, как Laravel, и не использовать его основные возможности в полной мере?
Итак, в произвольном порядке…
Ошибка 1. Не использовать группы маршрутов
По возможности объединяйте маршруты в группы. Например, есть такие маршруты:
Route::get('dashboard', [HomeController::class, 'index'])->name('dashboard')->middleware(['auth']);
Route::resource('donation', DonationController::class)->middleware(['auth']);
Route::resource('requisition', RequisitionController::class)->middleware(['auth']);
Route::name('admin.')->prefix('admin.')->group(function () {
Route::view('/', 'admin.welcome')->middleware(['auth', 'admincheck']);
Route::resource('donor', DonorController::class)->middleware(['auth', 'admincheck']);
Route::resource('details', OrganisationDetailController::class)->middleware(['auth', 'admincheck']);
});
Все маршруты с middleware auth
и три маршрута, также проверяющие является ли пользователь admin
. Все эти middleware повторяются каждый раз.
Было бы лучше поместить все маршруты в группу для проверки middleware auth
, а затем внутри создать ещё одну группу для маршрутов admin
.
Таким образом, когда разработчик откроет файл маршрутов, он сразу узнает, какие маршруты предназначены только для аутентифицированных пользователей.
Route::middleware('auth')->group(function () {
Route::get('dashboard', [HomeController::class, 'index'])->name('dashboard');
Route::resource('donation', DonationController::class);
Route::resource('requisition', RequisitionController::class);
Route::name('admin.')->prefix('admin.')->middleware('admincheck')->group(function () {
Route::view('/', 'admin.welcome');
Route::resource('donor', DonorController::class);
Route::resource('details', OrganisationDetailController::class);
});
});
- Документация Laravel: Группы Маршрутов
- Laravel: Управление маршрутами в большом приложении
- Laravel: Параметры маршрута из нескольких слов
Ошибка 2. Не использовать привязку модели маршрута
Часто вижу, как начинающие кодеры ищут вручную в контроллере данные, даже если в маршрутах привязка модели указана корректно. Например, в маршрутах у вас есть:
Route::resource('student', StudentController::class);
Так что здесь это даже ресурсный маршрут. Но я вижу, что некоторые новички всё ещё пишут код контроллера следующим образом:
public function show($id)
{
$student = Student::findOrFail($id);
return view('dashboard/student/show', compact(['student']));
}
Вместо этого используйте привязку модели маршрута, и Laravel найдёт модель:
public function show(Student $student)
{
return view('dashboard/student/show', compact(['student']));
}
- Документация Laravel: Route Model Binding
Ошибка 3. Слишком длинный код Eloquent Create/Update
При сохранении данных в БД я видел, как пишут код, похожий на этот:
public function update(Request $request)
{
$request->validate(['name' => 'required']);
$user = Auth::user();
$user->name = $request->name;
$user->username = $request->username;
$user->mobile = $request->mobile;
// Другие поля...
$user->save();
return redirect()->route('profile.index');
}
Вместо этого, его можно написать короче, по крайней мере, двумя способами.
Во-первых, в этом примере не нужно устанавливать Auth::user()
в переменную $user
. Первый вариант может быть:
public function update(Request $request)
{
$request->validate(['name' => 'required']);
auth()->user()->update([$request->only([
'name',
'username',
'mobile',
// Другие поля...
]);
return redirect()->route('profile.index');
}
Во втором варианте помещаем проверку в Form Request. Затем в метод update()
нужно будет просто передать $request->validated()
.
public function update(ProfileRequest $request)
{
auth()->user()->update([$request->validated());
return redirect()->route('profile.index');
}
Видите, насколько короче код?
Ошибка 4. Неправильное именование
Часто новички дают имена как хотят, не думая о других разработчиках, которые будут читать их код.
Например, сокращение имён переменных: вместо $data
они называют $d
. Всегда используйте правильное именование. Например:
Route::get('/', [IndexController::class, 'show'])
->middleware(['dq'])
->name('index');
Route::get('/about', [IndexController::class, 'about'])
->middleware(['dq'])
->name('about');
Route::get('/dq', [IndexController::class, 'dq'])
->middleware(['auth'])
->name('dq');
Что это за middleware и метод в IndexController
, называемые dq
? Ну, в этом примере, если бы мы зашли в app/Http/Kernel.php
, чтобы найти это middleware, то могли обнаружить что-то вроде этого:
class Kernel extends HttpKernel
{
// ...
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'admin' => \App\Http\Middleware\EnsureAdmin::class,
'dq' => \App\Http\Middleware\Disqualified::class,
'inprogress' => \App\Http\Middleware\InProgress::class,
// ...
];
}
Неважно, что внутри middleware, но название файла Disqualified
. Так что везде вместо dq
его надо называть disqualified
. Таким образом, если к проекту присоединятся другие разработчики, им будет понятнее.
Ошибка 5. Слишком большие контроллеры
Довольно часто я вижу, как новички пишут огромные контроллеры со всеми возможными действиями в одном методе:
- Валидация.
- Проверка данных.
- Преобразование данных.
- Сохранение данных.
- Сохранение большого количества данных в другие таблицы.
- Отправка писем/уведомлений.
- и т.д.
Всё это может быть в одном методе store()
, например:
public function store(Request $request)
{
$this->authorize('user_create');
$userData = $request->validate([
'name' => 'required',
'email' => 'required|unique:users',
'password' => 'required',
]);
$userData['start_at'] = Carbon::createFromFormat('m/d/Y', $request->start_at)->format('Y-m-d');
$userData['password'] = bcrypt($request->password);
$user = User::create($userData);
$user->roles()->sync($request->input('roles', []));
Project::create([
'user_id' => $user->id,
'name' => 'Demo project 1',
]);
Category::create([
'user_id' => $user->id,
'name' => 'Demo category 1',
]);
Category::create([
'user_id' => $user->id,
'name' => 'Demo category 2',
]);
MonthlyRepost::where('month', now()->format('Y-m'))->increment('users_count');
$user->sendEmailVerificationNotification();
$admins = User::where('is_admin', 1)->get();
Notification::send($admins, new AdminNewUserNotification($user));
return response()->json([
'result' => 'success',
'data' => $user,
], 200);
}
Это не обязательно неправильно, но в будущем другим разработчикам будет очень трудно быстро прочитать. И то, что трудно читать, становиться трудно изменить и исправить будущие ошибки.
Вместо этого контроллеры должны быть короче и просто брать данные из маршрутов, вызывать некоторые методы и возвращать результат. Вся логика работы с данными должна находится в специально подходящих для этого классах:
- Валидация, в классах Form Request.
- Преобразование данных в Моделях и/или Наблюдателях.
- Отправка писем в Событиях/Слушателях, поставленная в очередь.
- т.д.
Существуют различные подходы к структурированию кода, но всегда следует избегать одного огромного метода, отвечающего за всё.
- Laravel: Чистка контроллеров
- Laravel: Ваши контроллеры должны выглядеть так
- Laravel: переносим Контроллер в Сервисный Класс с внедрением
- Laravel: Рефакторинг контроллера
- Laravel: Валидация данных приложения
- Laravel: Погружение в Уведомления/Notifications
Ошибка 6. Проблема N+1 запроса Eloquent
На сегодняшняя типичная причина №1 низкой производительности проекта Laravel — структура запросов Eloquent. В частности, проблема Запроса N+1 является наиболее распространённой: выполнение сотен SQL-запросов на одной странице определённо требует много ресурсов сервера.
И относительно легко обнаружить эту проблему на таких простых примерах, как этот:
// Controller not eager loading Users:
$projects = Project::all();
// Blade:
@foreach ($projects as $project)
<li>{{ $project->name }} ({{ $project->user->name }})</li>
@endforeach
Но примеры из реальной жизни становятся более сложными, и количество запросов может быть "спрятано" в аксессорах, пакетных запросах и других непредсказуемых местах.
Кроме того, типичные джуны не тратят достаточно времени на тестирование своего приложения с большим количеством данных. Это работает для них с несколькими записями базы данных, поэтому они не делают лишних усилий, чтобы смоделировать будущие сценарии, где их код на самом деле вызовет проблемы с производительностью.
Ошибка 7. Нарушение шаблона MVC: Логика в Blade
Всякий раз, когда я вижу директиву @php
в Blade файле, моё сердце начинает биться быстрее.
Посмотрите этот пример:
@php
$x = 5
@endphp
За исключением очень редких случаев, весь PHP-код для получения данных должен быть выполнен до того, как он будет показан в Blade.
Архитектура MVC была создана по той причине, что разделение задач между Моделью (Model), Представлением (View) и Контроллером (Controller) делает более предсказуемым, где искать определённые фрагменты кода.
И в то время как части М и C могут обсуждаться, следует ли хранить логику в Модели, в Контроллере или в отдельных Сервисах/Action, уровень V Представлений является своего рода священным. Золотое правило: представления не должны содержать логику. Другими словами, представления предназначены только для представления данных, а не для преобразования или вычислений.
Происхождения этого связано с тем, что Представление может использовать фронтенд HTML/CSS разработчики, и они могут вносить изменения без необходимости понимания PHP-кода. Конечно, в реальной жизни такое разделение редко встречается в командах, но это благородная цель паттерна, который пришёл извне Laravel и даже PHP.
Для большинства ошибок в этом списке есть список ссылок, но здесь мне особо нечего добавить. Просто не храните логику в Представлениях, вот и всё.
Ошибка 8. Отношения: Не созданы внешние ключи
Отношения между таблицами создаются на двух уровнях: вам нужно создать связанное поле, а затем внешний ключ. Я вижу, что многие джуны забывают вторую часть.
Вы когда-нибудь видели что-то подобное в миграции?
$table->unsignedBigInteger('user_id');
На первый взгляд всё выглядит нормально. И это действительно работает, баз каких-либо ошибок. Сначала.
Позвольте мне показать, что произойдёт если вы не поместите constrained()
или references()
в этот файл миграции.
Внешний ключ — это механизм ограничения связанных операций на уровне базы данных. Поэтому, когда вы удаляете родительскую запись, вы можете выбирать, что делать с дочерними: либо удалить тоже, либо запретить удаление родителя в первую очередь.
Итак, если вы создаёте только поле unsignedBigInteger()
, без внешнего ключа, вы позволяете своим пользователям удалять родителя без каких-либо последствий. Таким образом, дети остаются в базе данных, а их родитель больше не существует.
Ошибка 9. Не читать документацию
Я хочу закончить этот список слоном в комнате. Уже много лет самые популярные мои статьи и твиты — это информация, буквально взятая из документации, практически слово в слово.
С годами я понял, что люди на самом деле не читают документацию полностью, а только основные части, которые наиболее важны для них.
Много раз разработчики удивляли меня, не зная очевидных функций, которые были в документации.
Джуны учатся в основном на готовых руководствах или курсах, но чтение официальной документации должно быть регулярным занятием.