Laravel (Eloquent ORM)에서 관련 행 자동 삭제


161

이 구문을 사용하여 행을 삭제할 때 :

$user->delete();

예를 들어 자동으로 수행 할 수 있도록 일종의 콜백을 첨부하는 방법이 있습니까?

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

가급적 모델 클래스 내부.

답변:


218

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

참조 무결성을 보장하기 위해 모든 것을 트랜잭션 안에 넣어야합니다.


8
참고 :이 작업을 수행 할 때까지 약간의 시간을 보냅니다. 모델 이벤트에first() 액세스 할 수 있도록 쿼리 에 추가해야했습니다. 예 : SourceUser::where('id', '=', $id)->first()->delete();
Michel Ayres

6
@MichelAyres : 예, 쿼리 빌더가 아닌 모델 인스턴스에서 delete ()를 호출해야합니다. 빌더는 ... 나는 그것을 ORM 이벤트에 대해 아무것도 모르는 가정, 그래서 기본적으로 그냥하는 DELETE SQL 쿼리를 실행하는 자신의 삭제 () 메소드의가
반호

3
이것은 일시 삭제하는 방법입니다. 새롭고 선호되는 Laravel 방식은이 모든 것을 AppServiceProvider의 boot () 메서드에 다음과 같이 붙이는 것입니다. \ App \ User :: deleting (function ($ u) {$ u-> photos ()-> delete ( );});
Watercayman

4
거의 Laravel 5.5에서 작업 foreach($user->photos as $photo)한 후 를 추가 한 다음 $photo->delete()각 어린이가 어떤 이유로 인해 한 명만 제거되는 대신 모든 수준에서 어린이를 제거하도록해야했습니다.
조지

11
이것은 더 이상 계단식으로 연결되지 않습니다. 예를 들어 경우가 Photos있다 tags당신은에서 동일한 작업을 수행 Photos(에 즉 모델 deleting방법 $photo->tags()->delete();)은 트리거를 얻을 수 없다. 그러나 내가 그것을 for루프로 만들고 다음과 for($user->photos as $photo) { $photo->delete(); }같이하면 tags또한 삭제됩니다! 단지 참고
supersan

207

마이그레이션에서 실제로 설정할 수 있습니다.

$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 년

63
그러나 소프트 삭제를 사용하는 경우에는 행이 실제로 삭제되지 않으므로 그렇지 않습니다.
tremby 2014

7
또한-이렇게하면 DB의 레코드가 삭제되지만 삭제 방법은 실행되지 않으므로 삭제에 대한 추가 작업 (예 : 파일 삭제)을 수행하는 경우 실행되지 않습니다.
amosmos dec

10
이 접근 방식은 DB를 사용하여 계단식 삭제를 수행하지만 모든 DB가이를 지원하는 것은 아니므로 추가주의가 필요합니다. 예를 들어 MyISAM 엔진이있는 MySQL은 NoSQL DB, 기본 설정의 SQLite 등이 없습니다. 추가 문제는 장인이 마이그레이션을 실행할 때 이에 대해 경고하지 않고 MyISAM 테이블에 외래 키를 생성하지 않는다는 것입니다. 나중에 레코드를 삭제하면 캐스케이드가 발생하지 않습니다. 이 문제가 한 번 있었는데 디버그하기가 매우 어렵다고 믿습니다.
ivanhoe

1
@kehinde 표시된 접근 방식은 삭제할 관계에 대한 삭제 이벤트를 호출하지 않습니다. 관계를 반복하고 개별적으로 delete를 호출해야합니다.
Tom

52

참고 :이 답변은 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

20
더 효율적으로 만들려면 하나의 쿼리를 사용하십시오. Photo :: where ( "user_id", $ this-> id)-> delete (); 아니 가장 좋은 방법,하지만 1 개 쿼리 방법으로 더 나은 성능을 사용자가 1.000.000 사진의 경우.
Dirk

5
실제로 호출 할 수 있습니다 : $ this-> photos ()-> delete (); 반호 - 루프가 필요
아이 반호하지

5
@ivanhoe 컬렉션을 삭제하면 사진에서 삭제 이벤트가 발생하지 않는 것으로 나타 났지만 akhyar가 제안한대로 반복하면 삭제 이벤트가 발생합니다. 이것은 버그입니까?
adamkrell

1
예, 저는 L4에 있습니다. 작동하지 않습니다. 삭제 이벤트가 발생하지 않습니다. 나는 그것을 몇 번 시도했다. 이유를 찾기 위해 Laravel의 코드를 살펴볼 시간이 없었습니다.
adamkrell

33

사용자 모델에서의 관계 :

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

기록 및 관련 삭제 :

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

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

$user->delete();

5
이것은 작동하지만, 관련된 piviot 테이블이 있으면 $ user ()-> relation ()-> detach ()를 사용하도록주의하십시오 (hasMany / belongsToMany 관계의 경우). 그렇지 않으면 관계가 아닌 참조를 삭제합니다. .
James Bailey

이것은 나를 위해 작동합니다 laravel 6. @Calin 더 많은 pls를 설명 할 수 있습니까?
Arman H

23

이 문제를 해결하는 방법에는 세 가지가 있습니다.

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 이벤트 옵저버 사용하기 (ref : 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
데이터베이스 자체에 제약 조건을 구축하는 것이므로 세 가지 옵션이 가장 우아하다고 생각합니다. 나는 그것을 테스트하고 잘 작동합니다.
Gilbert

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

더 나은 애플리케이션 구조를 위해 클로저 대신 별도의 클래스로 이동한다고 가정합니다.


2
Laravel 5.3은 Observers 라는 별도의 클래스에 배치 할 것을 권장합니다. 하지만 5.3에서만 문서화되어 있지만이 Eloquent::observe()메서드는 5.2에서도 사용할 수 있으며 AppServiceProvider에서 사용할 수 있습니다.
Leith

3
당신이 어떤 'hasMany의'관계가있는 경우 에서 당신을 photos(), 당신은 또한 조심해야합니다 -이 과정 것 없는 당신이 모델을 삭제 손자를로드하지 않을 때문이다. 삭제 관련 이벤트를 발생 시키 려면 루프를 반복하고 photos(가 아니라 참고 photos()) delete()메서드를 모델로 실행해야합니다.
Leith

1
@Leith 관찰 방법은 5.1에서도 사용할 수 있습니다.
Tyler Reed

이것은 나를 위해 일했습니다. 감사합니다!
Jareer

4

이에 대한 delete방법을 재정의하는 것이 좋습니다 . 이렇게하면 delete메서드 자체에 DB 트랜잭션을 통합 할 수 있습니다 . 이벤트 방식을 사용하는 경우 delete호출 할 때마다 DB 트랜잭션 으로 메서드 호출을 가려야합니다 .

당신의에서 User모델.

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

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

    $result = parent::delete();

    \DB::commit();

    return $result;
}

2

선택한 답변에 대해 자세히 설명하려면 관계에 삭제해야하는 하위 관계가있는 경우 먼저 모든 하위 관계 레코드를 검색 한 다음 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

제 경우에는 데이터베이스 테이블이 삭제시 Cascade가있는 외래 키가있는 InnoDB이기 때문에 매우 간단했습니다.

따라서이 경우 사진 테이블에 사용자에 대한 외래 키 참조가 포함되어있는 경우 호텔을 삭제하고 정리가 데이터베이스에서 수행되는 것보다 데이터베이스가 데이터에서 모든 사진 레코드를 삭제합니다. 베이스.


다른 답변에서 언급했듯이 소프트 삭제를 사용할 때 데이터베이스 계층에서 계단식 삭제가 작동하지 않습니다. 구매자는 조심하십시오. :)
Ben Johnson

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

-1

예,하지만 @supersan이 주석에서 상단에 언급했듯이 QueryBuilder에서 delete ()하면 모델 자체를로드하지 않고 해당 모델에서 delete ()를 호출하기 때문에 모델 이벤트가 발생하지 않습니다.

이벤트는 모델 인스턴스에서 삭제 기능을 사용하는 경우에만 발생합니다.

그래서이 벌은 이렇게 말했습니다.

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

사용자를 삭제할 때 post 태그를 삭제하려면 반복 $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

이 방법을 대안으로 사용할 수 있습니다.

사용자 테이블과 관련된 모든 테이블을 가져와 루핑을 사용하여 관련 데이터를 삭제합니다.

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

명확하게 지정하면 eloquent orm이이를 처리 할 수 ​​있기 때문에 이러한 모든 쿼리가 필요하다고 생각하지 않습니다.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.