web-dev-qa-db-fra.com

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

Quand je supprime une ligne en utilisant cette syntaxe:

$user->delete();

Existe-t-il un moyen de joindre une sorte de rappel, de sorte qu’il puisse, par exemple, faire ceci automatiquement:

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

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

116
Martti Laine

Je crois que c’est un cas d’utilisation idéal pour les événements Eloquent ( 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.

152
ivanhoe

Vous pouvez réellement configurer cela 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 les propriétés "on delete" et "on update" de la contrainte:

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

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

Vous pouvez supprimer toutes les photos associées avant de supprimer réellement 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.

44
akhyar

Relation dans le modèle d'utilisateur:

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

Supprimer l'enregistrement et connexes:

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

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

$user->delete();
22
Calin Blaga

A partir de 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 dans des classes séparées au lieu de fermetures pour une meilleure structure d'application.

12
Attila Fulop

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

1. Utilisation d'événements Eloquent sur le démarrage du modèle (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. Utilisation d’Oloquent Event Observers (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 Observer comme ceci:

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

3. Utilisation de 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');
7
Paras

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, il vous suffit de supprimer l'hôtel et de nettoyer la base de données, la base de données supprimera tous les enregistrements de photos des données. base.

1
Alex

Je parcourrais 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 consiste à fournir au modèle une méthode. Comme ça:

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
Carlos A. Carneiro

Il est préférable de remplacer la méthode delete pour cela. De cette façon, vous pouvez incorporer des transactions de base de données dans la méthode delete. Si vous utilisez la méthode d'événement, vous devrez couvrir votre appel de la méthode delete avec une transaction de base de données chaque fois que vous l'appelez.

Dans votre modèle User.

public function delete()
{
    \DB::beginTransaction();

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

    $result = parent::delete();

    \DB::commit();

    return $result;
}
1
Ranga Lakshitha

Vous pouvez utiliser cette méthode comme alternative.

Ce qui se passera, c’est que nous prendrons toutes les tables associées à la table des utilisateurs et supprimerons les données associées à l’aide de la mise en boucle.

$tables = DB::select("
    SELECT
        TABLE_NAME,
        COLUMN_NAME,
        CONSTRAINT_NAME,
        REFERENCED_TABLE_NAME,
        REFERENCED_COLUMN_NAME
    FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
    WHERE REFERENCED_TABLE_NAME = 'users'
");

foreach($tables as $table){
    $table_name =  $table->TABLE_NAME;
    $column_name = $table->COLUMN_NAME;

    DB::delete("delete from $table_name where $column_name = ?", [$id]);
}
0
Daanzel

Pour préciser la réponse sélectionnée, si vos relations comportent également des relations enfants à supprimer, vous devez d'abord extraire tous les enregistrements de relations enfants, puis appeler la méthode delete() afin que leurs événements de suppression soient également déclenchés correctement.

Vous pouvez le faire facilement avec des messages d’ordre supérieur .

class User extends Eloquent
{
    /**
     * The "booting" method of the model.
     *
     * @return void
     */
    public static function boot() {
        parent::boot();

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

Vous pouvez également améliorer les performances en interrogeant uniquement la colonne ID des relations:

class User extends Eloquent
{
    /**
     * The "booting" method of the model.
     *
     * @return void
     */
    public static function boot() {
        parent::boot();

        static::deleting(function($user) {
             $user->photos()->get(['id'])->each->delete();
        });
    }
}
0
Steve Bauman

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);
}

Notez que si vous n'utilisez pas la connexion par défaut laravel db, vous devez procéder comme suit:

DB::connection('connection_name')->pdo->beginTransaction();
DB::connection('connection_name')->pdo->commit();
DB::connection('connection_name')->pdo->rollBack();
0
Darren Powers