Dans cet article (qui est le premier d'une longue série), je vais énumérer quelques tactiques que vous pouvez utiliser pour écrire du code plus propre. Au fur et à mesure que vous vous y habituerez, vous développerez un sens pour ce qui est un bon et un mauvais code.
Je vais également recommander quelques conseils généraux sur Laravel entre ces tactiques. Allons y 💪🏾 !
Lookup tables (Imbrication de conditions)
Au lieu d'écrire les conditions else if répétitives, utilisez un tableau pour rechercher la valeur souhaitée en fonction de la clé que vous avez.
Le code sera plus propre et plus lisible et vous verrez des exceptions compréhensibles si quelque chose ne va pas. Non cas limites de demi-passages.
❌ Mauvais
1if ($order->product->option->type === 'pdf') {2 $type = 'book';3} else if ($order->product->option->type === 'epub') {4 $type = 'book';5} else if ($order->product->option->type === 'license') {6 $type = 'license';7} else if ($order->product->option->type === 'artwork') {8 $type = 'creative';9} else if ($order->product->option->type === 'song') {10 $type = 'creative';11} else if ($order->product->option->type === 'physical') {12 $type = 'physical';13}1415if ($type === 'book') {16 $downloadable = true;17} else if ($type === 'license') {18 $downloadable = true;19} else if ($type === 'creative') {20 $downloadable = true;21} else if ($type === 'physical') {22 $downloadable = false;23}1if ($order->product->option->type === 'pdf') {2 $type = 'book';3} else if ($order->product->option->type === 'epub') {4 $type = 'book';5} else if ($order->product->option->type === 'license') {6 $type = 'license';7} else if ($order->product->option->type === 'artwork') {8 $type = 'creative';9} else if ($order->product->option->type === 'song') {10 $type = 'creative';11} else if ($order->product->option->type === 'physical') {12 $type = 'physical';13}1415if ($type === 'book') {16 $downloadable = true;17} else if ($type === 'license') {18 $downloadable = true;19} else if ($type === 'creative') {20 $downloadable = true;21} else if ($type === 'physical') {22 $downloadable = false;23}
✅ Correct
1$type = [2 'pdf' => 'book',3 'epub' => 'book',4 'license' => 'license',5 'artwork' => 'creative',6 'song' => 'creative',7 'physical' => 'physical',8][$order->product->option->type];910$downloadable = [11 'book' => true,12 'license' => true,13 'creative' => true,14 'physical' => false,15][$type];1$type = [2 'pdf' => 'book',3 'epub' => 'book',4 'license' => 'license',5 'artwork' => 'creative',6 'song' => 'creative',7 'physical' => 'physical',8][$order->product->option->type];910$downloadable = [11 'book' => true,12 'license' => true,13 'creative' => true,14 'physical' => false,15][$type];
Early return (retour anticipé)
Essayez d'éviter les imbrications inutiles en renvoyant une valeur rapidement. Trop d'imbrications et d'instructions else ont tendance à rendre le code plus difficile à lire.
❌ Mauvais
1if ($notificationSent) {2 $notify = false;3} else {4 if ($isActive) {5 if ($total > 100) {6 $notify = true;7 } else {8 $notify = false;9 }10 } else {11 if ($canceled) {12 $notify = true;13 } else {14 $notify = false;15 }16 }17}1819return $notify;1if ($notificationSent) {2 $notify = false;3} else {4 if ($isActive) {5 if ($total > 100) {6 $notify = true;7 } else {8 $notify = false;9 }10 } else {11 if ($canceled) {12 $notify = true;13 } else {14 $notify = false;15 }16 }17}1819return $notify;
✅ Correct
1if ($notificationSent) {2 return false;3}45if ($isActive && $total > 100) {6return true;7}89if (! $isActive && $canceled) {10 return true;11}1213return false;1if ($notificationSent) {2 return false;3}45if ($isActive && $total > 100) {6return true;7}89if (! $isActive && $canceled) {10 return true;11}1213return false;
Diviser les lignes correctement
Ne coupez pas les lignes au hasard, mais ne les rendez pas trop longues non plus.
Ouvrir un tableau avec [ et indenter les valeurs a tendance à bien fonctionner. Il en va de même pour les valeurs longues des paramètres de fonction.
D'autres bons endroits pour couper les lignes sont les appels en chaîne et les fermetures.
❌ Mauvais
1// No line split2return $this->request->session()->get($this->config->get('analytics.campaign_session_key'));34// Meaningless line split5return $this->request6 ->session()->get($this->config->get('analytics.campaign_session_key'));1// No line split2return $this->request->session()->get($this->config->get('analytics.campaign_session_key'));34// Meaningless line split5return $this->request6 ->session()->get($this->config->get('analytics.campaign_session_key'));
✅ Correct
1return $this->request->session()->get(2 $this->config->get('analytics.campaign_session_key')3);45// Closure6new EventCollection($this->events->map(function (Event $event) {7 return new Entries\Event($event->code, $event->pivot->data);8}))910// Array11$this->validate($request, [12 'code' => 'string|required',13 'name' => 'string|required',14]);1return $this->request->session()->get(2 $this->config->get('analytics.campaign_session_key')3);45// Closure6new EventCollection($this->events->map(function (Event $event) {7 return new Entries\Event($event->code, $event->pivot->data);8}))910// Array11$this->validate($request, [12 'code' => 'string|required',13 'name' => 'string|required',14]);
Ne pas créér de variables inutiles
Ne créez pas de variables lorsque vous pouvez simplement passer la valeur directement.
❌ Mauvais
1public function create()2{3 $data = [4 'resource' => 'campaign',5 'generatedCode' => Str::random(8),6 ];78 return $this->inertia('Resource/Create', $data);9}1public function create()2{3 $data = [4 'resource' => 'campaign',5 'generatedCode' => Str::random(8),6 ];78 return $this->inertia('Resource/Create', $data);9}
✅ Correct
1public function create()2{3 return $this->inertia('Resource/Create', [4 'resource' => 'campaign',5 'generatedCode' => Str::random(8),6 ]);7}1public function create()2{3 return $this->inertia('Resource/Create', [4 'resource' => 'campaign',5 'generatedCode' => Str::random(8),6 ]);7}
Créez des variables lorsqu'elles améliorent la lisibilité
Le contraire de l'astuce précédente. Parfois, la valeur provient d'un appel complexe et, à ce titre, la création d'une variable améliore la lisibilité et supprime la nécessité d'un commentaire. N'oubliez pas que le contexte est important et que votre objectif final est la lisibilité.
❌ Mauvais
1Visit::create([2 'url' => $visit->url,3 'referer' => $visit->referer,4 'user_id' => $visit->userId,5 'ip' => $visit->ip,6 'timestamp' => $visit->timestamp,7])->conversion_goals()->attach($conversionData);1Visit::create([2 'url' => $visit->url,3 'referer' => $visit->referer,4 'user_id' => $visit->userId,5 'ip' => $visit->ip,6 'timestamp' => $visit->timestamp,7])->conversion_goals()->attach($conversionData);
✅ Correct
1$visit = Visit::create([2 'url' => $visit->url,3 'referer' => $visit->referer,4 'user_id' => $visit->userId,5 'ip' => $visit->ip,6 'timestamp' => $visit->timestamp,7]);89$visit->conversion_goals()->attach($conversionData);1$visit = Visit::create([2 'url' => $visit->url,3 'referer' => $visit->referer,4 'user_id' => $visit->userId,5 'ip' => $visit->ip,6 'timestamp' => $visit->timestamp,7]);89$visit->conversion_goals()->attach($conversionData);
Créer des méthodes pour vos modèles pour la logique métier
Vos contrôleurs doivent être simples. Ils doivent dire des choses comme "créer une facture pour une commande". Ils ne doivent pas se préoccuper des détails de la structure de votre base de données.
Laissez cela au modèle.
❌ Mauvais
1// Create invoice for order2DB::transaction(function () use ($order) {3 $invoice = $order->invoice()->create();45 $order->pushStatus(new AwaitingShipping);67 return $invoice;8});1// Create invoice for order2DB::transaction(function () use ($order) {3 $invoice = $order->invoice()->create();45 $order->pushStatus(new AwaitingShipping);67 return $invoice;8});
✅ Correct
1$order->createInvoice();1$order->createInvoice();
Créer des classes d'action
Développons l'exemple précédent. Parfois, la création d'une classe pour une seule action permet de faire le ménage.
Les modèles doivent encapsuler la logique métier qui leur est liée, mais ils ne doivent pas être trop volumineux.
❌ Mauvais
1public function createInvoice(): Invoice2{3 if ($this->invoice()->exists()) {4 throw new OrderAlreadyHasAnInvoice('Order already has an invoice.');5 }67 return DB::transaction(function () use ($order) {8 $invoice = $order->invoice()->create();910 $order->pushStatus(new AwaitingShipping);1112 return $invoice;13 });14}1public function createInvoice(): Invoice2{3 if ($this->invoice()->exists()) {4 throw new OrderAlreadyHasAnInvoice('Order already has an invoice.');5 }67 return DB::transaction(function () use ($order) {8 $invoice = $order->invoice()->create();910 $order->pushStatus(new AwaitingShipping);1112 return $invoice;13 });14}
✅ Correct
1// Order model2public function createInvoice(): Invoice3{4 if ($this->invoice()->exists()) {5 throw new OrderAlreadyHasAnInvoice('Order already has an invoice.');6 }78 return app(CreateInvoiceForOrder::class)($this);9}1011// Action class12class CreateInvoiceForOrder13{14 public function __invoke(Order $order): Invoice15 {16 return DB::transaction(function () use ($order) {17 $invoice = $order->invoice()->create();1819 $order->pushStatus(new AwaitingShipping);2021 return $invoice;22 });23 }24}1// Order model2public function createInvoice(): Invoice3{4 if ($this->invoice()->exists()) {5 throw new OrderAlreadyHasAnInvoice('Order already has an invoice.');6 }78 return app(CreateInvoiceForOrder::class)($this);9}1011// Action class12class CreateInvoiceForOrder13{14 public function __invoke(Order $order): Invoice15 {16 return DB::transaction(function () use ($order) {17 $invoice = $order->invoice()->create();1819 $order->pushStatus(new AwaitingShipping);2021 return $invoice;22 });23 }24}
Utiliser des FormRequest
C'est un endroit idéal pour cacher une logique de validation complexe.
Mais attention, c'est exactement ce qu'il faut faire : cacher des choses. Lorsque la logique de validation est simple, il n'y a rien de mal à la faire dans le contrôleur. La déplacer vers une demande de formulaire la rend moins explicite.
1/**2* Get the validation rules that apply to the request.3*4* @return array5*/6public function rules()7{8 return [9 'title' => 'required|unique:posts|max:255',10 'body' => 'required',11 ];12}1/**2* Get the validation rules that apply to the request.3*4* @return array5*/6public function rules()7{8 return [9 'title' => 'required|unique:posts|max:255',10 'body' => 'required',11 ];12}
Utiliser les événements
Envisagez de décharger certaines logiques des contrôleurs vers les événements. Par exemple, lors de la création de modèles. L'avantage est que la création de ces modèles fonctionnera de la même manière partout (contrôleurs, tâches, ...) et le contrôleur a un souci de moins pour les détails du schéma de la base de données.
❌ Mauvais
1// Only works in this place & concerns it with2// details that the model should care about.3if (! isset($data['name'])) {4 $data['name'] = $data['code'];5}67$conversion = Conversion::create($data);1// Only works in this place & concerns it with2// details that the model should care about.3if (! isset($data['name'])) {4 $data['name'] = $data['code'];5}67$conversion = Conversion::create($data);
✅ Correct
1$conversion = Conversion::create($data);23// Model4class ConversionGoal extends Model5{6 public static function booted()7 {8 static::creating(function (self $model) {9 $model->name ??= $model->code;10 });11 }12}1$conversion = Conversion::create($data);23// Model4class ConversionGoal extends Model5{6 public static function booted()7 {8 static::creating(function (self $model) {9 $model->name ??= $model->code;10 });11 }12}
Extraire des méthodes
Si une méthode est trop longue ou complexe et qu'il est difficile de comprendre ce qui se passe exactement, divisez la logique en plusieurs méthodes.
1public function handle(Request $request, Closure $next)2{3 // We extracted 3 long methods into separate methods.4 $this->trackVisitor();5 $this->trackCampaign();6 $this->trackTrafficSource($request);78 $response = $next($request);910 $this->analytics->log($request);1112 return $response;13}1public function handle(Request $request, Closure $next)2{3 // We extracted 3 long methods into separate methods.4 $this->trackVisitor();5 $this->trackCampaign();6 $this->trackTrafficSource($request);78 $response = $next($request);910 $this->analytics->log($request);1112 return $response;13}
Créer des fonctions d'aide (helper functions)
Si vous répétez souvent un code, demandez-vous si l'extraire vers une fonction d'aide ne rendrait pas le code plus propre.
1// app/helpers.php, autoloaded in composer.json2function money(int $amount, string $currency = null): Money3{4 return new Money($amount, $currency ?? config('shop.base_currency'));5}67function html2text($html = ''): string8{9 return str_replace(' ', ' ', strip_tags($html));10}1// app/helpers.php, autoloaded in composer.json2function money(int $amount, string $currency = null): Money3{4 return new Money($amount, $currency ?? config('shop.base_currency'));5}67function html2text($html = ''): string8{9 return str_replace(' ', ' ', strip_tags($html));10}