Как мягко удалить связанные записи при мягком удалении родительской записи в Laravel?

У меня есть эта таблица счетов, которая имеет следующую структуру

id | name | amount | deleted_at
2    iMac   1500   | NULL

и таблица платежей со следующей структурой

id | invoice_id | amount | deleted_at
2    2            1000   | NULL

Модель счета

class Invoice extends Model {

    use SoftDeletes;

}

вот код для удаления счета

public function cance(Request $request,$id)
{
    $record = Invoice::findOrFail($id);
    $record->delete();
    return response()->json([
        'success' => 'OK',
    ]);
}

Платежная модель

class Payment extends Model {

    use SoftDeletes;

}

SoftDelete в таблице Invoice работает отлично, но связанные с ней записи (платежи) все еще существуют. Как удалить их с помощью softDelete?


person user3407278    schedule 23.08.2015    source источник


Ответы (4)


Eloquent не обеспечивает автоматическое удаление связанных объектов, поэтому вам придется написать некоторый код самостоятельно. К счастью, это довольно просто.

Модели Eloquent запускают разные события на разных этапах жизненного цикла модели, таких как создание, создание, удаление, удаление и т. д. Подробнее об этом можно прочитать здесь: http://laravel.com/docs/5.1/eloquent#events. Что вам нужно, так это прослушиватель, который будет запускаться при запуске события deleted — этот прослушиватель должен затем удалить все связанные объекты.

Вы можете зарегистрировать прослушиватели модели в методе boot() вашей модели. Слушатель должен перебрать все платежи по удаляемому счету и удалить их один за другим. Массовое удаление здесь не сработает, поскольку оно будет выполнять SQL-запрос напрямую, минуя события модели.

Это поможет:

class MyModel extends Model {
  protected static function boot() {
    parent::boot();

    static::deleted(function ($invoice) {
      $invoice->payments()->delete();
    });
  }
}
person jedrzej.kurylo    schedule 23.08.2015
comment
Не работает! FatalErrorException в строке Invoice.php 18: невозможно сделать статический метод Illuminate\Database\Eloquent\Model::boot() нестатическим в классе App\Models\Invoice - person user3407278; 23.08.2015
comment
Исправлено, в функции отсутствовал модификатор static - person jedrzej.kurylo; 23.08.2015
comment
Это сработало прекрасно! Большое спасибо! Вызывает ли это какие-либо проблемы с производительностью, когда мягкое удаление говорит о 100 записях? - person user3407278; 23.08.2015
comment
Вы должны получить их все, а затем сохранить каждый из них, так что это дополнительные 101 запрос... В таком случае вы можете вручную установить delete_at для связанных моделей, это будет менее чисто, но будет выполняться только один SQL-запрос. Я обновлю ответ через секунду - person jedrzej.kurylo; 23.08.2015
comment
Я знаю, что это старо, но я бы очень рекомендовал использовать наблюдателей для достижения этой цели. - person stetim94; 11.11.2019
comment
Работал как шарм! - person Edinaldo Ribeiro; 27.11.2020

Вы можете пойти одним из двух способов с этим.

Самый простой способ — переопределить метод Eloquents delete() и включить связанные модели, например:

public function delete()
{
    $this->payments()->delete();
    return parent::delete();
} 

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

Более чистым способом (IMO) было бы использовать события Eloquents, например:

public static function boot()
{
    parent::boot();

    static::deleting(function($invoice) { 
         $invoice->payments()->delete();

    });
}

Любой (но не оба) из вышеперечисленных методов будет использоваться в вашей модели Invoice. Кроме того, я предполагаю, что в вашей модели настроены отношения, однако я не уверен, разрешаете ли вы несколько платежей по одному счету. В любом случае вам может понадобиться изменить payments() в примерах на то, что вы назвали отношением в своей модели счета.

Надеюсь это поможет!

person Rwd    schedule 23.08.2015
comment
Вызов delete() непосредственно для отношения платежей будет обходить модель и не будет запускать SoftDelete для связанных моделей. Они будут удалены из базы данных. Чтобы сделать их мягко удаленными, вам нужно вызвать delete() для каждой из связанных моделей. - person jedrzej.kurylo; 23.08.2015
comment
Вам также не хватает оператора return в вашем переопределенном методе удаления - он должен возвращать parent::delete();, иначе вы потеряете значение, которое было бы возвращено из delete(), если бы вы его не перезаписали. - person jedrzej.kurylo; 23.08.2015
comment
@jedrzej.kurylo, я только что проверил, чтобы убедиться, что ДА, вы можете использовать мягкое удаление в отношениях! - person Rwd; 23.08.2015
comment
Правда, только что проверил код. Полезно знать на будущее :) - person jedrzej.kurylo; 23.08.2015
comment
гарантирует ли это транзакцию @RossWilson - person Tomonso Ejang; 20.07.2018
comment
@TomonsoEjang Что значит извини? Действительно ли он их удалит? Или он будет работать, если он завернут в транзакцию? - person Rwd; 20.07.2018
comment
@RossWilson об удалении события модели. Может ли случиться так, что связанная запись удалена, а исходная модель, подлежащая удалению, не может быть удалена (здесь удалено означает, что удаленный_ат обновлен до некоторой временной метки) - person Tomonso Ejang; 20.07.2018
comment
@TomonsoEjang Если вы заключаете вызов в транзакцию, он все равно должен вести себя так же. Нет никакого асинхронного поведения, поэтому он по-прежнему будет запускать транзакцию, запускать код в моделях и затем завершать транзакцию... если есть проблема, транзакция будет отменена. - person Rwd; 20.07.2018

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

Или вы можете использовать этот пакет, он тоже полезен.

Не забудьте установить правильную версию в зависимости от вашей версии laravel.

Вы должны установить его через композитор:

 composer require askedio/laravel5-soft-cascade ^version

Во втором пакете:

 composer require iatstuti/laravel-cascade-soft-deletes

Зарегистрируйте поставщика услуг в вашем config/app.php.

вы можете прочитать документы на странице GitHub.

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

Если у вас есть другое отношение в вашей дочерней модели, используйте его черту и в этой модели. это намного проще, чем делать это вручную.

Преимущество второго пакета заключается в удалении потомков модели. в некоторых случаях я говорю, что это лучший подход.

person Salar Bahador    schedule 18.04.2018
comment
Я использовал второй пакет, и он отлично работал для удаления(), но могу ли я заставить его работать и для восстановления? - person hassanrazadev; 26.03.2020

Если отношения вашей базы данных не выходят за пределы одного уровня, вы можете просто использовать события Laravel для обработки обратимых удалений в методе Model boot() следующим образом:

<?php
//...

    protected static boot() {
        parent::boot();

        static::deleting(function($invoice) { 
             $invoice->payments()->delete();

        }); 
    }

Однако, если ваша структура глубже одного уровня, вам придется настроить этот фрагмент кода.

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

<?php

// ...

class Invoice extends Model
{
    // ...

    /**
     * Holds the methods names of Eloquent Relations
     * to fall on delete cascade or on restoring
     * 
     * @var array
     */
    protected static $relations_to_cascade = ['payments']; 

    protected static boot()
    {
        parent::boot();

        static::deleting(function($resource) {
            foreach (static::$relations_to_cascade as $relation) {
                foreach ($resource->{$relation}()->get() as $item) {
                    $item->delete();
                }
            }
        });

        static::restoring(function($resource) {
            foreach (static::$relations_to_cascade as $relation) {
                foreach ($resource->{$relation}()->get() as $item) {
                    $item->withTrashed()->restore();
                }
            }
        });
    }

    public function payments()
    {
        return $this->hasMany(Payment::class);
    }
}

<?php

// ...

class User extends Model
{
    // ...

    /**
     * Holds the methods names of Eloquent Relations 
     * to fall on delete cascade or on restoring
     * 
     * @var array
     */
    protected static $relations_to_cascade = ['invoices']; 

    protected static boot()
    {
        parent::boot();

        static::deleting(function($resource) {
            foreach (static::$relations_to_cascade as $relation) {
                foreach ($resource->{$relation}()->get() as $item) {
                    $item->delete();
                }
            }
        });

        static::restoring(function($resource) {
            foreach (static::$relations_to_cascade as $relation) {
                foreach ($resource->{$relation}()->get() as $item) {
                    $item->withTrashed()->restore();
                }
            }
        });
    }

    public function invoices()
    {
        return $this->hasMany(Invoice::class);
    }
}

Эта парадигма гарантирует, что Laravel будет следовать по кроличьей норе, какой бы глубокой она ни была.

person Waiyl Karim    schedule 31.12.2019