Oryginał w języku angielskim (autorstwa alexeymezenin)
Niniejszy dokument nie stanowi adaptacji zasad SOLID lub jakichkolwiek wzorców. Znajdziesz tutaj najlepsze praktyki, które zwykle są nieświadomie pomijane podczas tworzenia aplikacji z wykorzystaniem frameworka Laravel.
Stosuj zasadę pojedynczej odpowiedzialności
Oszczędzaj kod w kontrolerach, kosztem modeli
Umieszczaj logikę biznesową w serwisach
Używaj Eloquent'a zamiast Query Builder'a / surowych zapytań SQL. Używaj kolekcji zamiast tablic
Masowe przypisywanie (Mass assignment)
Nie umieszczaj kodu JS i CSS w szablonach Blade, ani kodu HTML w klasach PHP
Używaj konfiguracji, plików językowych i stałych zamiast czystego tekstu w kodzie
Używaj paczek i narzędzi preferowanych przez społeczność Laravel'a
Stosuj się do konwencji nazewnictwa w Laravelu
Używaj krótszej oraz czytelniejszej składni wszędzie gdzie to możliwe
Używaj IoC container lub facades zamiast new Class
Nie pobieraj danych bezpośrednio z pliku .env
Każda klasa oraz metoda powinna mieć wyłącznie jedną odpowiedzialność. Innymi słowy powinna być stworzona i wykorzystywana tylko w jednym, określonym i z reguły prostym celu.
Źle:
public function getFullNameAttribute()
{
if (auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified()) {
return 'Mr. ' . $this->first_name . ' ' . $this->middle_name . ' ' . $this->last_name;
} else {
return $this->first_name[0] . '. ' . $this->last_name;
}
}
Dobrze:
public function getFullNameAttribute()
{
return $this->isVerifiedClient() ? $this->getFullNameLong() : $this->getFullNameShort();
}
public function isVerifiedClient()
{
return auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified();
}
public function getFullNameLong()
{
return 'Mr. ' . $this->first_name . ' ' . $this->middle_name . ' ' . $this->last_name;
}
public function getFullNameShort()
{
return $this->first_name[0] . '. ' . $this->last_name;
}
Jeżeli używasz Query Builder lub surowych zapytań SQL, umieść całą logikę bazy danych w modelach (Eloquent Models) lub w klasach z repozytoriami (Repository Classes).
Źle:
public function index()
{
$clients = Client::verified()
->with(['orders' => function ($q) {
$q->where('created_at', '>', Carbon::today()->subWeek());
}])
->get();
return view('index', ['clients' => $clients]);
}
Dobrze:
public function index()
{
return view('index', ['clients' => $this->client->getWithNewOrders()]);
}
class Client extends Model
{
public function getWithNewOrders()
{
return $this->verified()
->with(['orders' => function ($q) {
$q->where('created_at', '>', Carbon::today()->subWeek());
}])
->get();
}
}
Przenieś logikę odpowiedzialną za walidację z kontrolerów do Request Classes.
Źle:
public function store(Request $request)
{
$request->validate([
'title' => 'required|unique:posts|max:255',
'body' => 'required',
'publish_at' => 'nullable|date',
]);
....
}
Dobrze:
public function store(PostRequest $request)
{
....
}
class PostRequest extends Request
{
public function rules()
{
return [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
'publish_at' => 'nullable|date',
];
}
}
Skomplikowaną logikę biznesową umieszczaj w serwisach (ServiceContainer Classes), aby ograniczyć kod w kontrolerach do niezbędnego minimum.
Źle:
public function store(Request $request)
{
if ($request->hasFile('image')) {
$request->file('image')->move(public_path('images') . 'temp');
}
....
}
Dobrze:
public function store(Request $request)
{
$this->articleService->handleUploadedImage($request->file('image'));
....
}
class ArticleService
{
public function handleUploadedImage($image)
{
if (!is_null($image)) {
$image->move(public_path('images') . 'temp');
}
}
}
Staraj się wydzielać powtarzalne części tworzonego kodu, które będzie można wykorzystywać w wielu miejscach aplikacji. Zwróć uwagę na fakt, że najwięcej reużywalnych bloków kodu można stworzyć w tych obszarach: Blate Templates, Eloquent Scopes, Service Containers, Helpers itd.
Źle:
public function getActive()
{
return $this->where('verified', 1)->whereNotNull('deleted_at')->get();
}
public function getArticles()
{
return $this->whereHas('user', function ($q) {
$q->where('verified', 1)->whereNotNull('deleted_at');
})->get();
}
Dobrze:
public function scopeActive($q)
{
return $q->where('verified', 1)->whereNotNull('deleted_at');
}
public function getActive()
{
return $this->active()->get();
}
public function getArticles()
{
return $this->whereHas('user', function ($q) {
$q->active();
})->get();
}
Eloquent pozwala na pisanie czytelnego oraz łatwego w utrzymaniu kodu. Ponadto, Eloquent posiada wiele przydatnych wbudowanych narzędzi, takich jak: miękkie usuwanie (Soft Deletes), wydarzenia (Events), Scopes itd.
Źle:
SELECT *
FROM `articles`
WHERE EXISTS (SELECT *
FROM `users`
WHERE `articles`.`user_id` = `users`.`id`
AND EXISTS (SELECT *
FROM `profiles`
WHERE `profiles`.`user_id` = `users`.`id`)
AND `users`.`deleted_at` IS NULL)
AND `verified` = '1'
AND `active` = '1'
ORDER BY `created_at` DESC
Dobrze:
Article::has('user.profile')->verified()->latest()->get();
Wykorzystuj wbudowaną funkcjonalność Mass Assignment - dzięki temu kod stanie się bardziej czytelniejszy. Nie zapomnij o walidacji danych, a także określeniu polityki bezpieczeństwa pól modelu (fillable oraz guarded).
Źle:
$article = new Article;
$article->title = $request->title;
$article->content = $request->content;
$article->verified = $request->verified;
// Add category to article
$article->category_id = $category->id;
$article->save();
Dobrze:
$category->article()->create($request->validated());
Nie wykonuj zapytań bezpośrednio w szablonach. Używaj funkcjonalności Eager loading'u (problem N + 1)
Źle (dla 100 użytkowników zostanie wykonanych 101 zapytań do bazy danych):
@foreach (User::all() as $user)
{{ $user->profile->name }}
@endforeach
Dobrze (dla 100 użytkowników zostaną wykonane 2 zapytania do bazy danych):
$users = User::with('profile')->get();
...
@foreach ($users as $user)
{{ $user->profile->name }}
@endforeach
Komentuj swój kod wszędzie gdzie to możliwe, ale postaraj się zastępować tradycyjne komentarze czytelniejszym nazywaniem metod i zmiennych.
Źle:
if (count((array) $builder->getQuery()->joins) > 0)
Lepiej:
// Determine if there are any joins.
if (count((array) $builder->getQuery()->joins) > 0)
Dobrze:
if ($this->hasJoins())
Źle:
let article = `{{ json_encode($article) }}`;
Lepiej:
<input id="article" type="hidden" value="@json($article)">
Lub
<button class="js-fav-article" data-article="@json($article)">{{ $article->name }}<button>
W pliku Javascript:
let article = $('#article').val();
Najlepzym rozwiązaniem jest użycie wyspecjalizowanego pakietu przesyłającego dane z PHP do JS.
Źle:
public function isNormal()
{
return $article->type === 'normal';
}
return back()->with('message', 'Your article has been added!');
Dobrze:
public function isNormal()
{
return $article->type === Article::TYPE_NORMAL;
}
return back()->with('message', __('app.article_added'));
Korzystaj z wbudowanych funkcjonalności Laravela bądź paczek stworzonych przez jego społeczność, zamiast używać rozwiązań i narzędzi osób trzecich. Każdy deweloper który będzie pracował z Twoją aplikacją w przyszłości, będzie musiał uczyć się ich wszystkich, co znacznie wydłuży czas wprowadzania jakichkolwiek zmian. Ponadto, szansa na uzyskanie pomocy wśród społeczności Laravela spadnie, jeżeli będziesz wykorzystywać paczki/narzędzia osób trzecich. Nie pozwól, aby Twój klient musiał płacić za nieprzemyślane wybory oraz decyzje.
Zadanie | Standardowe narzędzia | Narzędzia osób trzecich |
---|---|---|
Autoryzacja | Policies | Entrust, Sentinel i inne paczki |
Kompilowanie zasobów | Laravel Mix | Grunt, Gulp, paczki osób trzecich |
Środowisko deweloperskie | Homestead | Docker |
Integracja ciągła | Laravel Forge | Deployer i inne rozwiązania |
Testowanie jednostkowe | PHPUnit, Mockery | Phpspec |
Testy przeglądarki | Laravel Dusk | Codeception |
Interfejs (PDO) bazy danych | Eloquent | SQL, Doctrine |
Szablony | Blade | Twig |
Praca z danymi | Laravel collections | Tablice |
Walidacja formularzy | Request classes | Paczki osób trzecich, walidacja w kontrolerze |
Uwierzytelnianie | Wbudowane | Paczki osób trzecich, własne implementacje |
Uwierzytelnianie w API | Laravel Passport | Paczki JWT i OAuth osób trzecich |
Tworzenie API | Wbudowane | Dingo API i podobne paczki |
Zarządzanie strukturą bazy danych | Migrations | bezpośrednie tworzenie tabel w bazie (np. przez phpMyAdmin lub komendami) |
Tłumaczenia / Lokalizacja | Wbudowane | Paczki osób trzecich |
Interfejsy czasu rzeczywistego | Laravel Echo, Pusher | Paczki osób trzecich, własne implementacje |
Generowanie danych testowych | Seeder classes, Model Factories, Faker | Ręczne tworzenie danych testowych |
Planowanie zadań (CRON) | Laravel Task Scheduler | Paczki osób trzecich, własne skrypty |
Bazy danych | MySQL, PostgreSQL, SQLite, SQL Server | MongoDB |
Stosuj się do standardów PSR, a także do konwencji nazewnictwa w Laravelu stosowanej wśród społeczności deweloperów:
Co? | Jak? | Dobrze | Źle |
---|---|---|---|
Kontroler | liczba pojedyncza | ArticleController | |
Ścieżka URI (Route) | liczba mnoga | articles/1 | |
Named route | snake_case wraz z notacją kropkową | users.show_active | |
Model | liczba pojedyncza | User | |
Relacja hasOne lub belongsTo | liczba pojedyncza | articleComment | |
Wszystkie inne relacje | liczba mnoga | articleComments | |
Tabela | liczba mnoga | article_comments | |
Pivot table | liczba pojedyncza; nazwy modeli w kolejności alfabetycznej | article_user | |
Kolumna tabeli | snake_case bez nazwy modelu | meta_title | |
Model property | snake_case | $model->created_at | |
Klucz obcy | liczba pojedyncza; nazwa modelu z postfixem _id | article_id | |
Klucz podstawowy | - | id | |
Migracja | - | 2017_01_01_000000_create_articles_table | |
Metoda w klasie | camelCase | getAll | |
Metoda w Resource Controller | table | store | |
Metoda w klasie testowej | camelCase | testGuestCannotSeeArticle | |
Zmienna | camelCase | $articlesWithAuthor | |
Collection | liczba mnoga; opisowa | $activeUsers = User::active()->get() | |
Obiekt | liczba pojedyncza; opisowa | $activeUser = User::active()->first() | |
Pliki konfiguracyjne oraz językowe | snake_case | articles_enabled | |
Widok | snake_case | show_filtered.blade.php | |
Plik konfiguracyjny | snake_case | google_calendar.php | |
Contract (interface) | przymiotnik lub rzeczownik | Authenticatable | |
Trait | przymiotnik | Notifiable |
Źle:
$request->session()->get('cart');
$request->input('name');
Dobrze:
session('cart');
$request->name;
Więcej przykładów:
Standardowa składnia | Skrócona i czytelniejsza składnia |
---|---|
Session::get('cart') |
session('cart') |
$request->session()->get('cart') |
session('cart') |
Session::put('cart', $data) |
session(['cart' => $data]) |
$request->input('name'), Request::get('name') |
$request->name, request('name') |
return Redirect::back() |
return back() |
is_null($object->relation) ? null : $object->relation->id |
optional($object->relation)->id |
return view('index')->with('title', $title)->with('client', $client) |
return view('index', compact('title', 'client')) |
$request->has('value') ? $request->value : 'default'; |
$request->get('value', 'default') |
Carbon::now(), Carbon::today() |
now(), today() |
App::make('Class') |
app('Class') |
->where('column', '=', 1) |
->where('column', 1) |
->orderBy('created_at', 'desc') |
->latest() |
->orderBy('age', 'desc') |
->latest('age') |
->orderBy('created_at', 'asc') |
->oldest() |
->select('id', 'name')->get() |
->get(['id', 'name']) |
->first()->name |
->value('name') |
Składnia new Class tworzy ścisłe połączenie między klasami i komplikuje testowanie. Zamiast tego użyj IoC Container or Facades.
Źle:
$user = new User;
$user->create($request->validated());
Dobrze:
public function __construct(User $user)
{
$this->user = $user;
}
....
$this->user->create($request->validated());
Przekaż dane z .env
do plików konfiguracyjnych, a następnie użyj funkcji config ()
, aby użyć danych w aplikacji.
Źle:
$apiKey = env('API_KEY');
Dobrze:
// config/api.php
'key' => env('API_KEY'),
// Use the data
$apiKey = config('api.key');
Przechowuj daty w standardowych formatach. Używaj akcesorów i mutatorów aby wyświetlić je w prawidłowym formacie
Źle:
{{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->toDateString() }}
{{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->format('m-d') }}
Dobrze:
// Model
protected $dates = ['ordered_at', 'created_at', 'updated_at'];
public function getSomeDateAttribute($date)
{
return $date->format('m-d');
}
// View
{{ $object->ordered_at->toDateString() }}
{{ $object->ordered_at->some_date }}
-
Nigdy nie umieszczaj logiki w plikach ze ścieżkami (Routes Files)
-
Zminimalizuj używanie czystego PHP w szablonach Blade'a.
-
Stosuj się do najlepszych praktyk pisania czystego kodu w PHP (ang.)