You might also want to check out the real-world Laravel example application
Traductions:
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)
Principe de responsabilité unique
Gros Modèles, contrôleurs maigres
La logique métier doit être dans une classe de service
Ne mettez pas JS et CSS dans les templates Blade et ne mettez pas de HTML dans les classes PHP
Utilisez des fichiers de configuration et de langue, des constantes au lieu du texte dans le code
Utiliser les outils standard de Laravel acceptés par la communauté
Suivre les conventions de nommage de Laravel
Utilisez une syntaxe plus courte et plus lisible dans la mesure du possible
Utilisez un conteneur IoC ou des façades au lieu de la nouvelle classe
Ne pas obtenir directement les données du fichier .env
directement
Une classe et une méthode ne devraient avoir qu'une seule responsabilité.
Mal:
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;
}
}
Bien:
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;
}
Placez toute la logique liée à la base de données dans les modèles Eloquent ou dans les classes du Repository si vous utilisez le générateur de requêtes ou des requêtes SQL brutes.
Mal:
public function index()
{
$clients = Client::verified()
->with(['orders' => function ($q) {
$q->where('created_at', '>', Carbon::today()->subWeek());
}])
->get();
return view('index', ['clients' => $clients]);
}
Bien:
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();
}
}
Déplacez la validation des contrôleurs vers les classes Request.
Mal:
public function store(Request $request)
{
$request->validate([
'title' => 'required|unique:posts|max:255',
'body' => 'required',
'publish_at' => 'nullable|date',
]);
...
}
Bien:
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 contrôleur ne doit avoir qu'une seule responsabilité. Par conséquent, déplacez la logique métier des contrôleurs vers les classes de service.
Mal:
public function store(Request $request)
{
if ($request->hasFile('image')) {
$request->file('image')->move(public_path('images') . 'temp');
}
...
}
Bien:
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');
}
}
}
Réutilisez le code quand vous le pouvez. SRP vous aide à éviter les doubles emplois. Réutilisez également les modèles Blade, utilisez les Eloquent scopes, etc.
Mal:
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();
}
Bien:
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();
}
Préférez utiliser Eloquent à l’utilisation de Query Builder et de requêtes SQL brutes. Préférez les collections aux tableaux
Eloquent vous permet d’écrire du code lisible et maintenable. Eloquent dispose également d'excellents outils intégrés tels que les suppressions, les événements, les scopes, etc.
Mal:
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
Bien:
Article::has('user.profile')->verified()->latest()->get();
Mal:
$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();
Bien:
$category->article()->create($request->validated());
N'exécutez pas de requêtes dans les modèles Blade et utilisez un chargement rapide (eager loading) (problème N + 1)
Mal (Pour 100 utilisateurs, 101 requêtes DB seront exécutées):
@foreach (User::all() as $user)
{{ $user->profile->name }}
@endforeach
Bien (pour 100 utilisateurs, 2 requêtes de base de données seront exécutées):
$users = User::with('profile')->get();
@foreach ($users as $user)
{{ $user->profile->name }}
@endforeach
Commentez votre code, mais préférez une méthode descriptive et des noms de variables aux commentaires
Mal:
if (count((array) $builder->getQuery()->joins) > 0)
Meilleure:
// Determine if there are any joins.
if (count((array) $builder->getQuery()->joins) > 0)
Bien:
if ($this->hasJoins())
Mal:
let article = `{{ json_encode($article) }}`;
Meilleure:
<input id="article" type="hidden" value='@json($article)'>
Or
<button class="js-fav-article" data-article='@json($article)'>{{ $article->name }}<button>
Dans un fichier Javascript:
let article = $('#article').val();
Le meilleur moyen consiste à utiliser un package PHP vers JS spécialisé pour transférer les données.
Mal:
public function isNormal()
{
return $article->type === 'normal';
}
return back()->with('message', 'Your article has been added!');
Bien:
public function isNormal()
{
return $article->type === Article::TYPE_NORMAL;
}
return back()->with('message', __('app.article_added'));
Préférez utiliser les fonctionnalités intégrées de Laravel et les packages de communauté au lieu d'utiliser des packages et des outils tiers. Tout développeur qui travaillera avec votre application à l'avenir devra apprendre de nouveaux outils. En outre, les chances d'obtenir de l'aide de la communauté Laravel sont considérablement réduites lorsque vous utilisez un package ou un outil tiers. Ne faites pas payer votre client pour cela.
Tâche | Outils standard | Outils tiers |
---|---|---|
Autorisation | Policies | Entrust, Sentinel et d'autres packages |
Compiler des assets | Laravel Mix, Vite | Grunt, Gulp, packages tiers |
Environnement de développement | Laravel Sail, Homestead | Docker |
Déploiement | Laravel Forge | Deployer et d'autre solutions |
Tests unitaires | PHPUnit, Mockery | Phpspec, Pest |
Test du navigateur | Laravel Dusk | Codeception |
DB | Eloquent | SQL, Doctrine |
Templates | Blade | Twig |
Travailler avec des données | Laravel collections | Arrays |
Validation du formulaire | Request classes | 3rd party packages, validation dans le contrôleur |
Authentification | Built-in | 3rd party packages, votre propre solution |
API D'authentification | Laravel Passport, Laravel Sanctum | 3rd party JWT et OAuth packages |
Création d'API | Built-in | Dingo API and similar packages |
Travailler avec une structure de base de données | Migrations | Travailler directement avec la structure de la base de données |
Localisation | Built-in | 3rd party packages |
Interfaces utilisateur en temps réel | Laravel Echo, Pusher | Packages tiers et utilisation directe de WebSockets |
Générer des données de test | Seeder classes, Model Factories, Faker | Création manuelle de données de test |
Planification des tâches | Laravel Task Scheduler | Scripts et packages tiers |
DB | MySQL, PostgreSQL, SQLite, SQL Server | MongoDB |
Suivre Normes PSR.
Suivez également les conventions de nommage acceptées par la communauté Laravel:
Quoi | Comment | Bien | Mal |
---|---|---|---|
Controller | singulier | ArticleController | |
Route | pluriel | articles/1 | |
Route nommée | snake_case avec notation par points | users.show_active | |
Model | singulier | User | |
Relations hasOne or belongsTo | singulier | articleComment | |
Toutes les autres relations | pluriel | articleComments | |
Table | plurielle | article_comments | |
Table pivot | noms des modèles au singulier dans l'ordre alphabétique | article_user | |
Colonne de table | snake_case sans nom de modèle | meta_title | |
Attribut du Model | snake_case | $model->created_at | |
Foreign key | Nom du modèle au singulier avec _id comme suffix | article_id | |
Primary key | - | id | |
Migration | - | 2017_01_01_000000_create_articles_table | |
Méthode | camelCase | getAll | |
Méthodes dans le controlleur de ressource | table | store | |
Méthode dans une classe de test | camelCase | testGuestCannotSeeArticle | |
Variable | camelCase | $articlesWithAuthor | |
Collection | descriptif, pluriel | $activeUsers = User::active()->get() | |
Object | descriptif, singulier | $activeUser = User::active()->first() | |
Index de fichier de config et de langage | snake_case | articles_enabled | |
Vue | kebab-case | show-filtered.blade.php | |
Config | snake_case | google_calendar.php | |
Contract (interface) | adjectif ou nom | AuthenticationInterface | |
Trait | adjectif | Notifiable | |
Trait (PSR) | adjective | NotifiableTrait | |
Enum | singular | UserType | |
FormRequest | singular | UpdateUserRequest | |
Seeder | singular | UserSeeder |
Mal:
$request->session()->get('cart');
$request->input('name');
Bien:
session('cart');
$request->name;
Plus d'exemples:
Syntaxe commune | Syntaxe plus courte et plus lisible |
---|---|
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 nouvelle syntaxe de classe crée un couplage étroit entre les classes et complique les tests. Utilisez plutôt le conteneur IoC ou les façades.
Mal:
$user = new User;
$user->create($request->validated());
Bien:
public function __construct(User $user)
{
$this->user = $user;
}
...
$this->user->create($request->validated());
Passez les données aux fichiers de configuration à la place, puis utilisez la fonction d'assistance config ()
pour utiliser les données dans une application.
Mal:
$apiKey = env('API_KEY');
Bien:
// config/api.php
'key' => env('API_KEY'),
// Use the data
$apiKey = config('api.key');
Stocker les dates au format standard. Utiliser des accesseurs et des mutateurs pour modifier le format de date
Mal:
{{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->toDateString() }}
{{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->format('m-d') }}
Bien:
// Model
protected $casts = [
'ordered_at' => 'datetime',
];
public function getSomeDateAttribute($date)
{
return $date->format('m-d');
}
// View
{{ $object->ordered_at->toDateString() }}
{{ $object->ordered_at->some_date }}
Ne mettez jamais aucune logique dans les fichiers de routes.
Minimisez l'utilisation de PHP vanilla dans les modèles de blade.