Te poate interesa si exemplul real de aplicație Laravel
Traduceri:
Nederlands (by Protoqol)
한국어 (by cherrypick)
ภาษาไทย (by kongvut sangkla)
فارسی (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)
العربية (by ahmedsaoud31)
اردو (by RizwanAshraf1)
Principiul responsabilității unice
Modele voluminoase, controllere scurte
Păstrează functiile logice in clase de serviciu
Evita duplicarea codului (DRY)
Fragmentează datele pentru sarcini care necesită multe date
Preferă denumiri descriptive pentru metode și variabile în loc de comentarii
Nu include cod JS și CSS în șabloanele Blade și nu include HTML în clasele PHP
Utilizează fișierele de configurație și de limbă, folosește constante în loc de text în cod
Utilizează instrumentele standard Laravel acceptate de comunitate
Respectă convențiile de denumire Laravel
Folosește sintaxa mai scurtă și mai ușor de citit, acolo unde este posibil
Folosește Containerul IoC / Service în loc de new Class
Evită să stochezi date direct în fișierele .env
O clasă și o metodă ar trebui să aibă doar o singură responsabilitate.
Greșit:
public function getFullNameAttribute(): string
{
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;
}
}
Corect:
public function getFullNameAttribute(): string
{
return $this->isVerifiedClient() ? $this->getFullNameLong() : $this->getFullNameShort();
}
public function isVerifiedClient(): bool
{
return auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified();
}
public function getFullNameLong(): string
{
return 'Mr. ' . $this->first_name . ' ' . $this->middle_name . ' ' . $this->last_name;
}
public function getFullNameShort(): string
{
return $this->first_name[0] . '. ' . $this->last_name;
}
Mută toată logica legată de baza de date în modelele Eloquent.
Greșit:
public function index()
{
$clients = Client::verified()
->with(['orders' => function ($q) {
$q->where('created_at', '>', Carbon::today()->subWeek());
}])
->get();
return view('index', ['clients' => $clients]);
}
Corect:
public function index()
{
return view('index', ['clients' => $this->client->getWithNewOrders()]);
}
class Client extends Model
{
public function getWithNewOrders(): Collection
{
return $this->verified()
->with(['orders' => function ($q) {
$q->where('created_at', '>', Carbon::today()->subWeek());
}])
->get();
}
}
Mută validarea din controllere în clase de tip Request.
Greșit:
public function store(Request $request)
{
$request->validate([
'title' => 'required|unique:posts|max:255',
'body' => 'required',
'publish_at' => 'nullable|date',
]);
...
}
Corect:
public function store(PostRequest $request)
{
...
}
class PostRequest extends Request
{
public function rules(): array
{
return [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
'publish_at' => 'nullable|date',
];
}
}
Un controller trebuie să aibă doar o singură responsabilitate, așa că mută logica aplicației din controllere în clase de serviciu.
Greșit:
public function store(Request $request)
{
if ($request->hasFile('image')) {
$request->file('image')->move(public_path('images') . 'temp');
}
...
}
Corect:
public function store(Request $request)
{
$this->articleService->handleUploadedImage($request->file('image'));
...
}
class ArticleService
{
public function handleUploadedImage($image): void
{
if (!is_null($image)) {
$image->move(public_path('images') . 'temp');
}
}
}
Reutilizează codul atunci când poți. SRP te ajută să eviți duplicarea. De asemenea, reutilizează template-urile Blade, utilizează Eloquent scopes, etc.
Greșit:
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();
}
Corect:
public function scopeActive($q)
{
return $q->where('verified', true)->whereNotNull('deleted_at');
}
public function getActive(): Collection
{
return $this->active()->get();
}
public function getArticles(): Collection
{
return $this->whereHas('user', function ($q) {
$q->active();
})->get();
}
Preferă utilizarea Eloquent în locul Query Builder și a interogărilor SQL brute. Preferă colecțiile în locul matricilor
Eloquent îți permite să scrii cod ușor de citit și întreținut. De asemenea, Eloquent dispune de unelte încorporate excelente, cum ar fi ștergerile soft, evenimentele, domeniile etc.
Greșit:
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
Corect:
Article::has('user.profile')->verified()->latest()->get();
Greșit:
$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();
Corect:
$category->article()->create($request->validated());
Evită executarea interogărilor în template-urile Blade și utilizează încărcarea eager (problema N + 1)
Greșit (pentru 100 de utilizatori, vor fi executate 101 interogări în baza de date):
@foreach (User::all() as $user)
{{ $user->profile->name }}
@endforeach
Corect (pentru 100 de utilizatori, vor fi executate doar 2 interogări în baza de date):
$users = User::with('profile')->get();
@foreach ($users as $user)
{{ $user->profile->name }}
@endforeach
Greșit:
$users = $this->get();
foreach ($users as $user) {
...
}
Corect:
$this->chunk(500, function ($users) {
foreach ($users as $user) {
...
}
});
Greșit:
// Determină dacă există vreun join
if (count((array) $builder->getQuery()->joins) > 0)
Corect:
if ($this->hasJoins())
Greșit:
let article = `{{ json_encode($article) }}`;
Corect:
<input id="article" type="hidden" value='@json($article)'>
Alternativ:
<button class="js-fav-article" data-article='@json($article)'>{{ $article->name }}<button>
Într-un fișier Javascript:
let article = $('#article').val();
Cea mai bună metodă este de a utiliza un pachet specializat PHP către JS pentru a transfera datele.
Greșit:
public function isNormal(): bool
{
return $article->type === 'normal';
}
return back()->with('message', 'Your article has been added!');
Corect:
public function isNormal()
{
return $article->type === Article::TYPE_NORMAL;
}
return back()->with('message', __('app.article_added'));
Preferă să utilizezi funcționalitățile integrate ale Laravel și pachetele comunității în locul folosirii pachetelor și instrumentelor terțe. Orice dezvoltator care va lucra cu aplicația ta în viitor va trebui să învețe noi instrumente. De asemenea, șansele de a primi ajutor din partea comunității Laravel sunt semnificativ mai mici atunci când folosești un pachet sau un instrument terț. Nu face clientul să plătească pentru asta.
Task | Instrumente standard | Instrumente terțe |
---|---|---|
Autorizare | Politici | Entrust, Sentinel și alte pachete |
Compilarea asset-urilor | Laravel Mix, Vite | Grunt, Gulp, pachete terțe |
Mediu de dezvoltare | Laravel Sail, Homestead | Docker |
Implementare | Laravel Forge | Deployer și alte soluții |
Testare unitară | PHPUnit, Mockery | Phpspec, Pest |
Testare în browser | Laravel Dusk | Codeception |
DB | Eloquent | SQL, Doctrine |
Șabloane | Blade | Twig |
Lucrul cu date | Laravel collections | Matrice |
Validarea formularului | Clasele Request | Pachete terțe, validarea în controlor |
Autentificare | Încorporată | Pachete terțe, soluția proprie |
Autentificare API | Laravel Passport, Laravel Sanctum | Pachete terțe JWT și OAuth |
Crearea API-ului | Încorporată | Dingo API și pachete similare |
Lucrul cu structura DB-ului | Migrații | Lucrul direct cu structura DB-ului |
Localizare | Încorporată | Pachete terțe |
Interfețe în timp real pentru utilizatori | Laravel Echo, Pusher | Pachete terțe și lucrul direct cu WebSockets |
Generarea datelor de testare | Clase Seeder, Fabrici de modele, Faker | Crearea manuală a datelor de testare |
Programarea sarcinilor | Planificator de sarcini Laravel | Scripturi și pachete terțe |
DB | MySQL, PostgreSQL, SQLite, SQL Server | MongoDB |
Respectă standardele PSR.
De asemenea, respectă convențiile de denumire acceptate de comunitatea Laravel:
Ce | Cum | Corect | Greșit |
---|---|---|---|
Controller | singular | ArticleController | |
Route | plural | articles/1 | |
Nume rută | snake_case cu notație punctată | users.show_active | |
Model | singular | User | |
Relație hasOne sau belongsTo | singular | articleComment | |
Toate celelalte relații | plural | articleComments | |
Tabel | plural | article_comments | |
Tabel pivot | nume model singular în ordine alfabetică | article_user | |
Coloană tabel | snake_case fără numele modelului | meta_title | |
Proprietate model | snake_case | $model->created_at | |
Cheie străină | nume model singular cu sufixul _id | article_id | |
Cheie primară | - | id | |
Migrare | - | 2017_01_01_000000_create_articles_table | |
Metodă | camelCase | getAll | |
Metodă în controller-ul de resurse | tabel | store | |
Metodă în clasă de testare | camelCase | testGuestCannotSeeArticle | |
Variabilă | camelCase | $articlesWithAuthor | |
Colecție | descripțivă, plural | $activeUsers = User::active()->get() | |
Obiect | descripțiv, singular | $activeUser = User::active()->first() | |
Fișiere de configurație și limbă - index | snake_case | articles_enabled | |
Vizualizare | kebab-case | show-filtered.blade.php | |
Configurație | snake_case | google_calendar.php | |
Contract (interfață) | adjectiv sau substantiv | AuthenticationInterface | |
Trait | adjectiv | Notifiable | |
Trait (PSR) | adjectiv | NotifiableTrait | |
Enum | singular | UserType | |
FormRequest | singular | UpdateUserRequest | |
Seeder | singular | UserSeeder |
Greșit:
$request->session()->get('cart');
$request->input('name');
Corect:
session('cart');
$request->name;
Mai multe exemple:
Sintaxă comună | Sintaxă mai scurtă și mai ușor de citit |
---|---|
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 (în 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') |
Sintaxa new Class creează un cuplaj strâns între clase și complică testarea. Folosește Containerul IoC / Service pentru a rezolva dependințele.
Greșit:
$user = new User;
$user->create($request->validated());
Corect:
public function __construct(User $user)
{
$this->user = $user;
}
...
$this->user->create($request->validated());
Sau mai bine, utilizează injecția de dependențe:
public function __construct(User $user)
{
$this->user = $user;
}
public function store(Request $request)
{
$this->user->create($request->all());
}
În loc să preiei datele direct din fișierul .env
, este mai indicat să le treci în fișierele de configurare și să utilizezi funcția ajutătoare config()
pentru a accesa aceste date în aplicație.
Greșit:
$apiKey = env('API_KEY');
Corect:
// config/api.php
'key' => env('API_KEY'),
// Use the data
$apiKey = config('api.key');
Stochează datele în format standard. Folosește accesorii și mutatori pentru a modifica formatul datelor
Un șir de caractere ca dată este mai puțin fiabil decât o instanță de obiect, cum ar fi o instanță Carbon. Se recomandă trecerea obiectelor Carbon între clase în locul șirurilor de caractere. Redarea ar trebui să se facă în stratul de prezentare (template-uri):
Greșit:
{{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->toDateString() }}
{{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->format('m-d') }}
Corect:
// Model
protected $casts = [
'ordered_at' => 'datetime',
];
// Blade view
{{ $object->ordered_at->toDateString() }}
{{ $object->ordered_at->format('m-d') }}
Evită utilizarea de modele și instrumente străine pentru Laravel și framework-urile similare (cum ar fi RoR, Django). Dacă îți place abordarea Symfony (sau Spring) pentru construirea aplicațiilor, este o idee bună să folosești aceste framework-uri în schimb.
Nu pune niciodată logica în fișierele de rute.
Minimizează utilizarea PHP vanilla în template-urile Blade.
Folosește o bază de date în memorie pentru teste.
Evită suprascrierea funcționalităților standard ale framework-ului pentru a evita probleme legate de actualizarea versiunii framework-ului și multe alte probleme.
Folosește sintaxa PHP modernă acolo unde este posibil, dar nu uita de lizibilitate.
Evită utilizarea View Composers și a altor instrumente similare, cu excepția cazului în care cunoști cu adevărat ce faci. În majoritatea cazurilor, există o modalitate mai bună de rezolvare a problemei.