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

115

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

$user->delete();

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

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

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

Теги:

11 ответов

154
Лучший ответ

Я считаю, что это идеальный вариант использования событий Eloquent (http://laravel.com/docs/eloquent#model-events). Вы можете использовать событие "delete" для очистки:

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

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

  • 5
    Примечание: я провожу некоторое время, пока не заработал. Мне нужно было добавить first() в запрос, чтобы я мог получить доступ к событию модели, например, User::where('id', '=', $id)->first()->delete(); Источник
  • 4
    @MichelAyres: да, вам нужно вызывать delete () для экземпляра модели, а не для Query Builder. У Builder есть собственный метод delete (), который в основном просто выполняет SQL-запрос DELETE, поэтому я предполагаю, что он ничего не знает о событиях orm ...
Показать ещё 11 комментариев
152

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

$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');
  • 1
    Это требует использования внешних ключей, верно?
  • 0
    Да, я думаю, мне следовало уточнить эту зависимость.
Показать ещё 10 комментариев
44

Примечание. Этот ответ был написан для 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 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 ()) В противном случае, хороший совет!
  • 19
    Чтобы сделать его более эффективным, используйте один запрос: Photo :: where ("user_id", $ this-> id) -> delete (); Не самый лучший способ, но только 1 запрос, способ повысить производительность, если у пользователя есть 1.000.000 фотографий.
Показать ещё 7 комментариев
19

Отношение в модели пользователя:

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

Удалить запись и связать:

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

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

$user->delete();
  • 1
    Это работает, но будьте осторожны, чтобы использовать $ user () -> ratio () -> detach (), если задействована сводная таблица (в случае отношений hasMany / ownToMany), иначе вы удалите ссылку, а не связь ,
10

Как и в 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 рекомендует помещать их в отдельные классы, называемые наблюдателями - хотя это документировано только в 5.3, хотя метод Eloquent::observe() доступен в 5.2 и может использоваться из AppServiceProvider.
  • 2
    Если у вас есть какие-либо отношения hasMany из ваших photos() , вам также нужно быть осторожным - этот процесс не удалит внуков, потому что вы не загружаете модели. Вам нужно будет зациклить photos (обратите внимание, не photos() ) и запустить метод delete() для них как моделей, чтобы инициировать события, связанные с удалением.
Показать ещё 1 комментарий
6

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

1. Использование красноречивых событий при загрузке модели (ссылка: 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. Использование ограничений внешнего ключа (ссылка: https://laravel.com/docs/5.7/migrations#foreign-key-constraints)

$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
  • 1
    Я думаю, что эти 3 варианта являются наиболее элегантными, поскольку встраивают ограничение в саму базу данных. Я проверяю это и работает просто отлично.
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();
1

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

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

  • 0
    Как было отмечено в других Ответах, каскадное удаление на уровне базы данных не будет работать при использовании Soft Deletes. Предостережение для покупателя. :)
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();
        });
    }
}
0

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

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

Итак, это существо сказало:

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

чтобы удалить теги $user->posts при удалении пользователя, нам нужно будет выполнить итерацию по $user->posts и вызову $post->delete()

foreach($user->posts as $post) { $post->delete(); } foreach($user->posts as $post) { $post->delete(); } → это вызовет событие удаления в посте

В.С.

$user->posts()->delete() → это не вызовет событие удаления сообщения, потому что мы на самом деле не загружаем модель сообщения (мы запускаем только SQL- DELETE * from posts where user_id = $user->id: DELETE * from posts where user_id = $user->id и, таким образом, модель Post даже не загружается)

-1

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

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

Обратите внимание, что если вы не используете соединение по умолчанию larvel db, вам необходимо сделать следующее:

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню