Suppression automatique des lignes associées dans Laravel (Eloquent ORM)

Lorsque je supprime une ligne en utilisant cette syntaxe:

$user->delete();

Existe-t-il un moyen d'attacher une sorte de rappel, de sorte qu'il le ferait par exemple automatiquement:

$this->photo()->delete();

De préférence à l'intérieur de la classe modèle.

105
demandé sur Martti Laine 2013-01-05 20:55:28

10 réponses

Je crois que c'est un cas d'utilisation parfait pour les événements éloquents ( http://laravel.com/docs/eloquent#model-events ). vous pouvez utiliser l'événement "deleting" pour effectuer le nettoyage:

class User extends Eloquent
{
    public function photos()
    {
        return $this->has_many('Photo');
    }

    // this is a recommended way to declare event handlers
    public static function boot() {
        parent::boot();

        static::deleting(function($user) { // before delete() method call this
             $user->photos()->delete();
             // do the rest of the cleanup...
        });
    }
}

Vous devriez probablement aussi mettre le tout dans une transaction, pour assurer l'intégrité référentielle..

139
répondu ivanhoe 2018-09-14 22:53:07

Vous pouvez réellement le configurer dans vos migrations:

$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');

Source: http://laravel.com/docs/5.1/migrations#foreign-key-constraints

Vous pouvez également spécifier l'action souhaitée pour le "sur " supprimer" et "sur update " propriétés de la contrainte:

$table->foreign('user_id')
      ->references('id')->on('users')
      ->onDelete('cascade');
151
répondu Chris Schmitz 2015-09-03 19:40:09

Note: Cette réponse a été écrit pour Laravel 3. Ainsi pourrait ou pourrait ne pas fonctionner bien dans la version plus récente de Laravel.

Vous pouvez supprimer toutes les photos associées avant de supprimer l'utilisateur.

<?php

class User extends Eloquent
{

    public function photos()
    {
        return $this->has_many('Photo');
    }

    public function delete()
    {
        // delete all related photos 
        $this->photos()->delete();
        // as suggested by Dirk in comment,
        // it's an uglier alternative, but faster
        // Photo::where("user_id", $this->id)->delete()

        // delete the user
        return parent::delete();
    }
}

J'espère que ça aide.

41
répondu akhyar 2014-02-06 04:26:52

Relation dans le modèle utilisateur:

public function photos()
{
    return $this->hasMany('Photo');
}

Supprimer l'enregistrement et connexes:

$user = User::find($id);

// delete related   
$user->photos()->delete();

$user->delete();
18
répondu Calin Blaga 2017-08-09 06:44:48

Depuis Laravel 5.2, la documentation indique que ces types de gestionnaires d'événements doivent être enregistrés dans AppServiceProvider:

<?php
class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        User::deleting(function ($user) {
            $user->photos()->delete();
        });
    }

Je suppose même de les déplacer vers des classes séparées au lieu de fermetures pour une meilleure structure d'application.

10
répondu Attila Fulop 2016-06-08 04:15:28

Dans mon cas, c'était assez simple car mes tables de base de données sont InnoDB avec des clés étrangères avec Cascade sur Delete.

Donc, dans ce cas, si votre table de photos contient une référence de clé étrangère pour l'utilisateur que tout ce que vous avez à faire est de supprimer l'hôtel et le nettoyage sera fait par la Base de données, la base de données supprimera tous les enregistrements de photos de la base de données.

1
répondu Alex 2015-01-07 08:11:55

Je voudrais parcourir la collection en détachant tout avant de supprimer l'objet lui-même.

Voici un exemple:

try {
        $user = user::findOrFail($id);
        if ($user->has('photos')) {
            foreach ($user->photos as $photo) {

                $user->photos()->detach($photo);
            }
        }
        $user->delete();
        return 'User deleted';
    } catch (Exception $e) {
        dd($e);
    }

Je sais que ce n'est pas automatique mais c'est très simple.

Une autre approche simple consisterait à fournir au modèle une méthode. Comme ceci:

public function detach(){
       try {

            if ($this->has('photos')) {
                foreach ($this->photos as $photo) {

                    $this->photos()->detach($photo);
                }
            }

        } catch (Exception $e) {
            dd($e);
        }
}

Ensuite, vous pouvez simplement appeler ceci où vous avez besoin:

$user->detach();
$user->delete();
1
répondu Carlos A. Carneiro 2018-02-21 20:40:30

Il existe 3 approches pour résoudre ce problème:

1. Utilisation D'événements Eloquent sur le modèle Boot (ref: https://laravel.com/docs/5.7/eloquent#events )

class User extends Eloquent
{
    public static function boot() {
        parent::boot();

        static::deleting(function($user) {
             $user->photos()->delete();
        });
    }
}

2. Utiliser des observateurs D'événements éloquents (ref: https://laravel.com/docs/5.7/eloquent#observers )

Dans votre AppServiceProvider, enregistrez l'observateur comme suit:

public function boot()
{
    User::observe(UserObserver::class);
}

Ensuite, ajoutez une classe D'observateur comme ceci:

class UserObserver
{
    public function deleting(User $user)
    {
         $user->photos()->delete();
    }
}

3. Utilisation des contraintes de clé étrangère (ref: https://laravel.com/docs/5.7/migrations#foreign-key-constraints)

$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
1
répondu Paras 2018-09-14 23:02:37

Ou vous pouvez le faire si vous le souhaitez, juste une autre option:

try {
    DB::connection()->pdo->beginTransaction();

    $photos = Photo::where('user_id', '=', $user_id)->delete(); // Delete all photos for user
    $user = Geofence::where('id', '=', $user_id)->delete(); // Delete users

    DB::connection()->pdo->commit();

}catch(\Laravel\Database\Exception $e) {
    DB::connection()->pdo->rollBack();
    Log::exception($e);
}

Remarque Si vous n'utilisez pas la connexion laravel db par défaut, vous devez effectuer les opérations suivantes:

DB::connection('connection_name')->pdo->beginTransaction();
DB::connection('connection_name')->pdo->commit();
DB::connection('connection_name')->pdo->rollBack();
0
répondu Darren Powers 2013-02-22 07:47:30

Oui, mais comme @ supersan l'a indiqué dans un commentaire, si vous delete () sur un QueryBuilder, l'événement model ne sera pas déclenché, car nous ne chargeons pas le modèle lui-même, puis appelons delete () sur ce modèle.

Les événements sont déclenchés uniquement si nous utilisons la fonction delete sur une Instance de modèle.

Donc, ce beeing dit:

if user->hasMany(post)
and if post->hasMany(tags)

Afin de supprimer les balises post lors de la suppression de l'utilisateur, nous devrions itérer sur $user->posts et appeler $post->delete()

foreach($user->posts as $post) { $post->delete(); } -> cela va déclencher le suppression d'un événement sur Post

VS

$user->posts()->delete() -> cela ne déclenchera pas l'événement de suppression sur post car nous ne chargeons pas réellement le modèle Post (nous exécutons seulement un SQL comme: DELETE * from posts where user_id = $user->id et donc, le modèle Post n'est même pas chargé)

-1
répondu rechim 2018-09-24 15:27:30