You might also want to check out the real-world Laravel example application
Translations:
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)
Principio della sola responsabilità
Modelli grassi, controller skinny
La logica aziendale dovrebbe essere nella classe di servizio
Non eseguire query nei modelli Blade e utilizzare il caricamento desideroso (problema N + 1)
Non inserire JS e CSS nei modelli Blade e non inserire HTML nelle classi PHP
Usa file di configurazione e lingua, costanti anziché testo nel codice
Utilizzare gli strumenti standard Laravel accettati dalla community
Segui le convenzioni di denominazione di Laravel
Utilizzare la sintassi più breve e più leggibile ove possibile
Utilizzare il contenitore o le facciate IoC anziché la nuova classe
Non ottiene direttamente i dati dal file .env
Una classe e un metodo dovrebbero avere una sola responsabilità.
Sbagliato:
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;
}
}
Giusto:
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;
}
Inserisci tutta la logica legata al DB nei Model Eloquent oppure nei Repository a seconda che tu stia usando il Query Builder o le query SQL raw.
Sbagliato:
public function index()
{
$clients = Client::verified()
->with(['orders' => function ($q) {
$q->where('created_at', '>', Carbon::today()->subWeek());
}])
->get();
return view('index', ['clients' => $clients]);
}
Giusto:
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();
}
}
Sposta le logiche di validazione dai controller alle Request class.
Sbagliato:
public function store(Request $request)
{
$request->validate([
'title' => 'required|unique:posts|max:255',
'body' => 'required',
'publish_at' => 'nullable|date',
]);
...
}
Giusto:
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',
];
}
}
Un controller deve avere una sola responsabilità, quindi sposta la logica di business dai controller alle classi di servizio.
Sbagliato:
public function store(Request $request)
{
if ($request->hasFile('image')) {
$request->file('image')->move(public_path('images') . 'temp');
}
...
}
Giusto:
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');
}
}
}
Riutilizzare il codice quando è possibile. Il Principio di Singola Responsabilità (SRP) ti aiuta a evitare la duplicazione. Inoltre, riutilizza i template blade, usa gli eloquenti scopes, ecc.
Sbagliato:
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();
}
Giusto:
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();
}
Favorisci l'utilizzo dei Model Eloquent rispetto al Query Builder e alle query SQL raw. Preferisci le Collection agli array
Eloquent ti consente di scrivere codice leggibile e manutenibile. Inoltre, Eloquent ha ottimi strumenti integrati come eliminazioni soft, eventi, scopes, ecc.
Sbagliato:
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
Giusto:
Article::has('user.profile')->verified()->latest()->get();
Sbagliato:
$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();
Giusto:
$category->article()->create($request->validated());
Sbagliato (per 100 utenti, verranno eseguite 101 query DB):
@foreach (User::all() as $user)
{{ $user->profile->name }}
@endforeach
Giusto (per 100 utenti, verranno eseguite 2 query DB):
$users = User::with('profile')->get();
@foreach ($users as $user)
{{ $user->profile->name }}
@endforeach
Sbagliato:
if (count((array) $builder->getQuery()->joins) > 0)
Meglio:
// Determine if there are any joins.
if (count((array) $builder->getQuery()->joins) > 0)
Giusto:
if ($this->hasJoins())
Sbagliato:
let article = `{{ json_encode($article) }}`;
Meglio:
<input id="article" type="hidden" value='@json($article)'>
Or
<button class="js-fav-article" data-article='@json($article)'>{{ $article->name }}<button>
In un file Javascript:
let article = $('#article').val();
Il modo migliore è utilizzare il pacchetto PHP-JS specializzato per trasferire i dati.
Sbagliato:
public function isNormal()
{
return $article->type === 'normal';
}
return back()->with('message', 'Your article has been added!');
Giusto:
public function isNormal()
{
return $article->type === Article::TYPE_NORMAL;
}
return back()->with('message', __('app.article_added'));
Favorisci l'utilizzo delle funzionalità integrate in Laravel e i pacchetti della community anziché utilizzare pacchetti e strumenti di terze parti. Altrimenti, qualsiasi sviluppatore che lavorerà con la tua app in futuro dovrà imparare nuovi strumenti. Inoltre, le possibilità di ottenere aiuto dalla comunità Laravel sono significativamente inferiori quando si utilizza un pacchetto o uno strumento di terze parti. Non far pagare il tuo cliente per quello.
Task | Strumenti standard | Strumenti di terze parti |
---|---|---|
Autorizzazione | Policies | Entrust, Sentinel e altri pacchetti |
Compiling assets | Laravel Mix, Vite | Grunt, Gulp, pacchetti terzi |
Ambiente di sviluppo | Laravel Sail, Homestead | Docker |
Distribuzione | Laravel Forge | Deployer e altre soluzioni |
Test unitari | PHPUnit, Mockery | Phpspec, Pest |
Test dal browser | Laravel Dusk | Codeception |
DB | Eloquent, Query Builder | SQL, Doctrine |
Template | Blade | Twig |
Lavorare con i dati | Laravel collection | Array |
Validazione form | Form Request | Pacchetti di terze parti, convalida nel controller |
Autenticazione | Incorporato | Pacchetti di terze parti, la tua soluzione |
Autenticazione API | Laravel Passport, Laravel Sanctum | Pacchetti JWT e OAuth di terze parti |
Creazione dell'API | Incorporato | API Dingo e pacchetti simili |
Lavorare con la struttura DB | Migrazioni | Lavorare direttamente con la struttura DB |
Localizzazione | Incorporato | Pacchetti di terze parti |
Interfacce utente in tempo reale | Laravel Echo, Pusher | Pacchetti di terze parti e funzionamento diretto con WebSocket |
Generazione di dati di test | Seeder, Model Factories, Faker | Creazione manuale dei dati di test |
Pianificazione delle attività | Laravel Task Scheduler | Script e pacchetti di terze parti |
DB | MySQL, PostgreSQL, SQLite, SQL Server | MongoDB |
Seguire Standard PSR.
Inoltre, segui le convenzioni di denominazione accettate dalla comunità Laravel:
Cosa | Come | Giusto | Sbagliato |
---|---|---|---|
Controller | singolare | ArticleController | |
Route | plurale | articles/1 | |
Route name | snake_case con notazione punto | users.show_active | |
Model | singolare | User | |
Relazioni hasOne o belongsTo | singolare | articleComment | |
Tutte le altre relazioni | plurale | articleComments | |
Tabella | plurale | article_comments | |
Tabella pivot | nomi di modelli singolari in ordine alfabetico | article_user | |
Colonna della tabella | snake_case senza nome modello | meta_title | |
Proprietà del Model | snake_case | $ model->created_at | |
Foreign key | modello in singolare con un suffisso _id | article_id | |
Chiave primaria | - | id | |
Migration | - | 2017_01_01_000000_create_articles_table | |
Metodo | camelCase | getAll | |
Metodo nel resource controller | resource | store | |
Metodo nella test class | camelCase | testGuestCannotSeeArticle | |
Variabile | camelCase | $articolesWithAuthor | |
Collection | descrittivo, plurale | $activeUsers = User::active()->get() | |
Oggetto | descrittivo, singolare | $activeUser = User::active()->first() | |
Indice file di configurazione e lingua | snake_case | articles_enabled | |
View | kebab-case | show-filtered.blade.php | |
Config | snake_case | google_calendar.php | |
Contratto (interfaccia) | aggettivo o sostantivo | AuthenticationInterface | |
Trait | aggettivo | Notificabile | |
Trait (PSR) | adjective | NotifiableTrait | |
Enum | singular | UserType | |
FormRequest | singular | UpdateUserRequest | |
Seeder | singular | UserSeeder |
Sbagliato:
$request->session()->get('cart');
$request->input('name');
Giusto:
session('cart');
$request->name;
Più esempi:
Common syntax | Shorter and more readable syntax |
---|---|
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') |
La sintassi new Class crea un accoppiamento stretto tra le classi e complica i test. Utilizzare invece il container IoC o i Facades.
Sbagliato:
$user = new User;
$user->create($request->validated());
Giusto:
public function __construct(User $user)
{
$this->user = $user;
}
...
$this->user->create($request->validated());
Passa i dati presenti nell'.env file ai file di configurazione e quindi usa l'helper config ()
per prelevare i dati all'interno dell'applicazione.
Sbagliato:
$apiKey = env('API_KEY');
Giusto:
// config/api.php
'key' => env('API_KEY'),
// Use the data
$apiKey = config('api.key');
Memorizza le date nel formato standard. Utilizza gli accessors e i mutators per modificare il formato della data
Sbagliato:
{{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->toDateString() }}
{{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->format('m-d') }}
Giusto:
// 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 }}
Non inserire mai alcuna logica nei file di route.
Ridurre al minimo l'utilizzo di vanilla PHP nei template Blade.