В этой статье мы рассмотрим эти основные лучшие практики Laravel, от структурирования вашего кода до оптимизации операций с базой данных, чтобы ваши проекты оставались эффективными и удобными для разработчиков.
Независимо от того, являетесь ли вы опытным разработчиком на Laravel или только начинаете, эти практики помогут вам повысить уровень своих навыков разработки и создавать высококачественные приложения.
Fat Models, Skinny Controllers
Сместите логику базы данных в Eloquent модели для поддержания более чистых контроллеров и повторно используемого кода
Плохой пример:
public function index()
{
$clients = Client::verified()
->with(['orders' => function ($query) {
$query->where('created_at', '>', now()->subDays(7));
}])
->get();
return view('index', compact('clients'));
}
Хороший пример:
public function index(Client $client)
{
return view('index', ['clients' => $client->getVerifiedWithRecentOrders()]);
}
class Client extends Model
{
public function getVerifiedWithRecentOrders(): Collection
{
return $this->verified()
->with(['orders' => fn($query) => $query->recent()])
->get();
}
public function scopeVerified($query)
{
return $query->where('is_verified', true);
}
}
class Order extends Model
{
public function scopeRecent($query)
{
return $query->where('created_at', '>', now()->subDays(7));
}
}
Принцип
одной ответственности Класс должен иметь только одну ответственность. Это означает, что класс должен быть сосредоточен на одной части функциональности. Нарушение этого принципа может затруднить чтение, тестирование и обслуживание кода, поскольку в нем смешиваются задачи, которые должны быть разделены.
By adhering to the Принцип, you create code that is easier to understand and refactor. Each class or service has a clear purpose, making the overall system more modular and flexible.
Плохой пример:
public function update(Request $request): string
{
$validated = $request->validate([
'name' => 'required|max:255',
'tasks' => 'required|array:due_date,status'
]);
foreach ($request->tasks as $task) {
$formattedDate = $this->carbon->parse($task['due_date'])->toDateTimeString();
$this->logger->info('Task updated: ' . $formattedDate . ' - ' . $task['status']);
}
$this->project->updateTasks($request->validated());
return redirect()->route('projects.index');
}
Хороший пример:
public function update(UpdateProjectRequest $request): string
{
$this->taskLogger->logTasks($request->tasks);
$this->projectService->updateTasks($request->validated());
return redirect()->route('projects.index');
}
class TaskLogger
{
public function logTasks(array $tasks): void
{
// Logic to log tasks
}
}
class ProjectService
{
public function updateTasks(array $data): void
{
// Logic to update project tasks
}
}
Методы должны делать только одну вещь
Функция должна иметь одну цель и хорошо ее выполнять. Когда метод выполняет более одной задачи, его становится труднее понять, протестировать и поддерживать. Разделение обязанностей на более мелкие, узконаправленные методы делает код более читабельным и упрощает отладку.
Плохой пример:
public function getFullNameAttribute(): string
{
if (auth()->user() && auth()->user()->hasRole('admin') && auth()->user()->isVerified()) {
return 'Admin ' . $this->first_name . ' ' . $this->last_name;
} else {
return $this->first_name[0] . '. ' . $this->last_name;
}
}
Хороший пример:
public function getFullNameAttribute(): string
{
return $this->isVerifiedAdmin() ? $this->formatFullName() : $this->formatShortName();
}
private function isVerifiedAdmin(): bool
{
$user = auth()->user();
return $user && $user->hasRole('admin') && $user->isVerified();
}
private function formatFullName(): string
{
return 'Admin ' . $this->first_name . ' ' . $this->last_name;
}
private function formatShortName(): string
{
return strtoupper($this->first_name[0]) . '. ' . ucfirst($this->last_name);
}
Сохраняйте бизнес-логику в классах
обслуживания Контроллеры должны обрабатывать только HTTP-запросы и ответы, делегируя сложную логику классам служб. Это делает код чистым, пригодным для повторного использования и более простым для тестирования.
Плохой пример:
public function store(Request $request)
{
if ($request->hasFile('image')) {
$image = $request->file('image');
$image->storeAs('temp', $image->getClientOriginalName(), 'public');
}
// Other unrelated logic...
}
Хороший пример:
public function store(Request $request, ArticleService $articleService)
{
$articleService->uploadImage($request->file('image'));
// Other unrelated logic...
}
class ArticleService
{
public function uploadImage(?UploadedFile $image): void
{
if ($image) {
$image->storeAs('uploads/temp', uniqid() . '_' . $image->getClientOriginalName(), 'public');
}
}
}
Избегайте бизнес-логики в маршрутах
Маршруты должны обрабатывать только HTTP-запросы, а не бизнес-логику. Это сохраняет ваш код чистым и удобным в обслуживании.
Плохой пример
// Business logic in the route
Route::post('/article', function (Request $request) {
$article = new Article;
$article->title = $request->title;
$article->content = $request->content;
$article->save();
});
Хороший пример
// Route delegates logic to the controller
Route::post('/article', [ArticleController::class, 'store']);
// In ArticleController
public function store(Request $request)
{
// logic to create article
}
Используйте отношения для более чистого кода
Используйте красноречивые отношения, чтобы упростить и прояснить взаимодействие связанных моделей. Это позволяет избежать повторяющихся заданий и делает код более простым в обслуживании и менее подверженным ошибкам.
Плохой пример
$article = new Article;
$article->title = $request->input('title');
$article->content = $request->input('content');
$article->verified = $request->boolean('verified');
$article->category_id = $category->id;
$article->save();
Хороший пример
$category->articles()->create($request->safe()->only(['title', 'content', 'verified']));
Используйте транзакции базы данных для атомарных бизнес-операций
Транзакции гарантируют, что все операции с базой данных будут успешными или неудачными как группа, сохраняя целостность данных.
Плохой пример
public function placeOrder(Request $request)
{
$order = new Order;
$order->user_id = $request->user_id;
$order->save();
$payment = new Payment;
$payment->order_id = $order->id;
$payment->save();
}
Хороший пример
use DB;
public function placeOrder(Request $request)
{
DB::beginTransaction();
try {
$order = Order::create($request->validated());
$payment = Payment::create(['order_id' => $order->id]);
DB::commit();
} catch (\Exception $e) {
DB::rollBack();
throw $e;
}
}
Избегайте запросов в Blade: используйте жадную загрузку
Выполнение запросов в шаблонах Blade приводит к неэффективным вызовам базы данных, особенно с циклами. Жадная загрузка извлекает связанные данные в одном запросе, повышая производительность и избегая проблемы с запросами N + 1.
Плохой пример
@foreach (User::all() as $user)
{{ $user->profile->name }}
@endforeach
Если у вас 100 пользователей, то будет отправлен 101 запрос: один для пользователей и по одному для профиля каждого пользователя.
Хороший пример
// in a service class or model passed back to controller which shares that with the blade file
$users = User::with('profile')->get();
// in blade file
@foreach ($users as $user)
{{ $user->profile->name }}
@endforeach
При этом запускается всего 2 запроса: один для пользователей и один для их профилей.
Блоки данных для производительности
Для задач, связанных с большими наборами данных, обработка блоками снижает использование памяти и повышает производительность за счет ограничения объема данных, хранящихся в памяти одновременно.
Плохой пример
$users = User::all();
foreach ($users as $user) {
// Process each user
}
Хороший пример
User::chunk(500, function ($users) {
foreach ($users as $user) {
// Process each user
}
});
Используйте константы вместо жестко заданных значений
Использование констант поможет вам найти места, где используется это значение, если вы захотите его изменить, провести рефакторинг и поможет вам с отладкой.
Плохой пример
public function isAdmin(User $user): bool
{
return $user->type === 'admin';
}
Хороший пример
public function isAdmin(User $user)
{
return $user->type === UserType::ADMIN;
}
Перевод строк
Вы будете благодарны себе в будущем по мере роста вашего приложения, если рассматривали перевод строк с самого начала. Все что вам нужно для передачи строк через __() функцию
Плохой пример
return back()->with('message', 'Your article has been added!');
Хороший пример
return back()->with('message', __('Your article has been added!')); // notice the call to __()
Внедрение зависимостей
Создание экземпляров с new помощью тесно связывает классы и затрудняет их тестирование или изменение. Использование контейнера IoC упрощает внедрение зависимостей и улучшает тестируемость.
Плохой пример
public function store(Request $request)
{
$user = new User;
$user->create($request->validated());
}
Хороший пример
public function __construct(protected UserService $userService) {}
public function store(Request $request)
{
$this->userService->create($request->validated());
}
Избегайте прямого использования .env в коде
Доступ к данным непосредственно из .env файла в приложении может затруднить поддержку и тестирование кода. Вместо этого сохраняйте значения в файлах конфигурации и извлекайте их с помощью config().
Плохой пример
$apiKey = env('API_KEY');
Хороший пример
// config/services.php
'api_key' => env('API_KEY'),
// Retrieve the value
$apiKey = config('services.api_key');
Хранение дат в виде объектов, а не строк
Хранение дат в виде строк может привести к несогласованным форматам и ошибкам синтаксического анализа. Лучше хранить их как экземпляры Carbon, которые обеспечивают надежную обработку даты. Используйте методы доступа и мутаторы для форматирования дат только при необходимости в слое отображения.
Плохой пример
{{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->toDateString() }}
{{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->format('m-d') }}
Хороший пример
// In Model
protected $casts = [
'ordered_at' => 'datetime',
];
// In Blade View
{{ $object->ordered_at->toDateString() }}
{{ $object->ordered_at->format('m-d') }}
Сохраняйте минимальную и содержательную
документацию по коду Излишняя документация часто загромождает код и затрудняет его обслуживание. Вместо этого полагайтесь на четкие, описательные имена для переменных, функций и классов. Используйте комментарии только в случае крайней необходимости для объяснения сложной логики.
Плохой пример
/**
* The function checks if the given string has any white spaces
*
* @param string $string String received from frontend which might contain
* space characters. Returns True if the string
* is valid.
*
* @return bool
*
* @license GPL
*/
public function checkString($string)
{
}
Хороший пример
public function hasWhiteSpaces(string $string): bool
{
}
Согласование стандартов программирования с вашей командой
Согласованный код улучшает удобочитаемость и удобство обслуживания, упрощая совместную работу.
Используйте Can, Laravel Pint для автоматического форматирования и применения стандартов кодирования. Он интегрируется с вашим рабочим процессом разработки, поэтому вы можете запускать его перед каждым коммитом с помощью Git Hooks.
composer require --dev laravel/pint
vendor/bin/pint
Тестируйте, тестируйте и еще раз тестируйте
И, наконец, одна из самых важных вещей, которую вы можете сделать для обеспечения надежности, удобства обслуживания и масштабируемости вашего кода, — это написание автоматических тестов.
Вам не нужно на 100% покрывать свой функционал (я бы сказал, что это контрпродуктивно), но, по крайней мере, вам нужно убедиться, что все ваши маршруты GET охвачены, и чем больше вы можете добавить сверху, тем лучше.
Вот почему:
-
Выявляйте ошибки на ранней стадии: Тесты помогают выявить проблемы до того, как они попадут в рабочую среду. Выявлять ошибки во время разработки гораздо дешевле и проще, чем исправлять их после развертывания.
-
Уверенность в коде: При надлежащем покрытии тестами вы можете с уверенностью вносить изменения в кодовую базу. Тесты гарантируют, что новые изменения не нарушат существующую функциональность.
-
Документация: Хорошо написанные тесты выступают в качестве живой документации для вашего кода. Они описывают, как должна вести себя система, и могут быть использованы для понимания замысла кода.
-
Безопасный рефакторинг: При рефакторинге или улучшении существующего кода тесты обеспечивают подстраховку, гарантирующую, что функциональность не будет потеряна во время изменений.
-
Улучшенный дизайн: Написание тестов часто способствует лучшему дизайну программного обеспечения. Для написания тестируемого кода обычно используются более мелкие, более узкоспециализированные методы и классы, которые проще поддерживать.
-
Совместная работа: тесты упрощают совместную работу команд над большими базами кода. Они определяют четкие ожидания в отношении поведения, уменьшая недопонимание и улучшая сотрудничество.
-
Непрерывная интеграция: Тесты необходимы для реализации непрерывной интеграции и рабочих процессов поставки. Автоматические тесты могут выполняться при каждой отправке кода, гарантируя, что будет развернут только стабильный код.
-
Долгосрочное обслуживание: В крупных проектах тесты помогают поддерживать стабильность с течением времени, особенно при смене команды. Новые разработчики могут полагаться на тесты, чтобы понять поведение кодовой базы и убедиться, что будущие изменения не нарушат функциональность.
Продолжайте давить, продолжайте строить! 🤘