• Время чтения ~8 мин
  • 28.01.2025

В этой статье мы рассмотрим эти основные лучшие практики 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 охвачены, и чем больше вы можете добавить сверху, тем лучше. 

Вот почему:

  1. Выявляйте ошибки на ранней стадии: Тесты помогают выявить проблемы до того, как они попадут в рабочую среду. Выявлять ошибки во время разработки гораздо дешевле и проще, чем исправлять их после развертывания.

  2. Уверенность в коде: При надлежащем покрытии тестами вы можете с уверенностью вносить изменения в кодовую базу. Тесты гарантируют, что новые изменения не нарушат существующую функциональность.

  3. Документация: Хорошо написанные тесты выступают в качестве живой документации для вашего кода. Они описывают, как должна вести себя система, и могут быть использованы для понимания замысла кода.

  4. Безопасный рефакторинг: При рефакторинге или улучшении существующего кода тесты обеспечивают подстраховку, гарантирующую, что функциональность не будет потеряна во время изменений.

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

  6. Совместная работа: тесты упрощают совместную работу команд над большими базами кода. Они определяют четкие ожидания в отношении поведения, уменьшая недопонимание и улучшая сотрудничество.

  7. Непрерывная интеграция: Тесты необходимы для реализации непрерывной интеграции и рабочих процессов поставки. Автоматические тесты могут выполняться при каждой отправке кода, гарантируя, что будет развернут только стабильный код.

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

Продолжайте давить, продолжайте строить! 🤘

Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

Про мене

Professional Fullstack Developer with extensive experience in website and desktop application development. Proficient in a wide range of tools and technologies, including Bootstrap, Tailwind, HTML5, CSS3, PUG, JavaScript, Alpine.js, jQuery, PHP, MODX, and Node.js. Skilled in website development using Symfony, MODX, and Laravel. Experience: Contributed to the development and translation of MODX3 i...

Об авторе CrazyBoy49z
WORK EXPERIENCE
Контакты
Ukraine, Lutsk
+380979856297

Мы сбиваем «шахеды» каждый день. Каждое сбитие — спасённые жизни. Нам нужна мобильность: бус или прицеп. Каждый донат = ещё одна ночь под защитой.

🚐 Сбор на микроавтобус для моего экипажа, полк 1020 🎯 Цель: 500 000 ₴
🔗 Ссылка на банку 💳 4441 1111 2546 4663