การลบแถวที่เกี่ยวข้องใน 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...
        });
    }
}

คุณควรใส่ทุกอย่างไว้ในการทำธุรกรรมเพื่อให้แน่ใจว่า Referential Integrity ..


7
หมายเหตุ: ฉันใช้เวลาจนกว่าจะได้งานนี้ ฉันต้องการเพิ่มfirst()ลงในแบบสอบถามเพื่อให้ฉันสามารถเข้าถึงโมเดลเหตุการณ์เช่นUser::where('id', '=', $id)->first()->delete(); แหล่งที่มา
มิเชลไอเรส

6
@MichelAyres: ใช่คุณต้องโทรลบ () ในอินสแตนซ์ของรุ่นไม่ใช่ใน Query Builder ตัวสร้างมีเมธอด delete () ของตัวเองซึ่งโดยทั่วไปเรียกใช้แบบสอบถาม DELETE sql ดังนั้นฉันจึงสันนิษฐานว่าไม่รู้เกี่ยวกับเหตุการณ์ orm ...
Ivanhoe

3
นี่เป็นวิธีที่จะไปสำหรับการลบแบบอ่อน ฉันเชื่อว่าวิธี Laravel ใหม่ / ที่ต้องการคือการติดสิ่งเหล่านี้ทั้งหมดในวิธีการ boot () ของ AppServiceProvider ด้วยวิธีนี้: \ App \ User :: การลบ (ฟังก์ชั่น ($ u) {$ u-> photos () -> delete ( );});
Watercayman

4
เกือบทำงานใน Laravel 5.5 ฉันต้องเพิ่ม a foreach($user->photos as $photo)แล้ว$photo->delete()ตรวจสอบให้แน่ใจว่าเด็กแต่ละคนมีลูกของมันออกทุกระดับแทนที่จะเป็นเพียงลูกเดียวเพราะมันเกิดขึ้นด้วยเหตุผลบางอย่าง
จอร์จ

9
สิ่งนี้ไม่ได้ลดความมันลงไปอีก ตัวอย่างเช่นถ้าPhotosมีtagsและคุณทำแบบเดียวกันPhotos(เช่นในdeletingวิธี:) $photo->tags()->delete();มันไม่เคยได้รับทริกเกอร์ แต่ถ้าฉันทำให้มันforวนซ้ำและทำอะไรแบบfor($user->photos as $photo) { $photo->delete(); }นั้นมันtagsก็จะถูกลบด้วย! แค่ FYI
supersan

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

62
แต่ไม่ใช่ถ้าคุณใช้การลบแบบอ่อนเนื่องจากแถวจะไม่ถูกลบจริงๆ
tremby

7
นอกจากนี้ - สิ่งนี้จะลบบันทึกในฐานข้อมูล แต่จะไม่เรียกใช้วิธีการลบของคุณดังนั้นหากคุณทำงานพิเศษในการลบ (เช่น - ลบไฟล์) มันจะไม่ทำงาน
amosmos

10
วิธีการนี้อาศัย DB เพื่อทำการลบแบบเรียงซ้อน แต่ไม่ใช่ DB ทั้งหมดที่สนับสนุนสิ่งนี้ดังนั้นจึงจำเป็นต้องมีการดูแลเป็นพิเศษ ตัวอย่างเช่น MySQL ที่มีเอ็นจิ้น MyISAM ไม่รวมถึง NoSQL DBs, SQLite ในการตั้งค่าเริ่มต้นเป็นต้นปัญหาเพิ่มเติมคือช่างจะไม่เตือนคุณเมื่อคุณเรียกใช้การโยกย้ายมันจะไม่สร้างกุญแจต่างประเทศในตาราง MyISAM และ เมื่อคุณลบบันทึกในภายหลังจะไม่มีการเรียงซ้อน ฉันมีปัญหานี้เพียงครั้งเดียวและเชื่อว่าฉันจะแก้ปัญหาได้ยากมาก
ivanhoe

1
@kehinde วิธีที่แสดงโดยคุณไม่ได้เรียกใช้เหตุการณ์การลบในความสัมพันธ์ที่ต้องถูกลบ คุณควรวนซ้ำความสัมพันธ์และโทรลบทีละรายการ
Tom

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 เป็น $ photo) ($ this-> photos แทนที่จะเป็น $ this-> photos ()) มิฉะนั้นเคล็ดลับดี!
Barryvdh

20
หากต้องการทำให้มีประสิทธิภาพมากขึ้นให้ใช้หนึ่งข้อความค้นหา: ภาพถ่าย :: where ("user_id", $ this-> id) -> delete (); ไม่ได้เป็นวิธีที่ดีที่สุด แต่เพียง 1 สอบถามวิธีประสิทธิภาพที่ดีขึ้นถ้าผู้ใช้มี 1.000.000 รูปภาพ
Dirk

5
จริงๆแล้วคุณสามารถโทร: $ this-> photos () -> delete (); ไม่จำเป็นต้องวนซ้ำ - ivanhoe
ivanhoe

4
@ivanhoe ฉันสังเกตเห็นว่าการลบกิจกรรมจะไม่เริ่มต้นในรูปหากคุณลบคอลเล็กชันอย่างไรก็ตามการทำซ้ำตามที่ akhyar แนะนำจะทำให้เหตุการณ์การลบเริ่มทำงาน นี่เป็นข้อบกพร่องหรือไม่?
adamkrell

1
@akhyar $this->photos()->delete()เกือบคุณสามารถทำมันได้ด้วย photos()ผลตอบแทนวัตถุสร้างแบบสอบถาม
Sven van Zoelen

32

ความสัมพันธ์ในรุ่นของผู้ใช้:

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

ลบบันทึกและที่เกี่ยวข้อง:

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

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

$user->delete();

4
ใช้งานได้ แต่ใช้ความระมัดระวังในการใช้ $ user () -> ความสัมพันธ์ () -> detach () หากมีตาราง piviot ที่เกี่ยวข้อง (ในกรณีที่มีความสัมพันธ์หลายอย่าง / มีหลายอย่าง) มิฉะนั้นคุณจะลบข้อมูลอ้างอิงไม่ใช่ความสัมพันธ์ .
James Bailey

สิ่งนี้ใช้ได้กับฉัน laravel 6 @ Calin คุณช่วยอธิบายเพิ่มเติมได้ไหม?
Arman H

20

มี 3 วิธีในการแก้ปัญหานี้:

1. การใช้ Eloquent Events ใน Model Boot (อ้างอิง: 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 Event Observers (อ้างอิง: 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. การใช้ข้อ จำกัด ของ Foreign Key (อ้างอิง: https://laravel.com/docs/5.7/migrations#foreign-key-constraints )

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

1
ฉันคิดว่าตัวเลือกทั้ง 3 ตัวนั้นสวยที่สุดเพราะสร้างข้อ จำกัด ในฐานข้อมูลของตัวเอง ฉันทดสอบและใช้งานได้ดี
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();
        });
    }

ฉันยังคิดว่าจะย้ายพวกเขาไปยังชั้นเรียนแยกต่างหากแทนที่จะปิดเพื่อให้โครงสร้างแอปพลิเคชันดีขึ้น


1
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

2

มันจะดีกว่าถ้าคุณแทนที่deleteวิธีการนี้ ด้วยวิธีนี้คุณสามารถรวมธุรกรรม DB ภายในdeleteวิธีการเอง หากคุณใช้วิธีเหตุการณ์คุณจะต้องครอบคลุมdeleteวิธีการโทรของคุณด้วยการทำธุรกรรม DB ทุกครั้งที่คุณเรียกมัน

ในUserแบบของคุณ

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

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

    $result = parent::delete();

    \DB::commit();

    return $result;
}

1

ในกรณีของฉันมันค่อนข้างง่ายเพราะตารางฐานข้อมูลของฉันคือ InnoDB ที่มีคีย์ต่างประเทศพร้อมด้วย Cascade on Delete

ดังนั้นในกรณีนี้หากตารางภาพถ่ายของคุณมีการอ้างอิงคีย์ต่างประเทศสำหรับผู้ใช้มากกว่าสิ่งที่คุณต้องทำคือการลบโรงแรมและการล้างข้อมูลจะกระทำโดยฐานข้อมูลฐานข้อมูลจะลบระเบียนภาพถ่ายทั้งหมดจากข้อมูล ฐาน.


ตามที่ได้รับการบันทึกไว้ใน Answers อื่น ๆ การลบแบบเรียงซ้อนที่เลเยอร์ฐานข้อมูลจะไม่ทำงานเมื่อใช้ Soft Deletes ผู้ซื้อระวัง :)
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();

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 () ในโมเดลนั้น

เหตุการณ์จะเกิดขึ้นก็ต่อเมื่อเราใช้ฟังก์ชั่นลบใน Model Instance

ดังนั้นผึ้งตัวนี้พูดว่า:

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

เพื่อลบแท็กโพสต์เมื่อลบผู้ใช้เราจะต้องทำซ้ำ$user->postsและโทร$post->delete()

foreach($user->posts as $post) { $post->delete(); } -> สิ่งนี้จะดำเนินการเหตุการณ์การลบในโพสต์

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

ฉันไม่คิดว่าแบบสอบถามเหล่านี้จำเป็นเนื่องจาก oro eloquent สามารถจัดการได้หากคุณระบุไว้อย่างชัดเจน
7
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.