Автоматическое удаление связанных строк в Laravel (Eloquent ORM)


когда я удаляю строку, используя этот синтаксис:

$user->delete();

есть ли способ прикрепить обратный вызов, чтобы он, например, делал это автоматически:

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

предпочтительно внутри модели-класса.

10 105

10 ответов:

Я считаю, что это идеальный прецедент для красноречивых событий (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...
        });
    }
}

вероятно, вы также должны поместить все это в транзакцию, чтобы обеспечить ссылочную целостность..

вы можете настроить это в своих миграциях:

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

Источник:http://laravel.com/docs/5.1/migrations#foreign-key-constraints

вы также можете указать желаемое действие для "on delete" и " on обновить" свойства ограничения:

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

Примечание: этот ответ был написан для 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();
    }
}

надеюсь, что это помогает.

взаимосвязь в модели User:

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

удалить запись и связанные с:

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

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

$user->delete();

по состоянию на 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();
        });
    }

Я даже предполагаю переместить их в отдельные классы вместо замыканий для лучшей структуры приложения.

в моем случае это было довольно просто, потому что мои таблицы базы данных InnoDB с внешними ключами с каскадом на удаление.

поэтому в этом случае, если ваша таблица фотографий содержит ссылку на внешний ключ для пользователя, чем все, что вам нужно сделать, это удалить отель, и очистка будет выполнена базой данных, база данных удалит все записи фотографий из базы данных.

Я бы перебрал коллекцию, отсоединяя все, прежде чем удалять сам объект.

вот пример:

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

есть 3 подхода к решению этой проблемы:

1. Использование Красноречивых Событий На Модели Boot (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. Использование Красноречивых Наблюдателей Событий (ref:https://laravel.com/docs/5.7/eloquent#observers)

в вашем AppServiceProvider зарегистрируйте наблюдателя следующим образом:

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

затем добавьте класс наблюдателя, например Итак:

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

3. Использование Ограничений Внешнего Ключа (ref:https://laravel.com/docs/5.7/migrations#foreign-key-constraints)

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

или вы можете сделать это, если хотите, просто еще один вариант:

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

да, но, как указано выше в комментарии @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() -> это не приведет к запуску события удаления на post, потому что мы фактически не загружаем модель Post (мы запускаем только SQL, например: DELETE * from posts where user_id = $user->id и таким образом, модель Post даже не загружается)