Tự động xóa các hàng liên quan trong Laravel (Eloquent ORM)


158

Khi tôi xóa một hàng bằng cú pháp này:

$user->delete();

Có cách nào để đính kèm một cuộc gọi lại các loại, để nó sẽ tự động làm điều này:

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

Tốt nhất là bên trong lớp mô hình.

Câu trả lời:


203

Tôi tin rằng đây là trường hợp sử dụng hoàn hảo cho các sự kiện Eloquent ( http://laravel.com/docs/eloquent#model-events ). Bạn có thể sử dụng sự kiện "xóa" để dọn dẹp:

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

Có lẽ bạn cũng nên đặt toàn bộ nội dung trong một giao dịch, để đảm bảo tính toàn vẹn tham chiếu ..


7
Lưu ý: Tôi dành một chút thời gian cho đến khi tôi làm việc này. Tôi cần thêm first()vào truy vấn để tôi có thể truy cập vào sự kiện mô hình, ví dụ User::where('id', '=', $id)->first()->delete(); Nguồn
Michel Ayres

6
@MichelAyres: có, bạn cần gọi xóa () trên phiên bản mô hình, không phải trên Trình tạo truy vấn. Trình xây dựng có phương thức xóa () riêng của nó, về cơ bản chỉ chạy một truy vấn sql DELETE, vì vậy tôi cho rằng nó không biết gì về các sự kiện orm ...
ivanhoe

3
Đây là cách để đi cho xóa mềm. Tôi tin rằng cách Laravel mới / ưa thích là gắn tất cả những thứ này vào phương thức boot () của AppServiceProvider theo cách này: \ App \ User :: xóa (function ($ u) {$ u-> photos () -> xóa ( );});
Watercayman

4
Hầu như đã làm việc trong Laravel 5.5, tôi đã phải thêm một foreach($user->photos as $photo), sau đó $photo->delete()để chắc chắn rằng mỗi đứa trẻ đã loại bỏ con của chúng ở tất cả các cấp, thay vì chỉ một như nó xảy ra vì một số lý do.
George

9
Điều này không xếp tầng hơn nữa mặc dù. Ví dụ: nếu Photostagsvà bạn làm tương tự trong Photosmô hình (tức là trên deletingphương thức $photo->tags()->delete();:) thì nó không bao giờ được kích hoạt. Nhưng nếu tôi làm cho nó một forvòng lặp và làm một cái gì đó như thế for($user->photos as $photo) { $photo->delete(); }thì tagscũng bị xóa! chỉ FYI
supersan

198

Bạn thực sự có thể thiết lập điều này trong quá trình di chuyển của bạn:

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

Nguồn: http://laravel.com/docs/5.1/migations#forign-key-constraint

Bạn cũng có thể chỉ định hành động mong muốn cho các thuộc tính "khi xóa" và "khi cập nhật" của ràng buộc:

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

Vâng, tôi đoán tôi nên đã làm rõ sự phụ thuộc đó.
Chris Schmitz

62
Nhưng không, nếu bạn đang sử dụng xóa mềm, vì các hàng không thực sự bị xóa.
run rẩy

7
Ngoài ra - điều này sẽ xóa bản ghi trong DB, nhưng sẽ không chạy phương thức xóa của bạn, vì vậy nếu bạn đang thực hiện thêm thao tác xóa (ví dụ: xóa tệp), nó sẽ không chạy
amosmos

10
Cách tiếp cận này dựa vào DB để thực hiện xóa tầng, nhưng không phải tất cả các DB đều hỗ trợ điều này, vì vậy cần phải có sự chăm sóc thêm. Ví dụ, MySQL với công cụ MyISAM không, cũng như bất kỳ DB NoQuery, SQLite nào trong thiết lập mặc định, v.v. Vấn đề khác là nghệ nhân sẽ không cảnh báo bạn về điều này khi bạn chạy di chuyển, nó sẽ không tạo khóa ngoại trên bảng MyISAM và khi bạn xóa một bản ghi, sẽ không có tầng nào xảy ra. Tôi đã có vấn đề này một lần và tôi tin rằng rất khó để gỡ lỗi.
ivanhoe

1
@kehinde Cách tiếp cận được hiển thị bởi bạn KHÔNG gọi các sự kiện xóa trên các mối quan hệ sẽ bị xóa. Bạn nên lặp lại qua mối quan hệ và gọi xóa cá nhân.
Tom

51

Lưu ý : Câu trả lời này được viết cho Laravel 3 . Do đó, có thể hoặc có thể không hoạt động tốt trong phiên bản gần đây hơn của Laravel.

Bạn có thể xóa tất cả các ảnh liên quan trước khi thực sự xóa người dùng.

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

Hy vọng nó giúp.


1
Bạn phải sử dụng: foreach ($ this-> photos as $ photo) ($ this-> photos thay vì $ this-> photos ()) Nếu không, mẹo hay!
Barryvdh

20
Để làm cho nó hiệu quả hơn, hãy sử dụng một truy vấn: Photo :: where ("user_id", $ this-> id) -> xóa (); Không phải là cách đẹp nhất, nhưng chỉ có 1 truy vấn, cách hiệu suất tốt hơn nếu người dùng có 1.000.000 ảnh của.
Dirk

5
thực tế bạn có thể gọi: $ this-> photos () -> xóa (); không cần vòng lặp - ivanhoe
ivanhoe

4
@ivanhoe Tôi nhận thấy rằng sự kiện xóa sẽ không kích hoạt trong ảnh nếu bạn xóa bộ sưu tập, tuy nhiên, việc lặp đi lặp lại như akhyar gợi ý sẽ khiến sự kiện xóa bị kích hoạt. Đây có phải là một lỗi?
adamkrell

1
@akhyar Hầu như, bạn có thể làm điều đó với $this->photos()->delete(). Trả photos()về đối tượng xây dựng truy vấn.
Sven van Zoelen

32

Mối quan hệ trong mô hình người dùng:

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

Xóa hồ sơ và liên quan:

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

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

$user->delete();

4
Điều này hoạt động, nhưng hãy cẩn thận sử dụng $ user () -> mối quan hệ () -> tách ra () nếu có bảng piviot liên quan (trong trường hợp có quan hệ hasMany / thuộc sở hữu), nếu không bạn sẽ xóa tham chiếu, không phải mối quan hệ .
James Bailey

Điều này làm việc cho tôi laravel 6. @Calin bạn có thể giải thích thêm xin vui lòng?
Arman H

19

Có 3 cách tiếp cận để giải quyết điều này:

1. Sử dụng các sự kiện có hiệu quả khi khởi động mô hình (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. Sử dụng Trình quan sát sự kiện Eloquent (ref: https://laravel.com/docs/5.7/eloquent#observers )

Trong AppServiceProvider của bạn, hãy đăng ký người quan sát như vậy:

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

Tiếp theo, thêm một lớp Observer như vậy:

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

3. Sử dụng các ràng buộc khóa ngoài (ref: https://laravel.com/docs/5.7/migations#forign-key-constraint )

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

1
Tôi nghĩ rằng 3 tùy chọn là thanh lịch nhất vì đang xây dựng các ràng buộc vào chính cơ sở dữ liệu. Tôi kiểm tra nó và hoạt động tốt.
Gilbert

14

Kể từ Laravel 5.2, tài liệu nói rằng các loại trình xử lý sự kiện này phải được đăng ký trong AppServiceProvider:

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

Tôi thậm chí còn giả sử chuyển chúng sang các lớp riêng biệt thay vì đóng để có cấu trúc ứng dụng tốt hơn.


1
Laravel 5.3 khuyên bạn nên đặt chúng vào các lớp riêng biệt được gọi là Observers - mặc dù nó chỉ được ghi lại trong 5.3, nhưng Eloquent::observe()phương thức này cũng có sẵn trong 5.2 và có thể được sử dụng từ AppServiceProvider.
Leith

3
Nếu bạn có bất kỳ mối quan hệ 'hasMany' nào từ bạn photos(), bạn cũng cần phải cẩn thận - quá trình này sẽ không xóa cháu vì bạn không tải mô hình. Bạn sẽ cần lặp lại photos(lưu ý, không photos()) và kích hoạt delete()phương thức trên chúng dưới dạng mô hình để kích hoạt các sự kiện liên quan đến xóa.
Leith

1
@Leith Phương pháp quan sát cũng có sẵn trong 5.1.
Tyler Reed

2

Sẽ tốt hơn nếu bạn ghi đè deletephương thức cho việc này. Bằng cách đó, bạn có thể kết hợp các giao dịch DB trong deletechính phương thức đó. Nếu bạn sử dụng cách sự kiện, bạn sẽ phải bao gồm lệnh gọi deletephương thức của mình với giao dịch DB mỗi khi bạn gọi nó.

Trong Usermô hình của bạn .

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

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

    $result = parent::delete();

    \DB::commit();

    return $result;
}

1

Trong trường hợp của tôi, nó khá đơn giản vì các bảng cơ sở dữ liệu của tôi là InnoDB với các khóa ngoại có Cascade on Delete.

Vì vậy, trong trường hợp này nếu bảng ảnh của bạn chứa tham chiếu khóa ngoài cho người dùng hơn tất cả những gì bạn phải làm là xóa khách sạn và việc dọn dẹp sẽ được Cơ sở dữ liệu thực hiện, cơ sở dữ liệu sẽ xóa tất cả các bản ghi ảnh khỏi dữ liệu căn cứ.


Như đã được lưu ý trong các Câu trả lời khác, xóa tầng ở tầng cơ sở dữ liệu sẽ không hoạt động khi sử dụng Xóa mềm. Người mua hãy cẩn thận. :)
Ben Johnson

1

Tôi sẽ lặp đi lặp lại qua bộ sưu tập tách rời mọi thứ trước khi xóa chính đối tượng.

đây là một ví dụ:

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

Tôi biết nó không tự động nhưng nó rất đơn giản.

Một cách tiếp cận đơn giản khác là cung cấp cho mô hình một phương thức. Như thế này:

public function detach(){
       try {

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

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

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

Sau đó, bạn có thể chỉ cần gọi đây là nơi bạn cần:

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

0

Hoặc bạn có thể làm điều này nếu bạn muốn, chỉ là một tùy chọn khác:

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

Lưu ý nếu bạn không sử dụng kết nối db laravel mặc định thì bạn cần làm như sau:

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

0

Để giải thích câu trả lời đã chọn, nếu các mối quan hệ của bạn cũng phải xóa các mối quan hệ con, bạn phải truy xuất tất cả các bản ghi mối quan hệ con trước, sau đó gọi delete()phương thức để các sự kiện xóa của chúng cũng được kích hoạt đúng.

Bạn có thể làm điều này dễ dàng với các tin nhắn có thứ tự cao hơn .

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

Bạn cũng có thể cải thiện hiệu suất bằng cách chỉ truy vấn cột ID mối quan hệ:

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

vâng, nhưng như @supersan đã nêu ở trên trong một nhận xét, nếu bạn xóa () trên QueryBuilder, sự kiện mô hình sẽ không được kích hoạt, vì chúng tôi không tải chính mô hình, sau đó gọi xóa () trên mô hình đó.

Các sự kiện chỉ được kích hoạt nếu chúng ta sử dụng chức năng xóa trên Trường hợp mẫu.

Vì vậy, con ong này nói:

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

để xóa các thẻ bài khi xóa người dùng, chúng tôi sẽ phải lặp lại $user->postsvà gọi$post->delete()

foreach($user->posts as $post) { $post->delete(); } -> điều này sẽ kích hoạt sự kiện xóa trên Post

VS

$user->posts()->delete()-> điều này sẽ không kích hoạt sự kiện xóa trên bài đăng vì chúng tôi không thực sự tải Mô hình bài đăng (chúng tôi chỉ chạy một SQL như: DELETE * from posts where user_id = $user->idvà do đó, mô hình Bài đăng thậm chí không được tải)


-2

Bạn có thể sử dụng phương pháp này như là một thay thế.

Điều gì sẽ xảy ra là chúng tôi lấy tất cả các bảng được liên kết với bảng người dùng và xóa dữ liệu liên quan bằng cách sử dụng vòng lặp

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

Tôi không nghĩ rằng tất cả các truy vấn này là cần thiết vì orm hùng hồn có thể xử lý việc này nếu bạn chỉ định rõ ràng.
7
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.