Laravel'de (Eloquent ORM) ilgili satırları otomatik olarak silme


158

Bu sözdizimini kullanarak bir satırı sildiğimde:

$user->delete();

Bir tür geri arama eklemenin bir yolu var mı, örneğin bunu otomatik olarak yapar:

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

Tercihen model sınıfı içinde.

Yanıtlar:


205

Bunun Eloquent olayları için mükemmel bir kullanım örneği olduğuna inanıyorum ( http://laravel.com/docs/eloquent#model-events ). Temizleme işlemini yapmak için "silme" etkinliğini kullanabilirsiniz:

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

Referans bütünlüğünü sağlamak için muhtemelen her şeyi bir işlemin içine koymalısınız.


7
Not: Çalışana kadar biraz zaman harcıyorum. Örnek olayfirst() erişmek için sorguya eklemek gerekiyordu örneğin KaynakUser::where('id', '=', $id)->first()->delete();
Michel Ayres

6
@MichelAyres: evet, Query Builder'da değil, model örneğinde delete () öğesini çağırmanız gerekir. Oluşturucu temelde sadece bir DELETE sql sorgusu çalıştıran kendi delete () yöntemine sahiptir, bu yüzden orm olayları hakkında hiçbir şey bilmediğini
varsayıyorum

3
Yumuşak silmelere giden yol budur. Yeni / tercih edilen Laravel yolunun AppServiceProvider'ın boot () yöntemine tüm bunları bu şekilde yapıştırmak olduğuna inanıyorum: \ App \ User :: silme (işlev ($ u) {$ u-> photos () -> delete ( );});
Watercayman

4
Neredeyse Laravel 5.5'te çalıştım foreach($user->photos as $photo), sonra eklemek zorunda kaldım , o zaman $photo->delete()her çocuğun bir sebepten dolayı olduğu gibi tek bir çocuk yerine tüm seviyelerinde çocuklarının kaldırıldığından emin olmak için.
George

9
Bu daha da artmıyor. Örneğin eğer Photosvardır tagsve içinde aynı şeyi Photos(üzerinde yani modeli deletingyöntemiyle: $photo->tags()->delete();) o tetiği geçmez. Bunun bir hale Ama eğer fordöngü ve böyle bir şey yapmak for($user->photos as $photo) { $photo->delete(); }sonra tagsda silinir! sadece FYI
supersan

200

Bunu aslında taşıma işlemlerinizde ayarlayabilirsiniz:

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

Kaynak: http://laravel.com/docs/5.1/migrations#foreign-key-constraints

Kısıtlamanın "silindiğinde" ve "güncellenirken" özellikleri için istediğiniz eylemi de belirtebilirsiniz:

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

Evet, sanırım bu bağımlılığı netleştirmeliydim.
Chris Schmitz

62
Ancak yumuşak silme kullanıyorsanız, satırlar gerçekten silinmediğinden değil.
tremby

7
Ayrıca - bu, DB'deki kaydı siler, ancak silme yönteminizi çalıştırmaz, bu nedenle silme (örneğin - dosyaları silme) üzerinde ekstra iş yapıyorsanız, çalışmaz
amosmos

10
Bu yaklaşım, basamaklı silme işlemi için DB'ye dayanır, ancak tüm DB'ler bunu desteklemez, bu nedenle ekstra dikkat gerekir. Örneğin, MyISAM motorlu MySQL veya varsayılan kurulumda herhangi bir NoSQL DB, SQLite vb. Yapmaz. Ek sorun, geçişleri çalıştırdığınızda esnaf sizi bu konuda uyarmayacak, MyISAM tablolarında yabancı anahtarlar oluşturmayacak ve daha sonra bir kaydı sildiğinizde herhangi bir basamaklama gerçekleşmez. Bu sorunu bir kez yaşadım ve inanıyorum ki hata ayıklamak çok zor.
ivanhoe

1
@kehinde Gösterdiğiniz yaklaşım, silinecek ilişkilerdeki silme olaylarını ÇALIŞMAZ. İlişki üzerinde yineleme yapmalı ve bireysel olarak silme çağrısı yapmalısınız.
Tom

51

Not : Bu cevap Laravel 3 için yazılmıştır . Bu nedenle Laravel'in daha yeni sürümünde iyi olabilir veya olmayabilir.

Kullanıcıyı gerçekten silmeden önce ilgili tüm fotoğrafları silebilirsiniz.

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

Umarım yardımcı olur.


1
Kullanmanız gerekenler: foreach ($ this-> fotoğraflar $ photo olarak) ($ this-> $ this-> photos () yerine fotoğraflar ()) Aksi takdirde, iyi bir ipucu!
Barryvdh

20
Daha verimli hale getirmek için bir sorgu kullanın: Photo :: where ("user_id", $ this-> id) -> delete (); Bir kullanıcı 1.000.000 fotoğraf varsa en güzel yolu değil, sadece 1 sorgu, yol daha iyi performans.
Dirk

5
aslında: $ this-> photos () -> delete (); döngü gerek yok - ivanhoe
ivanhoe

4
@ivanhoe Koleksiyonu silerseniz silme olayının fotoğrafta tetiklenmeyeceğini fark ettim, ancak akhyar'ın önerdiği gibi yineleme silme olayının tetiklenmesine neden olur. Bu bir hata mı?
adamkrell

1
@akhyar Neredeyse, bunu yapabilirsiniz $this->photos()->delete(). photos()Sorgu oluşturucu nesnesini geri gönderir.
Sven van Zoelen

32

Kullanıcı modelinde ilişki:

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

Kayıt silme ve ilgili:

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

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

$user->delete();

4
Bu işe yarar, ancak dahil bir piviot tablosu varsa (hasMany / belongsToMany ilişkilerinin olması durumunda) $ user () -> relation () -> detach () işlevini kullanmaya dikkat edin, aksi takdirde ilişkiyi değil referansı silersiniz .
James Bailey

Bu benim için çalışıyor laravel 6. @Calin daha fazla pls açıklayabilir misiniz?
Arman H

20

Bunu çözmek için 3 yaklaşım vardır:

1. Model Önyüklemesinde Eloquent Olayları Kullanma (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. Olağanüstü Olay Gözlemcilerini Kullanma (ref: https://laravel.com/docs/5.7/eloquent#observers )

AppServiceProvider'ınızda gözlemciyi şu şekilde kaydedin:

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

Sonra şöyle bir Observer sınıfı ekleyin:

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

3. Yabancı Anahtar Kısıtlamalarını Kullanma (ref: https://laravel.com/docs/5.7/migrations#foreign-key-constraints )

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

1
Ben veritabanının içine kısıtlama inşa beri 3 seçenek en zarif olduğunu düşünüyorum. Test ediyorum ve gayet iyi çalışıyor.
Gilbert

14

Laravel 5.2 itibariyle, belgeler bu tür olay işleyicilerin AppServiceProvider'da kaydedilmesi gerektiğini belirtir:

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

Hatta onları daha iyi uygulama yapısı için kapaklar yerine ayrı sınıflara taşımayı düşünürüm.


1
Laravel 5.3, bunları Observers adlı ayrı sınıflara koymanızı önerir - ancak 5.3'te belgelenmesine rağmen, Eloquent::observe()yöntem 5.2'de de mevcuttur ve AppServiceProvider'dan kullanılabilir.
Leith

3
Eğer herhangi bir 'hasMany' ilişkileri varsa dan SİZİN photos(), ayrıca dikkatli olmak gerekir - bu işlem olacak değil sen modellerini silme torun yüklenmemesi çünkü. Silme ile ilgili olayları tetiklemek için döngü üzerine photos(not not not over photos()) ve delete()yöntemi yöntem olarak tetiklemeniz gerekir.
Leith

1
@Leith Gözlemleme Yöntemi 5.1'de de mevcuttur.
Tyler Reed

2

Bunun için deleteyöntemi geçersiz kılmanız daha iyidir . Bu şekilde, DB işlemlerini deleteyöntemin içine dahil edebilirsiniz . Olay yolunu kullanırsanız, deleteher çağrışınızda yöntem çağrınızı bir DB işlemiyle örtmeniz gerekir.

Sizin de Usermodelin.

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

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

    $result = parent::delete();

    \DB::commit();

    return $result;
}

1

Benim durumumda oldukça basitti çünkü veritabanı tablolarım InnoDB ile Cascade on Delete tuşlarına sahip.

Bu durumda, fotoğraf tablonuz kullanıcı için yapmanız gereken tek şey yabancı bir anahtar referansı içeriyorsa oteli silmek ve temizleme Veri Tabanı tarafından yapılacaksa, veri tabanı tüm fotoğraf kayıtlarını verilerden silecektir tabanı.


Diğer Yanıtlarda belirtildiği gibi, Soft Deletes kullanılırken veritabanı katmanındaki basamaklı silme işlemleri çalışmaz. Alıcı dikkat. :)
Ben Johnson

1

Nesnenin kendisini silmeden önce her şeyi ayıran koleksiyonda yineleme yapardım.

İşte bir örnek:

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

Otomatik olmadığını biliyorum ama çok basit.

Başka bir basit yaklaşım, modele bir yöntem sağlamak olacaktır. Bunun gibi:

public function detach(){
       try {

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

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

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

O zaman bunu ihtiyacınız olan yerde arayabilirsiniz:

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

0

Ya da isterseniz, başka bir seçenek yapabilirsiniz:

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

Varsayılan laravel db bağlantısını kullanmıyorsanız, aşağıdakileri yapmanız gerektiğini unutmayın:

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

0

Seçilen cevabı ayrıntılı olarak açıklamak için, ilişkilerinizde de silinmesi gereken alt ilişkiler varsa, önce tüm alt ilişki kayıtlarını almanız ve ardından delete() silme olaylarının da düzgün bir şekilde yöntemi gerekir.

Bunu daha üst düzey mesajlarla kolayca yapabilirsiniz .

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

Ayrıca yalnızca ilişki kimliği sütununu sorgulayarak performansı artırabilirsiniz:

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

evet, ancak bir yorumda @supersan'ın yukarıda belirttiği gibi, bir QueryBuilder'da () silerseniz, model olayı tetiklenmeyecektir, çünkü modelin kendisini yüklemiyoruz, sonra bu modelde delete () çağırıyoruz.

Olaylar yalnızca bir Model Eşgörünümünde silme işlevini kullanırsak tetiklenir.

Yani, bu arı şöyle dedi:

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

kullanıcıyı silerken posta etiketlerini silmek için tekrar tekrar aramamız $user->postsve$post->delete()

foreach($user->posts as $post) { $post->delete(); } -> Bu, Post'taki silme etkinliğini tetikler

VS

$user->posts()->delete()-> Bu, Post Modelini yüklemediğimiz için silme olayını gönderilmeyecek (yalnızca aşağıdaki gibi bir SQL çalıştırıyoruz: DELETE * from posts where user_id = $user->idve böylece Post modeli bile yüklenmiyor)


-2

Bu yöntemi alternatif olarak kullanabilirsiniz.

Ne olacağı, kullanıcı tablosuyla ilişkili tüm tabloları alıp ilgili verileri döngü kullanarak silmemizdir.

$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üm bu sorguların gerekli olduğunu düşünmüyorum çünkü eloquent orm bunu açıkça belirtirseniz bu sorunu çözebilir.
7
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.