• Час читання ~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