Laravel(Eloquent ORM)の関連する行を自動的に削除する


158

この構文を使用して行を削除すると、次のようになります。

$user->delete();

種類のコールバックをアタッチする方法はありますか?これにより、例えばこれを自動的に行います:

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

できればモデルクラス内。

回答:


205

これはEloquentイベント(http://laravel.com/docs/eloquent#model-events)の完璧なユースケースだと思います。「削除」イベントを使用してクリーンアップを実行できます。

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

また、参照整合性を確保するために、トランザクション全体をすべてトランザクション内に配置する必要があります。


7
注:これが機能するまで、少し時間を費やしています。モデルイベントにfirst()アクセスできるように、クエリに追加する必要がありました。例:SourceUser::where('id', '=', $id)->first()->delete();
Michel Ayres

6
@MichelAyres:はい、クエリビルダーではなく、モデルインスタンスでdelete()を呼び出す必要があります。Builderには独自のdelete()メソッドがあり、基本的にはDELETE SQLクエリを実行するだけなので、ormイベントについて何も知らないと思います...
ivanhoe

3
これが一時的削除の方法です。新しい/推奨されるLaravelの方法は、これらすべてをAppServiceProviderのboot()メソッドに次のように貼り付けることです:\ App \ User :: deleting(function($ u){$ u-> photos()-> delete( );});
ウォーターケイマン2017年

4
ほとんどLaravel 5.5で働いていた、私は追加する必要がありましたforeach($user->photos as $photo)そして、$photo->delete()それが何らかの理由で起こっていたとして、代わりに一つだけの、必ずそれぞれの子は、その子がすべてのレベルで削除していたにします。
ジョージ

9
しかし、これはそれをさらにカスケードしません。たとえば、Photostagsあり、Photosモデル内で同じことを行う場合(つまり、deletingメソッド$photo->tags()->delete();:)はトリガーされません。しかし、私はそれ作ればforループような何かfor($user->photos as $photo) { $photo->delete(); }、その後tagsも削除されますが!FYIのみ
スーパーサン

200

これは実際に移行時に設定できます。

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

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

また、制約の「削除時」および「更新時」プロパティに必要なアクションを指定することもできます。

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

ええ、私はその依存関係を明確にすべきだったと思います。
Chris Schmitz、2013年

62
ただし、行が実際には削除されないため、ソフト削除を使用している場合はそうではありません。
2014

7
また、これによりDB内のレコードは削除されますが、deleteメソッドは実行されないため、削除で追加の作業(たとえば、ファイルの削除)を実行している場合、実行されません
amosmos

10
このアプローチは、カスケード削除を実行するためにDBに依存していますが、すべてのDBがこれをサポートしているわけではないため、追加の注意が必要です。たとえば、MyISAMエンジンを搭載したMySQLは動作せず、NoSQL DB、デフォルト設定のSQLiteなども動作しません。追加の問題は、マイグレーションを実行するときに、職人がこれについて警告せず、MyISAMテーブルに外部キーを作成せず、後でレコードを削除しても、カスケードは発生しません。私はこの問題を一度経験しましたが、デバッグするのは非常に難しいと思います。
ivanhoe 2016

1
@kehindeあなたが示すアプローチは、削除されるリレーションの削除イベントを呼び出しません。リレーションを反復処理し、個別にdeleteを呼び出す必要があります。
トム

51

:この回答はLaravel 3用に書かれました。したがって、Laravelの最新バージョンではうまく機能しない場合があります。

ユーザーを実際に削除する前に、関連するすべての写真を削除できます。

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

それが役に立てば幸い。


1
使用する必要があります:foreach($ this-> photos as $ photo)($ this-> photos()の代わりに$ this-> photos)それ以外の場合、良いヒントです!
Barryvdh 2013年

20
より効率的にするには、次の1つのクエリを使用します。Photo :: where( "user_id"、$ this-> id)-> delete(); ない素敵な道が、わずか1つのクエリ、より良い性能、ユーザーが1.000.000写真の持っている場合。
Dirk

5
実際には次のように呼び出すことができます:$ this-> photos()-> delete(); ループの必要なし– ivanhoe
ivanhoe

4
@ivanhoeコレクションを削除した場合、写真で削除イベントが発生しないことに気づきましたが、akhyarが示唆するように反復すると、削除イベントが発生します。これはバグですか?
adamkrell 2013

1
@akhyarほとんど、あなたはそれを使ってそれを行うことができます$this->photos()->delete()photos()クエリビルダオブジェクトを返します。
Sven van Zoelen 2014年

32

ユーザーモデルの関係:

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

レコードと関連の削除:

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

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

$user->delete();

4
これは機能しますが、関係するピボットテーブルがある場合(hasMany / belongsToManyリレーションの場合)、またはリレーションではなく参照を削除する場合は、$ user()-> relation()-> detach()を使用するように注意してください。 。
ジェームズベイリー

これは私にとってララベル6で機能します。
アルマンH

20

これを解決するには3つの方法があります。

1.モデルブートでのEloquentイベントの使用(参照: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. Eloquentイベントオブザーバーの使用(参照:https : //laravel.com/docs/5.7/eloquent#observers

AppServiceProviderで、次のようにオブザーバーを登録します。

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

次に、次のようにObserverクラスを追加します。

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

3.外部キー制約の使用(参照:https : //laravel.com/docs/5.7/migrations#foreign-key-constraints

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

1
データベース自体に制約を組み込むので、3つのオプションが最もエレガントだと思います。私はそれをテストし、うまく動作します。
ギルバート、

14

Laravel 5.2以降、ドキュメントには、次の種類のイベントハンドラーをAppServiceProviderに登録する必要があると記載されています。

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

アプリケーション構造を改善するために、それらをクロージャーではなく別個のクラスに移動することも想定しています。


1
Laravel 5.3では、それらをObserversと呼ばれる個別のクラスに配置することをお勧めします。ただし、5.3でのみ説明されていますが、このEloquent::observe()メソッドは5.2でも使用可能で、AppServiceProviderから使用できます。
リース

3
あなたはどんな「hasManyの」関係している場合からあなたをphotos()、あなたも注意する必要があります-このプロセスはなりません、あなたがモデルを削除孫をロードしていないので。削除に関連するイベントを起動するには、ループしてphotos(注、ではなくphotos())、delete()モデルとしてそれらのメソッドを起動する必要があります。
リース、

1
@Leith observeメソッドは5.1でも使用できます。
タイラーリード

2

deleteこのためのメソッドをオーバーライドすることをお勧めします。これにより、deleteメソッド自体にDBトランザクションを組み込むことができます。イベントの方法を使用する場合は、呼び出すdeleteたびにDBトランザクションでメソッドの呼び出しをカバーする必要があります。

あなたのUserモデルで。

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

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

    $result = parent::delete();

    \DB::commit();

    return $result;
}

1

私の場合、データベーステーブルは、カスケード削除の外部キーを持つInnoDBであるため、非常に簡単でした。

したがって、この場合、写真テーブルにユーザーの外部キー参照が含まれている場合は、ホテルを削除するだけで、クリーンアップはデータベースによって行われ、データベースはすべての写真レコードをデータから削除しますベース。


他の回答で述べたように、データベース層でのカスケード削除は、ソフト削除を使用する場合は機能しません。バイヤーは注意してください。:)
ベンジョンソン

1

オブジェクト自体を削除する前に、すべてを切り離してコレクションを反復処理します。

ここに例があります:

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

自動ではないことは知っていますが、とても簡単です。

別の簡単なアプローチは、モデルにメソッドを提供することです。このような:

public function detach(){
       try {

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

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

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

次に、必要な場所でこれを呼び出すだけです。

$user->detach();
$user->delete();

0

または、必要に応じてこれを行うことができますが、別のオプションを使用するだけです。

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

デフォルトのlaravel db接続を使用していない場合は、以下を実行する必要があることに注意してください。

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

0

選択した回答について詳しく説明するには、削除する必要のある子リレーションシップもリレーションシップにある場合、最初にすべての子リレーションシップレコードを取得してから、delete()メソッドを呼び出して削除イベントも適切に発生するようにする必要があります。

これは、より高次のメッセージで簡単に行えます

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

リレーションシップID列のみをクエリすることで、パフォーマンスを向上させることもできます。

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

-1

ええ、でも@supersanがコメントの上部で述べたように、QueryBuilderでdelete()を実行した場合、モデル自体をロードしていないのでモデルイベントは発生しません。その後、そのモデルでdelete()を呼び出します。

イベントは、モデルインスタンスで削除機能を使用した場合にのみ発生します。

だから、この蜂は言った:

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

ユーザーを削除するときに投稿タグを削除するには、繰り返し処理し$user->postsて呼び出す必要があります$post->delete()

foreach($user->posts as $post) { $post->delete(); } -> Postで削除イベントが発生します

VS

$user->posts()->delete()->ポストモデルを実際にロードしないため、ポストで削除イベントを起動しません(SQLを実行するだけなDELETE * from posts where user_id = $user->idので、ポストモデルもロードされません)。


-2

この方法を代わりに使用できます。

何が起こるかというと、usersテーブルに関連付けられているすべてのテーブルを取得し、ループを使用して関連データを削除します

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

明確に指定すれば雄弁なormがこれを処理できるので、これらすべてのクエリが必要であるとは思いません。
7rust
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.