Après le premier épisode de Laravel Tips Clean Code nous ouvrons l'épisode ✌🏾. Si vous n'avez pas lu la première partie nous vous invitons à vous rendre sur cet article
Éviter les classes d'aide (Helper Class)
Parfois, les dévéloppeurs mettent des helpers dans une classe.
Attention, cela peut devenir désordonné. Ceci est une classe avec uniquement des méthodes statiques utilisées comme fonctions d'aide. Il est généralement préférable de placer ces méthodes dans des classes ayant une logique connexe ou de les conserver comme fonctions globales.
❌ Mauvais
1class Helper {2 public function convertCurrency(Money $money, string $currency): self3 {4 // We're converting from current currency, to base currency and finally to the new currency5 $newCurrencyConfig = config("shop.currencies.$currency");6 $mathDecimalDifference = $newCurrencyConfig['math_decimals'] - config("shop.currencies." . config('shop.base_currency')78 return new static(9 (int) round($money->baseValue() * $newCurrencyConfig['value'] * 10**$mathDecimalDifference, 0), $currency10 );11 }12}1314// Usage:15use App\Helper;1617Helper::convertCurrency($total, 'EUR');1class Helper {2 public function convertCurrency(Money $money, string $currency): self3 {4 // We're converting from current currency, to base currency and finally to the new currency5 $newCurrencyConfig = config("shop.currencies.$currency");6 $mathDecimalDifference = $newCurrencyConfig['math_decimals'] - config("shop.currencies." . config('shop.base_currency')78 return new static(9 (int) round($money->baseValue() * $newCurrencyConfig['value'] * 10**$mathDecimalDifference, 0), $currency10 );11 }12}1314// Usage:15use App\Helper;1617Helper::convertCurrency($total, 'EUR');
✅ Correct
1class Money {2 // the other money/currency logic34 public function convertTo(string $currency): self5 {6 // We're converting from current currency, to base currency and finally to the new currency7 $newCurrencyConfig = config("shop.currencies.$currency");8 $mathDecimalDifference = $newCurrencyConfig['math_decimals'] - config("shop.currencies." . config('shop.base_currency')910 return new static(11 (int) round($this->baseValue() * $newCurrencyConfig['value'] * 10**$mathDecimalDifference, 0), $currency12 );13 }14}1516// Usage17$EURtotal = $total->convertTo('EUR');1class Money {2 // the other money/currency logic34 public function convertTo(string $currency): self5 {6 // We're converting from current currency, to base currency and finally to the new currency7 $newCurrencyConfig = config("shop.currencies.$currency");8 $mathDecimalDifference = $newCurrencyConfig['math_decimals'] - config("shop.currencies." . config('shop.base_currency')910 return new static(11 (int) round($this->baseValue() * $newCurrencyConfig['value'] * 10**$mathDecimalDifference, 0), $currency12 );13 }14}1516// Usage17$EURtotal = $total->convertTo('EUR');
Consacrez un week-end à l'apprentissage de la POO
Connaissez la différence entre les méthodes et les variables statiques/instance et la visibilité privée/protégée/publique. Apprenez également comment Laravel utilise les méthodes magiques.
Vous n'en avez pas besoin en tant que débutant, mais à mesure que votre code se développe, c'est crucial.
Ne vous contentez pas d'écrire du code procédural dans des classes
Cela relie le tip précédent avec les autres conseils ici. La POO existe pour rendre votre code plus lisible, utilisez-la. N'écrivez pas seulement du code procédural de 400 lignes dans les actions du contrôleur.
Utilisez des objets de transfert de données (DTO)
Plutôt que de passer une quantité énorme d'arguments dans un ordre spécifique, envisagez de créer un objet avec des propriétés pour stocker ces données.
Des points bonus si vous trouvez que certains comportements peuvent être déplacés dans cet objet.
❌ Mauvais
1public function log($url, $route_name, $route_data, $campaign_code, $traffic_source, $referer, $user_id, $visitor_id, $ip, $tim)2{3 // ...4}1public function log($url, $route_name, $route_data, $campaign_code, $traffic_source, $referer, $user_id, $visitor_id, $ip, $tim)2{3 // ...4}
✅ Correct
1public function log(Visit $visit)2{3 // ...4}56class Visit {7 public string $url;8 public ?string $routeName;9 public array $routeData;1011 public ?string $campaign;12 public array $trafficSource = [];1314 public ?string $referer;1516 public Carbon $timestamp;1718 // ...1920}1public function log(Visit $visit)2{3 // ...4}56class Visit {7 public string $url;8 public ?string $routeName;9 public array $routeData;1011 public ?string $campaign;12 public array $trafficSource = [];1314 public ?string $referer;1516 public Carbon $timestamp;1718 // ...1920}
Créer des objets fluent (chainage)
Vous pouvez également créer des objets avec des API fluent. Ajoutez progressivement des données par le biais d'appels distincts, et n'exigez que le strict minimum dans le constructeur.
Chaque méthode renvoie $this, vous pouvez donc vous arrêter à n'importe quel appel.
1Visit::make($url, $routeName, $routeData)2 ->withCampaign($campaign)3 ->withTrafficSource($trafficSource)4 ->withReferer($referer)5// ... etc1Visit::make($url, $routeName, $routeData)2 ->withCampaign($campaign)3 ->withTrafficSource($trafficSource)4 ->withReferer($referer)5// ... etc
Utiliser des collections personnalisées
La création de collections personnalisées peut être un excellent moyen d'obtenir une syntaxe plus expressive. Prenons cet exemple avec les totaux des commandes.
❌ Mauvais
1$total = $order->products->sum(function (OrderProduct $product) {2 return $product->price * $product->quantity * (1 + $product->vat_rate);3});1$total = $order->products->sum(function (OrderProduct $product) {2 return $product->price * $product->quantity * (1 + $product->vat_rate);3});
✅ Correct
1$order->products->total();23class OrderProductCollection extends Collection4{5 public function total()6 {7 $this->sum(function (OrderProduct $product) {8 return $product->price * $product->quantity * (1 + $product->vat_rate);9 });10 }11}1$order->products->total();23class OrderProductCollection extends Collection4{5 public function total()6 {7 $this->sum(function (OrderProduct $product) {8 return $product->price * $product->quantity * (1 + $product->vat_rate);9 });10 }11}
N'utilisez pas d'abréviations
Ne pensez pas que les longs noms de variables/méthodes sont mauvais. Ils ne le sont pas. Ils sont expressifs.
Il vaut mieux appeler une méthode longue qu'une courte et vérifier le bloc de documentation pour comprendre ce qu'elle fait. Il en va de même pour les variables. N'utilisez pas d'abréviations absurdes de 3 lettres.
❌ Mauvais
1$ord = Order::create($data);23// ...45$ord->notify();1$ord = Order::create($data);23// ...45$ord->notify();
✅ Correct
1$order = Order::create($data);23// ...45$order->sendCreatedNotification();1$order = Order::create($data);23// ...45$order->sendCreatedNotification();
Créer des traits à usage unique
L'ajout de méthodes aux classes auxquelles elles appartiennent est plus propre que la création de classes d'action pour tout, mais cela peut faire grossir les classes.
Envisagez d'utiliser des traits. Ils sont conçus principalement pour la réutilisation du code, mais il n'y a rien de mal à utiliser des traits à usage unique.
❌ Mauvais
1class Order extends Model2{34 public static function bootHasStatuses() { ... }5 public static $statusMap = [ ... ];6 public static function getStatusId($status) { ... }7 public function getPaymentStatusAttribute(): OrderPaymentStatus { ... }89 // ...1011}1class Order extends Model2{34 public static function bootHasStatuses() { ... }5 public static $statusMap = [ ... ];6 public static function getStatusId($status) { ... }7 public function getPaymentStatusAttribute(): OrderPaymentStatus { ... }89 // ...1011}
✅ Correct
1class Order extends Model2{3 use HasStatuses;45 // ...67}89trait HasStatuses10{11 public static function bootHasStatuses() { ... }12 public static $statusMap = [ ... ];13 public static function getStatusId($status) { ... }14 public function getPaymentStatusAttribute(): OrderPaymentStatus { ... }1516 // ...17}1class Order extends Model2{3 use HasStatuses;45 // ...67}89trait HasStatuses10{11 public static function bootHasStatuses() { ... }12 public static $statusMap = [ ... ];13 public static function getStatusId($status) { ... }14 public function getPaymentStatusAttribute(): OrderPaymentStatus { ... }1516 // ...17}
Importer des namespaces au lieu d'alias
Parfois, vous pouvez avoir plusieurs classes avec le même nom. Plutôt que de les importer avec un alias, importez les namespaces.
❌ Mauvais
1use App\Types\Entries\Visit as VisitEntry;2use App\Storage\Database\Models\Visit as VisitModel;34class DatabaseStorage5{6 public function log(VisitEntry $visit) {7 $visitModel = VisitModel::create([8 ...9 ]);10 }11}1use App\Types\Entries\Visit as VisitEntry;2use App\Storage\Database\Models\Visit as VisitModel;34class DatabaseStorage5{6 public function log(VisitEntry $visit) {7 $visitModel = VisitModel::create([8 ...9 ]);10 }11}
✅ Correct
1use App\Types\Entries;2use App\Storage\Database\Models;34class DatabaseStorage5{6 public function log(Entries\Visit $visit)7 {8 $visitModel = Models\Visit::create([9 ...10 ]);11 }12}1use App\Types\Entries;2use App\Storage\Database\Models;34class DatabaseStorage5{6 public function log(Entries\Visit $visit)7 {8 $visitModel = Models\Visit::create([9 ...10 ]);11 }12}
Créer des scopes pour les where()s complexes
Plutôt que d'écrire des clauses where() complexes, créez des scopes avec des noms expressifs. Ainsi, vos contrôleurs par exemple auront moins besoin de connaître la structure de la base de données et votre code sera plus propre.
❌ Mauvais
1Order::whereHas('status', function ($status) {2 $status->where('canceled', true);3})->get();1Order::whereHas('status', function ($status) {2 $status->where('canceled', true);3})->get();
✅ Correct
1Order::whereCanceled()->get();23class Order extends Model4{5 public function scopeWhereCanceled(Builder $query)6 {7 return $query->whereHas('status', function ($status) {8 $status->where('canceled', true);9 });10 }11}1Order::whereCanceled()->get();23class Order extends Model4{5 public function scopeWhereCanceled(Builder $query)6 {7 return $query->whereHas('status', function ($status) {8 $status->where('canceled', true);9 });10 }11}