• Czas czytania ~8 min
  • 28.01.2025

W tym artykule przyjrzymy się tym podstawowym najlepszym praktykom Laravel, od strukturyzacji kodu po optymalizację operacji na bazie danych, dzięki czemu Twoje projekty pozostaną wydajne i przyjazne dla programistów.

Niezależnie od tego, czy jesteś doświadczonym programistą Laravel, czy dopiero zaczynasz, te praktyki pomogą Ci podnieść poziom umiejętności programistycznych i dostarczać wysokiej jakości aplikacje.

Fat Models, Skinny Controllers

Przenieś logikę bazy danych na modele elokwentne, aby zachować czystsze kontrolery i kod

wielokrotnego użytkuZły przykład:

public function index()
{
    $clients = Client::verified()
        ->with(['orders' => function ($query) {
            $query->where('created_at', '>', now()->subDays(7));
        }])
        ->get();
    return view('index', compact('clients'));
}

Dobry przykład:

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));
    }
}

Zasada

pojedynczej odpowiedzialności Klasa powinna mieć tylko jedną odpowiedzialność. Oznacza to, że klasa powinna skupiać się na jednym elemencie funkcjonalności. Naruszenie tej zasady może sprawić, że kod będzie trudniejszy do odczytania, przetestowania i utrzymania, ponieważ miesza problemy, które należy rozdzielić.

By adhering to the Zasada, 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.

wielokrotnego użytkuZły przykład:

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');
}

Dobry przykład:

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
    }
}


Metody powinny robić tylko jedną rzecz

: funkcja powinna mieć jeden cel i dobrze go wykonywać. Kiedy metoda robi więcej niż jedną rzecz, staje się trudniejsza do zrozumienia, przetestowania i utrzymania. Podział obowiązków na mniejsze, skoncentrowane metody sprawia, że kod jest bardziej czytelny i łatwiejszy do debugowania.

wielokrotnego użytkuZły przykład:

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;
    }
}

Dobry przykład:

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);
}


Zachowaj logikę biznesową w klasach

usług Kontrolery powinny obsługiwać tylko żądania i odpowiedzi HTTP, delegując złożoną logikę do klas usług. Dzięki temu kod jest czysty, nadaje się do wielokrotnego użytku i jest łatwiejszy do przetestowania.

wielokrotnego użytkuZły przykład:

public function store(Request $request)
{
    if ($request->hasFile('image')) {
        $image = $request->file('image');
        $image->storeAs('temp', $image->getClientOriginalName(), 'public');
    }
    
    // Other unrelated logic...
}

Dobry przykład:

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');
        }
    }
}

Unikaj logiki biznesowej w trasach

Trasy powinny obsługiwać tylko żądania HTTP, a nie logikę biznesową. Dzięki temu Twój kod jest czysty i łatwy w utrzymaniu.

Zły przykład

// Business logic in the route
Route::post('/article', function (Request $request) {
    $article = new Article;
    $article->title = $request->title;
    $article->content = $request->content;
    $article->save();
});

Dobry przykład

// Route delegates logic to the controller
Route::post('/article', [ArticleController::class, 'store']);
// In ArticleController
public function store(Request $request)
{
    // logic to create article
}


Używanie relacji w celu ulepszenia kodu

Użyj relacji elokwentnych, aby uprościć i wyjaśnić, w jaki sposób powiązane modele współdziałają. Pozwala to uniknąć powtarzających się przypisań i sprawia, że kod jest łatwiejszy w utrzymaniu i mniej podatny na błędy.

Zły przykład

$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();

Dobry przykład

$category->articles()->create($request->safe()->only(['title', 'content', 'verified']));

Korzystanie z transakcji bazy danych dla niepodzielnych transakcji operacji

biznesowych zapewnia, że wszystkie operacje bazy danych kończą się powodzeniem lub niepowodzeniem jako grupa, zachowując integralność danych.

Zły przykład

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();
}

Dobry przykład

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;
    }
}

Unikaj zapytań w bloku Blade: Użyj zachłannego ładowania

Wykonywanie zapytań wewnątrz szablonów bloku prowadzi do nieefektywnych wywołań bazy danych, zwłaszcza w przypadku pętli. Zachłanne ładowanie pobiera powiązane dane w jednym zapytaniu, zwiększając wydajność i unikając problemu z zapytaniami N + 1.

Zły przykład

@foreach (User::all() as $user)
    {{ $user->profile->name }}
@endforeach

Jeśli masz 100 użytkowników, spowoduje to wyzwolenie 101 zapytań: jedno dla użytkowników i jedno dla każdego profilu użytkownika.

Dobry przykład

// 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

Spowoduje to wyzwolenie tylko 2 zapytań: jednego dla użytkowników i jednego dla ich profili.


Fragmentowanie danych w celu zwiększenia wydajności

W przypadku zadań, które obejmują duże zestawy danych, przetwarzanie we fragmentach zmniejsza użycie pamięci i poprawia wydajność, ograniczając ilość danych przechowywanych w pamięci jednocześnie.

Zły przykład

$users = User::all();
foreach ($users as $user) {
    // Process each user
}

Dobry przykład

User::chunk(500, function ($users) {
    foreach ($users as $user) {
        // Process each user
    }
});


Używaj stałych zamiast zakodowanych na stałe wartości

Korzystanie ze stałych pomoże Ci zlokalizować miejsca, w których ta wartość jest używana w przypadku, gdy chcesz ją zmienić, refaktoryzować i pomoże w debugowaniu. 

Zły przykład

public function isAdmin(User $user): bool
{
    return $user->type === 'admin';
}

Dobry przykład

public function isAdmin(User $user)
{
    return $user->type === UserType::ADMIN;
}


Tłumaczenie ciągów

Podziękujesz sobie w przyszłości, gdy Twoja aplikacja będzie się rozwijać, jeśli od samego początku rozważyłeś tłumaczenie ciągów. Wszystko, czego potrzebujesz, aby przekazać ciągi __() znaków przez funkcję 

Zły przykład

return back()->with('message', 'Your article has been added!');

Dobry przykład

return back()->with('message', __('Your article has been added!'));  // notice the call to __()


Wstrzykiwanie zależności

Tworzenie instancji z klasami ściśle wiąże się z new klasami i utrudnia ich testowanie lub modyfikowanie. Korzystanie z kontenera IoC pozwala na łatwiejsze wstrzykiwanie zależności i lepszą testowalność.

Zły przykład

public function store(Request $request)
{
    $user = new User;
    $user->create($request->validated());
}

Dobry przykład

public function __construct(protected UserService $userService) {}
public function store(Request $request)
{
    $this->userService->create($request->validated());
}


Unikaj bezpośredniego używania .env w kodzie

Uzyskiwanie dostępu do danych bezpośrednio z .env pliku w całej aplikacji może utrudnić utrzymanie i testowanie kodu. Zamiast tego przechowuj wartości w plikach konfiguracyjnych i pobieraj je za pomocą config().

Zły przykład

$apiKey = env('API_KEY');

Dobry przykład

// config/services.php
'api_key' => env('API_KEY'),
// Retrieve the value
$apiKey = config('services.api_key');


Przechowywanie dat jako obiektów, a nie ciągów

Przechowywanie dat jako ciągów może prowadzić do niespójnych formatów i błędów analizowania. Lepiej jest przechowywać je jako instancje Carbon, które zapewniają solidną obsługę dat. Dostępu i modyfikatorów można formatować daty tylko wtedy, gdy są potrzebne w warstwie wyświetlania.

Zły przykład

{{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->toDateString() }}
{{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->format('m-d') }}

Dobry przykład

// In Model
protected $casts = [
    'ordered_at' => 'datetime',
];
// In Blade View
{{ $object->ordered_at->toDateString() }}
{{ $object->ordered_at->format('m-d') }}


Utrzymuj dokumentację kodu na minimalnym i znaczącym

poziomie Nadmierna dokumentacja często zaśmieca kod i utrudnia jego utrzymanie. Zamiast tego polegaj na jasnych, opisowych nazwach zmiennych, funkcji i klas. Używaj komentarzy tylko wtedy, gdy jest to absolutnie konieczne, aby wyjaśnić złożoną logikę.

Zły przykład

/**
 * 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)
{
}

Dobry przykład

public function hasWhiteSpaces(string $string): bool
{
}

Dostosuj standardy kodowania do swojego zespołu

Spójny kod poprawia czytelność i łatwość konserwacji, ułatwiając współpracę.

Użyj puszki Laravel Pint , aby automatycznie formatować i egzekwować standardy kodowania. Integruje się z przepływem pracy programowania, dzięki czemu można go uruchamiać przed każdym zatwierdzeniem za pomocą punktów zaczepienia Git.

composer require --dev laravel/pint
vendor/bin/pint


Testowanie, testowanie i testowanie

I wreszcie, jedną z najważniejszych rzeczy, które możesz zrobić, aby zapewnić niezawodność, łatwość konserwacji i skalowalność swojego kodu, jest pisanie testów automatycznych.

Nie potrzebujesz 100% pokrycia swojej funkcjonalności (twierdzę, że przynosi to efekt przeciwny do zamierzonego), ale przynajmniej musisz upewnić się, że wszystkie trasy, które OTRZYMUJESZ, są pokryte, a im więcej możesz dodać na górze, tym lepiej. 

Oto dlaczego:

  1. Wczesne wykrywanie błędów: Testy pomagają zidentyfikować problemy, zanim trafią do produkcji. Wyłapywanie błędów podczas programowania jest znacznie tańsze i łatwiejsze niż naprawianie ich po wdrożeniu.

  2. Pewność kodu: Dzięki odpowiedniemu pokryciu testami możesz bez obaw wprowadzać zmiany w bazie kodu. Testy zapewniają, że nowe zmiany nie przerywają istniejących funkcji.

  3. Dokumentacja: Dobrze napisane testy działają jako żywa dokumentacja dla Twojego kodu. Opisują one, jak system ma się zachowywać i mogą służyć do zrozumienia intencji kodu.

  4. Bezpieczna refaktoryzacja: Podczas refaktoryzacji lub ulepszania istniejącego kodu testy zapewniają siatkę bezpieczeństwa, która zapewnia, że żadna funkcjonalność nie zostanie utracona podczas zmian.

  5. Ulepszony projekt: Pisanie testów często zachęca do lepszego projektowania oprogramowania. Aby napisać testowalny kod, zazwyczaj kończy się to mniejszymi, bardziej skoncentrowanymi metodami i klasami, które są łatwiejsze do utrzymania.

  6. Współpraca: Testy ułatwiają zespołom wspólną pracę nad dużymi bazami kodu. Definiują jasne oczekiwania dotyczące zachowania, redukując nieporozumienia i poprawiając współpracę.

  7. Ciągła integracja: Testy są niezbędne do wdrożenia przepływów pracy ciągłej integracji i dostarczania. Testy automatyczne mogą być uruchamiane przy każdym wypchnięciu kodu, zapewniając, że wdrażany jest tylko stabilny kod.

  8. Utrzymanie długoterminowe: W dużych projektach testy pomagają utrzymać stabilność w czasie, zwłaszcza gdy zespół się zmienia. Nowi programiści mogą polegać na testach, aby zrozumieć zachowanie bazy kodu i upewnić się, że przyszłe zmiany nie spowodują przerwania funkcjonalności.

Nie przestawaj pchać, buduj dalej! 🤘

Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

O

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...

O autorze CrazyBoy49z
WORK EXPERIENCE
Kontakt
Ukraine, Lutsk
+380979856297

Codziennie zestrzeliwujemy „Shahedy”. Za każdym razem to uratowane życie. Potrzebujemy mobilności: busa lub przyczepy. Każda wpłata = kolejna noc pod ochroną.

🚐 Zbiórka na busa dla zespołu, pułk 1020 🎯 Cel: 500 000 ₴
🔗 Link do zbiórkiі 💳 4441 1111 2546 4663