1. Accueil
  2. Articles
5 min de lecture
438 vues

Laravel Tips Clean Code: Rendez votre code Laravel plus propre et plus sûr #2

Image d'illustration pour Laravel Tips Clean Code: Rendez votre code Laravel plus propre et plus sûr #2

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): self
3 {
4 // We're converting from current currency, to base currency and finally to the new currency
5 $newCurrencyConfig = config("shop.currencies.$currency");
6 $mathDecimalDifference = $newCurrencyConfig['math_decimals'] - config("shop.currencies." . config('shop.base_currency')
7 
8 return new static(
9 (int) round($money->baseValue() * $newCurrencyConfig['value'] * 10**$mathDecimalDifference, 0), $currency
10 );
11 }
12}
13 
14// Usage:
15use App\Helper;
16 
17Helper::convertCurrency($total, 'EUR');
1class Helper {
2 public function convertCurrency(Money $money, string $currency): self
3 {
4 // We're converting from current currency, to base currency and finally to the new currency
5 $newCurrencyConfig = config("shop.currencies.$currency");
6 $mathDecimalDifference = $newCurrencyConfig['math_decimals'] - config("shop.currencies." . config('shop.base_currency')
7 
8 return new static(
9 (int) round($money->baseValue() * $newCurrencyConfig['value'] * 10**$mathDecimalDifference, 0), $currency
10 );
11 }
12}
13 
14// Usage:
15use App\Helper;
16 
17Helper::convertCurrency($total, 'EUR');

✅ Correct

1class Money {
2 // the other money/currency logic
3 
4 public function convertTo(string $currency): self
5 {
6 // We're converting from current currency, to base currency and finally to the new currency
7 $newCurrencyConfig = config("shop.currencies.$currency");
8 $mathDecimalDifference = $newCurrencyConfig['math_decimals'] - config("shop.currencies." . config('shop.base_currency')
9 
10 return new static(
11 (int) round($this->baseValue() * $newCurrencyConfig['value'] * 10**$mathDecimalDifference, 0), $currency
12 );
13 }
14}
15 
16// Usage
17$EURtotal = $total->convertTo('EUR');
1class Money {
2 // the other money/currency logic
3 
4 public function convertTo(string $currency): self
5 {
6 // We're converting from current currency, to base currency and finally to the new currency
7 $newCurrencyConfig = config("shop.currencies.$currency");
8 $mathDecimalDifference = $newCurrencyConfig['math_decimals'] - config("shop.currencies." . config('shop.base_currency')
9 
10 return new static(
11 (int) round($this->baseValue() * $newCurrencyConfig['value'] * 10**$mathDecimalDifference, 0), $currency
12 );
13 }
14}
15 
16// Usage
17$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}
5 
6class Visit {
7 public string $url;
8 public ?string $routeName;
9 public array $routeData;
10 
11 public ?string $campaign;
12 public array $trafficSource = [];
13 
14 public ?string $referer;
15 
16 public Carbon $timestamp;
17 
18 // ...
19 
20}
1public function log(Visit $visit)
2{
3 // ...
4}
5 
6class Visit {
7 public string $url;
8 public ?string $routeName;
9 public array $routeData;
10 
11 public ?string $campaign;
12 public array $trafficSource = [];
13 
14 public ?string $referer;
15 
16 public Carbon $timestamp;
17 
18 // ...
19 
20}

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// ... etc
1Visit::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();
2 
3class OrderProductCollection extends Collection
4{
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();
2 
3class OrderProductCollection extends Collection
4{
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);
2 
3// ...
4 
5$ord->notify();
1$ord = Order::create($data);
2 
3// ...
4 
5$ord->notify();

✅ Correct

1$order = Order::create($data);
2 
3// ...
4 
5$order->sendCreatedNotification();
1$order = Order::create($data);
2 
3// ...
4 
5$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 Model
2{
3 
4 public static function bootHasStatuses() { ... }
5 public static $statusMap = [ ... ];
6 public static function getStatusId($status) { ... }
7 public function getPaymentStatusAttribute(): OrderPaymentStatus { ... }
8 
9 // ...
10 
11}
1class Order extends Model
2{
3 
4 public static function bootHasStatuses() { ... }
5 public static $statusMap = [ ... ];
6 public static function getStatusId($status) { ... }
7 public function getPaymentStatusAttribute(): OrderPaymentStatus { ... }
8 
9 // ...
10 
11}

✅ Correct

1class Order extends Model
2{
3 use HasStatuses;
4 
5 // ...
6 
7}
8 
9trait HasStatuses
10{
11 public static function bootHasStatuses() { ... }
12 public static $statusMap = [ ... ];
13 public static function getStatusId($status) { ... }
14 public function getPaymentStatusAttribute(): OrderPaymentStatus { ... }
15 
16 // ...
17}
1class Order extends Model
2{
3 use HasStatuses;
4 
5 // ...
6 
7}
8 
9trait HasStatuses
10{
11 public static function bootHasStatuses() { ... }
12 public static $statusMap = [ ... ];
13 public static function getStatusId($status) { ... }
14 public function getPaymentStatusAttribute(): OrderPaymentStatus { ... }
15 
16 // ...
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;
3 
4class DatabaseStorage
5{
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;
3 
4class DatabaseStorage
5{
6 public function log(VisitEntry $visit) {
7 $visitModel = VisitModel::create([
8 ...
9 ]);
10 }
11}

✅ Correct

1use App\Types\Entries;
2use App\Storage\Database\Models;
3 
4class DatabaseStorage
5{
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;
3 
4class DatabaseStorage
5{
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();
2 
3class Order extends Model
4{
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();
2 
3class Order extends Model
4{
5 public function scopeWhereCanceled(Builder $query)
6 {
7 return $query->whereHas('status', function ($status) {
8 $status->where('canceled', true);
9 });
10 }
11}