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

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

$user->delete();

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

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

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

Ответ 1

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

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

Ответ 2

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

$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');

Ответ 3

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

Надеюсь, что это поможет.

Ответ 4

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

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

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

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

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

$user->delete();

Ответ 5

Как и в 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();
        });
    }

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

Ответ 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');

Ответ 7

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

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

Ответ 8

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

вот пример:

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

Ответ 9

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

В вашей модели User.

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

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

    $result = parent::delete();

    \DB::commit();

    return $result;
}

Ответ 10

Чтобы уточнить выбранный ответ, если ваши отношения также имеют дочерние отношения, которые необходимо удалить, вы должны сначала извлечь все записи дочерних отношений, а затем вызвать метод 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();
        });
    }
}

Ответ 11

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

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

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

Ответ 12

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

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

Ответ 13

да, но, как @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 даже не загружается)