You might also want to check out the real-world Laravel example application
Tłumaczenia:
Nederlands (by Protoqol)
Indonesia (by P0rguy, Doni Ahmad)
한국어 (by cherrypick)
ภาษาไทย (by kongvut sangkla)
বাংলা (by Anowar Hossain)
فارسی (by amirhossein baghaie)
Українська (by Tenevyk)
Tiếng Việt (by Chung Nguyễn)
Español (by César Escudero)
Français (by Mikayil S.)
Polski (by Karol Pietruszka)
Deutsch (by Sujal Patel)
Italiana (by Sujal Patel)
Azərbaycanca (by Maharramoff)
العربية (by ahmedsaoud31)
اردو (by RizwanAshraf1)
Zasada pojedynczej odpowiedzialności
Grube modele, chude kontrolery
Logika biznesowa powinna znajdować się w klasie Service
Nie wykonuj zapytań w szablonach Blade oraz używaj eager loading-u (problem N + 1)
Komentuj swój kod, ale preferuj opisowe nazwy metod i zmiennych zamiast komentarzy
Używaj plików konfiguracyjnych oraz językowych, stałych zamiast tekstu w kodzie
Używaj standardowych narzędzi Laravel-a zaakceptowanych przez społeczność
Postępuj zgodnie z konwencją nazewniczą Laravel-a
W miarę możliwości używaj krótszej i bardziej czytelnej składni
Użyj kontenera IoC lub fasad zamiast nowych klas
Nie pobieraj wartości z pliku .env
bezpośrednio
Przechowuj daty w standardowym formacie. Używaj akcesorów i mutatorów do modyfikacji formatów.
Klasa i metoda powinny mieć tylko jedną odpowiedzialność.
Źle:
public function getFullNameAttribute()
{
if (auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified()) {
return 'Pan ' . $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 'Pan ' . $this->first_name . ' ' . $this->middle_name . ' ' . $this->last_name;
}
public function getFullNameShort()
{
return $this->first_name[0] . '. ' . $this->last_name;
}
Umieszczaj całą logikę związaną z DB w modelach Eloquent-a lub w klasach Repozytorium.
Ź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ś walidację z kontrolerów do klas Request.
Ź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',
];
}
}
Kontroler musi mieć tylko jedną odpowiedzialność, więc przenieś logikę biznesową z kontrolerów do klas Service.
Ź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');
}
}
}
Używaj kod ponownie, kiedy tylko możesz. SRP pomoże Ci uniknąć duplikatów. Ponadto, ponownie używaj szablonów Blade, używaj scope-ów Eloquent-a itp.
Ź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();
}
Preferuj używanie modeli Eloquent-a ponad klasy Query Builder lub surowe zapytania SQL. Staraj się używać kolecji zamist tablic
Eloquent pozwala na pisanie czytelnego i łatwego w utrzymaniu kodu. Ponadto, Eloquent ma wbudowane świetne narzędzia, takie jak miękkie usuwanie, zdarzenia, scope-y itp.
Ź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();
Źle:
$article = new Article;
$article->title = $request->title;
$article->content = $request->content;
$article->verified = $request->verified;
// Dodaj kategorię do artykułu
$article->category_id = $category->id;
$article->save();
Dobrze:
$category->article()->create($request->validated());
Ź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 tylko 2 zapytania do bazy danych):
$users = User::with('profile')->get();
@foreach ($users as $user)
{{ $user->profile->name }}
@endforeach
Źle:
if (count((array) $builder->getQuery()->joins) > 0)
Lepiej:
// Ustal czy istnieją jakieś join-y
if (count((array) $builder->getQuery()->joins) > 0)
Dobrze:
if ($this->hasJoins())
Nie umieszczaj kodu JS i CSS w szablonach Blade oraz nie osadzaj żadnego kodu HTML wewnątrz klas PHP.
Ź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();
Najlepszym sposobem jest użycie specialnego obiektu do transferu danych pomiędzy PHP i JS.
Źle:
public function isNormal()
{
return $article->type === 'normal';
}
return back()->with('message', 'Twój artykuł został dodany!');
Dobrze:
public function isNormal()
{
return $article->type === Article::TYPE_NORMAL;
}
return back()->with('message', __('app.article_added'));
Preferuj używanie wbudowanych funkcjonalności Laravel-a i paczek społecznościowych zamiast używania paczek i narzędzi innych podmiotów. Każdy programista, który będzie pracował z twoją aplikacją w przyszłości, będzie musiał nauczyć się nowych narzędzi. Ponadto, szanse na uzyskanie pomocy od społeczności Laravel-a są znacznie mniejsze, gdy używasz paczek lub narzędzi innych podmiotów. Nie każ swojemu klientowi za to płacić.
Zadanie | Standardowe narzędzia | Narzędzia innych podmiotów |
---|---|---|
Autoryzacja | Laravel Policies | Entrust, Sentinel i inne paczki |
Kompilowanie zasobów | Laravel Mix, Vite | Grunt, Gulp oraz inne |
Środowisko pracy | Laravel Sail, Homestead | Docker |
Wdrażanie | Laravel Forge | Deployer i inne rozwiązania |
Testy jednostkowe | PHPUnit, Mockery | Phpspec, Pest |
Testy przeglądarkowe | Laravel Dusk | Codeception |
Baza danych | Eloquent | SQL, Doctrine |
Szablony widoków | Blade | Twig |
Praca z danymi | kolekcje Laravel-a | natywne tablice |
Walidacja formularzy | klasy Request | inne paczki, walidacja w kontrolerze |
Uwierzytelnianie | wbudowane | inne paczki, Twoje własne rozwiązanie |
Uwierzytelnianie API | Laravel Passport, Laravel Sanctum | inne paczki JWT oraz paczki OAuth |
Tworzenie API | wbudowane | Dingo API oraz podobne paczki |
Praca ze strukturą bazy danych | wbudowane migracje | bezpośrednia praca ze strukturą |
Lokalizacja | wbudowane | inne paczki |
Interfejsy użytkownika w czasie rzeczywistym | Laravel Echo, Pusher | inne paczki oraz bezpośrednia praca z WebSockets |
Generowanie danych testowych | klasy Seeder-ów, fabryki modeli, Faker | manualne tworzenie danych testowych |
Planowanie zadań | Laravel Task Scheduler | skrypty oraz inne paczki |
Baza danych | MySQL, PostgreSQL, SQLite, SQL Server | MongoDB |
Stosuj standardy PSR.
Przestrzegaj również konwencji nazewniczych przyjętych przez społeczność Laravel-a
Zagadnienie | Konwencja | Dobrze | Źle |
---|---|---|---|
Kontrolery | liczba pojedyncza | ArticleController | |
Ścieżka URL | liczba mnoga | articles/1 | |
Nazwana ścieżka URL | snake_case wraz z notacją kropkową | users.show_active | |
Model | liczba pojedyncza | User | |
relacje hasOne lub belongsTo | liczba pojedyncza | articleComment | |
Wszystkie pozostałe relacje | liczba mnoga | articleComments | |
Table | liczba mnoga | article_comments | |
Tabela przestawna (Pivot) | nazwy modeli w liczbie pojedynczej w kolejności alfabetycznej | article_user | |
Kolumna w tabeli | snake_case bez nazwy modelu | meta_title | |
Właściwość modelu | snake_case | $model->created_at | |
Klucz obcy | nazwa modelu w liczbie pojedynczej z przyrostkiem _id | article_id | |
Klucz podstawowy | - | id | |
Migracja | - | 2017_01_01_000000_create_articles_table | |
Metoda | camelCase | getAll | |
Metoda w kontrolerze zasobu | zobacz tabelę | store | |
Metoda w klasie testowania | camelCase | testGuestCannotSeeArticle | |
Zmienna | camelCase | $articlesWithAuthor | |
Kolekcja | opisowo, liczba mnoga | $activeUsers = User::active()->get() | |
Obiekt | opisowo, liczba pojedyncza | $activeUser = User::active()->first() | |
Indeks plików konfiguracyjnych i językowych | snake_case | articles_enabled | |
Widok | kebab-case | show-filtered.blade.php | |
Pliki konfiguracyjne | snake_case | google_calendar.php | |
Kontrakt (interfejs) | przymiotnik lub rzeczownik | AuthenticationInterface | |
Cecha (trait) | przymiotnik | Notifiable | |
Trait (PSR) | adjective | NotifiableTrait | |
Enum | singular | UserType | |
FormRequest | singular | UpdateUserRequest | |
Seeder | singular | UserSeeder |
Źle:
$request->session()->get('cart');
$request->input('name');
Dobrze:
session('cart');
$request->name;
Więcej przykładów:
Powszechna składnia | Krótsza i bardziej czytelna 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 (w PHP 8: $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 tworzenia nowych klas tworzy ścisłe sprzężenie pomiędzy nimi i komplikuje testowanie. Zamiast tego używaj kontenera IoC lub fasad.
Źle:
$user = new User;
$user->create($request->validated());
Dobrze:
public function __construct(User $user)
{
$this->user = $user;
}
...
$this->user->create($request->validated());
Zamiast tego wstaw je do plików konfiguracyjnych, a następnie użyj funkcji pomocniczej config()
aby użyć tych danych w aplikacji.
Źle:
$apiKey = env('API_KEY');
Dobrze:
// config/api.php
'key' => env('API_KEY'),
// Użyj danych
$apiKey = config('api.key');
Ź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 $casts = [
'ordered_at' => 'datetime',
];
public function getSomeDateAttribute($date)
{
return $date->format('m-d');
}
// Widok
{{ $object->ordered_at->toDateString() }}
{{ $object->ordered_at->some_date }}
NNigdy nie umieszczaj żadnej logiki w plikach ścieżek URL (routes/*.php).
Zminimalizuj użycie natywnego kodu PHP w szablonach Blade.