X

Transactions de la base de données Laravel : 3 exemples pratiques


Les transactions de base de données sont très utiles pour plusieurs opérations de base de données, et Laravel a des fonctionnalités pour elles. Mais quels seraient les exemples pratiques de QUAND devriez-vous les utiliser ?

En bref, les transactions sont utiles pour plusieurs opérations de base de données, lorsque vous voulez vous assurer que si l’une d’entre elles échoue, elles seront toutes annulées automatiquement.

Dans cet article, je vais montrer trois exemples typiques, en Laravel :

  • Création d’un nouvel enregistrement avec plusieurs enregistrements associés
  • Suppression de plusieurs enregistrements pour un utilisateur
  • Mise à jour du tableau récapitulatif après un nouvel enregistrement

Soyons pratiques.


Exemple 1. Plusieurs à plusieurs avec transaction.

Jetez un œil à ce code de contrôleur typique :

public function store(StoreUserRequest $request) {

$user = User::create($request->validated());

$user->roles()->attach($request->input('roles'));

 

return redirect()->route('users.index');

}

Comme vous pouvez le voir, il y a un nouvel enregistrement d’utilisateur, puis plusieurs rôles sont attachés à l’utilisateur. Mais que se passe-t-il si quelque chose ne va pas dans la deuxième phrase ?

Imaginons que $request->input('roles') est passé non pas en tant que tableau mais en tant que chaîne invalide. Que se passe-t-il alors ?

Et le pire, ce n’est pas l’erreur, mais le fait que l’enregistrement de l’utilisateur a été effectivement enregistré dans la base de données.

Dans le cas des utilisateurs, cela peut avoir une mauvaise conséquence si les e-mails sont déjà pris, même si l’enregistrement n’est pas encore terminé, car le users.email champ est unique au niveau de la base de données.

C’est pourquoi il est avantageux d’utiliser une transaction de base de données ici :

use Illuminate\Support\Facades\DB;

 

// ...

 

public function store(StoreUserRequest $request) {

DB::transaction(function() use ($request) {

$user = User::create($request->validated());

$user->roles()->attach($request->input('roles'));

}

 

return redirect()->route('users.index');

}

Avis: Gardez à l’esprit que vous devez passer use ($request) ou toute autre variable externe que vous devez utiliser dans la fonction de transaction.

Maintenant, ne vous méprenez pas : vous obtiendrez toujours la même erreur. “Valeur entière incorrecte : ‘abcd’ pour la colonne ‘role_id’ à la ligne 1”. Mais l’instruction de création d’utilisateur sera annulée et vous ne verrez pas l’utilisateur dans la base de données.


Exemple 2. Suppression de plusieurs enregistrements pour l’utilisateur

Imaginons que vous vouliez supprimer l’enregistrement qui a beaucoup de relations hasMany/belongsToMany. Vous devez également les supprimer, n’est-ce pas ? Si vous n’avez pas défini le cascadeOnDelete() au niveau de la base de données dans les migrations, vous devez le faire manuellement.

Quelque chose comme ça:

$profile->avatar->forceDelete();

MediaTag::whereProfileId($profile->id)->delete();

StatusHashtag::whereProfileId($profile->id)->delete();

DirectMessage::whereFromId($profile->id)->delete();

FollowRequest::whereFollowingId($profile->id)

->orWhere('follower_id', $profile->id)

->forceDelete();

Follower::whereProfileId($profile->id)

->orWhere('following_id', $profile->id)

->forceDelete();

Like::whereProfileId($profile->id)->forceDelete();

 

// ... only then delete the profile itself:

$profile->delete();

Imaginez ce qui se passe si une phrase du milieu de cet extrait de code échoue. Donc on a supprimé quelque chose mais pas tout ?

Bien sûr, par rapport à l’exemple précédent, la conséquence n’est pas aussi sévère, car, eh bien, nous voulons toujours supprimer ces enregistrements de toute façon, nous le ferions plus tard.

Néanmoins, le profil resterait actif mais ne verrait pas certaines de ses données, comme le nom d’utilisateur sans avatar. Pas cool, non ?

Ajoutez simplement quelques lignes de code :

DB::transaction(function() use ($profile) {

$profile->avatar->forceDelete();

MediaTag::whereProfileId($profile->id)->delete();

StatusHashtag::whereProfileId($profile->id)->delete();

DirectMessage::whereFromId($profile->id)->delete();

FollowRequest::whereFollowingId($profile->id)

->orWhere('follower_id', $profile->id)

->forceDelete();

Follower::whereProfileId($profile->id)

->orWhere('following_id', $profile->id)

->forceDelete();

Like::whereProfileId($profile->id)->forceDelete();

 

$profile->delete();

});


Exemple 3. Mise à jour du tableau “Résumé”

Imaginez un projet avec des utilisateurs et des opérations financières. Habituellement, ils s’appelleraient “Transactions”, mais pour éviter toute confusion avec le sujet de l’article, je les appellerai simplement “Dépenses”.

Vous devez suivre toutes les dépenses ainsi que le solde actuel de chaque utilisateur. Ainsi, après chaque achat, vous feriez quelque chose comme ceci :

Expense::create($expenseDetails);

$user->decrement('balance', $expenseDetails->amount);

Cela semble trivial, mais dans des scénarios plus complexes, vous devrez également mettre à jour des données récapitulatives supplémentaires dans d’autres tableaux, pour certains rapports.

Ici, la conséquence de ne pas utiliser les transactions DB est énorme : les utilisateurs auraient plus d’argent à dépenser qu’ils ne le devraient.

Réparons ça :

DB::transaction(function() use ($expenseDetails, $user) {

Expense::create($expenseDetails);

$user->decrement('balance', $expenseDetails->amount);

});


Donc, ce ne sont que trois exemples simples de transactions DB. J’espère qu’ils vous pousseront à rendre vos données correctes tout le temps.