स्वचालित रूप से लारवेल (संबंधित ORM) में संबंधित पंक्तियों को हटाना


158

जब मैं इस सिंटैक्स का उपयोग करके एक पंक्ति हटाता हूं:

$user->delete();

वहाँ एक तरह की कॉलबैक संलग्न करने के लिए एक तरीका है, ताकि यह स्वचालित रूप से ऐसा करेगा:

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

अधिमानतः मॉडल-क्लास के अंदर।

जवाबों:


205

मेरा मानना ​​है कि यह एलोकेंट इवेंट्स ( 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...
        });
    }
}

आपको संभवत: संपूर्ण वस्तु को लेन-देन के अंदर रखना चाहिए, ताकि संदर्भात्मक अखंडता सुनिश्चित हो सके।


7
नोट: मैं कुछ समय बिताता हूं जब तक मुझे यह काम नहीं मिला। मुझे first()क्वेरी में जोड़ने की आवश्यकता थी ताकि मैं मॉडल-ईवेंट का उपयोग कर User::where('id', '=', $id)->first()->delete();
सकूं

6
@MichelAyres: हाँ, आपको एक मॉडल उदाहरण पर डिलीट () कॉल करने की आवश्यकता है, क्वेरी बिल्डर पर नहीं। बिल्डर के पास अपनी स्वयं की डिलीट () विधि है जो मूल रूप से केवल एक DELETE sql क्वेरी चलाता है, इसलिए मुझे लगता है कि यह
ऑर्म्स

3
यह नरम-हटाने के लिए जाने का तरीका है। मेरा मानना ​​है कि नया / पसंदीदा Laravel तरीका इन सभी को AppServiceProvider के बूट () विधि से इस तरह से चिपकाना है: \ App \ उपयोगकर्ता :: हटाना (फ़ंक्शन ($ u) {$ u-> फ़ोटो) () - " );});
जलकायमन

4
लगभग लारावेल 5.5 में काम किया, मुझे एक जोड़ना था foreach($user->photos as $photo), फिर $photo->delete()यह सुनिश्चित करने के लिए कि प्रत्येक बच्चे को उसके बच्चों को सभी स्तरों पर हटा दिया गया था, केवल एक के बजाय यह किसी कारण से हो रहा था।
जॉर्ज

9
हालांकि यह इसे आगे नहीं करता है। उदाहरण के लिए यदि Photosहै tagsऔर आप में भी ऐसा ही Photosमॉडल (पर यानी deletingविधि: $photo->tags()->delete();) यह ट्रिगर हो जाता है कभी नहीं। लेकिन अगर मैं इसे एक forलूप बनाऊं और कुछ ऐसा करूं for($user->photos as $photo) { $photo->delete(); }तो वह tagsभी डिलीट हो जाए! सिर्फ FYI करें
सुपरनैन

200

आप वास्तव में इसे अपने माइग्रेशन में सेट कर सकते हैं:

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

स्रोत: http://laravel.com/docs/5.1/migrations#foreign-key-constraints

आप "हटाएं" और "अपडेट पर" बाधा के गुणों के लिए वांछित कार्रवाई भी निर्दिष्ट कर सकते हैं:

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

हाँ, मुझे लगता है कि मुझे उस निर्भरता को स्पष्ट करना चाहिए था।
क्रिस Schmitz

62
लेकिन अगर आप सॉफ्ट डिलीट का उपयोग नहीं कर रहे हैं, क्योंकि पंक्तियाँ वास्तव में डिलीट नहीं हैं।
कांपते हुए

7
इसके अलावा - यह डीबी में रिकॉर्ड को हटा देगा, लेकिन आपकी डिलीट विधि को नहीं चलाएगा, इसलिए यदि आप डिलीट (उदाहरण के लिए - डिलीट फाइल्स) पर अतिरिक्त काम कर रहे हैं, तो यह नहीं चलेगा
amosmos

10
यह दृष्टिकोण कैस्केड डिलीट करने के लिए DB पर निर्भर करता है, लेकिन सभी DB इसका समर्थन नहीं करते हैं, इसलिए अतिरिक्त देखभाल की आवश्यकता होती है। उदाहरण के लिए MySQL के साथ MySQL इंजन नहीं है, न ही कोई NoSQL DBs, डिफ़ॉल्ट सेटअप में SQLite, आदि। अतिरिक्त समस्या यह है कि कारीगर आपको इस बारे में चेतावनी नहीं देंगे जब आप माइग्रेशन चलाते हैं, तो यह सिर्फ MySAM तालिकाओं पर विदेशी कुंजी नहीं बनाएगा और जब आप बाद में कोई रिकॉर्ड हटाते हैं तो कोई कैस्केड नहीं होगा। मुझे एक बार यह समस्या हुई और मेरा मानना ​​है कि यह डिबग करना बहुत कठिन है।
ivanhoe

1
@kehinde आपके द्वारा दिखाया गया दृष्टिकोण संबंधों को हटाने वाली घटनाओं को हटाने के लिए नहीं है। आपको रिलेशन को लेकर चलना चाहिए और कॉल डिलीट को अलग-अलग करना चाहिए।
टॉम

51

नोट : यह उत्तर Laravel 3 के लिए लिखा गया था । इस प्रकार लारवेल के अधिक हाल के संस्करण में अच्छी तरह से काम कर सकता है या नहीं।

उपयोगकर्ता को हटाने से पहले आप सभी संबंधित फ़ोटो हटा सकते हैं।

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

आशा करता हूँ की ये काम करेगा।


1
आपको उपयोग करना है: foreach ($ this-> फोटो as $ photo) ($ this-> फोटो बजाय $ this-> photos ()) अन्यथा, अच्छा टिप!
बृजदेव Bar

20
इसे और अधिक कुशल बनाने के लिए, एक क्वेरी का उपयोग करें: फोटो :: जहां ("user_id", $ यह-> आईडी) -> हटाना (); नहीं सबसे अच्छा तरीका है, लेकिन केवल 1 क्वेरी, जिस तरह से बेहतर प्रदर्शन करता है, तो उपयोगकर्ता ने 1.000.000 तस्वीर की।
डिर्क

5
वास्तव में आप कॉल कर सकते हैं: $ यह-> फ़ोटो () -> हटाना (); लूप की कोई आवश्यकता नहीं - ivanhoe
ivanhoe

4
@ivanhoe मैंने देखा कि यदि आप संग्रह को हटाते हैं, तो डिलीट होने वाली घटना फोटो में आग नहीं लगेगी, हालांकि, अखाड़े के रूप में इसके माध्यम से पुनरावृत्ति करने से घटना को आग लगने का कारण होगा। क्या यह एक बग है?
adamkrell

1
@ आख़िरकार, आप इसे कर सकते हैं $this->photos()->delete()photos()क्वेरी बिल्डर वस्तु देता है।
स्वेन वैन ज़ोलेन

32

उपयोगकर्ता मॉडल में संबंध:

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

रिकॉर्ड हटाएं और संबंधित:

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

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

$user->delete();

4
यह काम करता है, लेकिन $ उपयोगकर्ता का उपयोग करने के लिए देखभाल का उपयोग करें () -> संबंध () -> अलग () अगर वहाँ एक piviot तालिका शामिल है (hasMany / अंतर्गत संबंध संबंधों के मामले में), या फिर आप संदर्भ हटा देंगे, संबंध नहीं ।
जेम्स बैली

यह मेरे लिए काम करता है लार्वा 6. @ क्या आप अधिक pls समझा सकते हैं?
अरमान एच

20

इसे हल करने के लिए 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. एलोकेंट इवेंट ऑब्जर्वर का उपयोग करना (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. विदेशी कुंजी बाधाओं (रेफरी: https://laravel.com/docs/5.7/migrations#foreign-key-constraints ) का उपयोग करना

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

1
मुझे लगता है कि 3 विकल्प सबसे सुरुचिपूर्ण हैं क्योंकि डेटाबेस में ही बाधा बन रही है। मैं इसका परीक्षण करता हूं और ठीक काम करता हूं।
गिल्बर्ट

14

लारावेल 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
लारवेल 5.3 उन्हें पर्यवेक्षकों के नाम से अलग-अलग कक्षाओं में रखने की सलाह देते हैं - जबकि यह केवल 5.3 में प्रलेखित है, हालांकि Eloquent::observe()विधि 5.2 में भी उपलब्ध है और इसका उपयोग ऐपस्वाइसप्रोविडर से किया जा सकता है।
लीथ

3
आप किसी भी 'hasMany' संबंधों है से अपने photos(), आप भी सावधान रहने की आवश्यकता होगी - इस प्रक्रिया को होगा नहीं हटाने पोते क्योंकि आप मॉडल लोड नहीं कर रहे हैं। आपको डिलीट से संबंधित घटनाओं को आग लगाने के लिए मॉडल के रूप में लूप photos(नोट, नहीं photos()) और delete()उन पर विधि को आग लगाने की आवश्यकता होगी।
लेथ

1
@Leith निरीक्षण विधि 5.1 में भी उपलब्ध है।
टायलर रीड

2

इसके लिए deleteविधि को ओवरराइड करें तो बेहतर है । इस तरह, आप डीबी लेनदेन को deleteविधि के भीतर ही शामिल कर सकते हैं । यदि आप ईवेंट तरीके का उपयोग करते हैं, तो आपको deleteहर बार कॉल करने के दौरान डीबी लेनदेन के साथ अपने कॉल ऑफ मेथड को कवर करना होगा।

अपने Userमॉडल में।

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

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

    $result = parent::delete();

    \DB::commit();

    return $result;
}

1

मेरे मामले में यह बहुत आसान था क्योंकि मेरे डेटाबेस टेबल इनकोडी हैं जो कैस्केड के साथ विदेशी कुंजी के साथ डिलीट हैं।

तो इस मामले में यदि आपकी फ़ोटो तालिका में उपयोगकर्ता के लिए एक विदेशी कुंजी संदर्भ है, जो आपको करना है, तो होटल को हटाना है और डेटा बेस द्वारा क्लीनअप किया जाएगा, डेटा बेस डेटा से सभी फ़ोटो रिकॉर्ड हटा देगा आधार।


जैसा कि अन्य उत्तरों में उल्लेख किया गया है, सॉफ्ट डिलीट का उपयोग करते समय डेटाबेस लेयर पर कैस्केडिंग विलोपन काम नहीं करेगा। सावधान ग्राहक। :)
बेन जॉनसन

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

0

या आप यह कर सकते हैं यदि आप चाहते थे, तो बस एक और विकल्प:

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

ध्यान दें कि यदि आप डिफ़ॉल्ट लार्वा डीबी कनेक्शन का उपयोग नहीं कर रहे हैं तो आपको निम्न कार्य करने की आवश्यकता है:

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

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

आप केवल रिलेशनशिप आईडी कॉलम को क्वेरी करके प्रदर्शन में सुधार कर सकते हैं:

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

हाँ, लेकिन जैसा कि @supersan ने एक टिप्पणी में ऊपरी तौर पर कहा है, अगर आप QueryBuilder पर हटाते हैं (), तो मॉडल ईवेंट को निकाल नहीं दिया जाएगा, क्योंकि हम मॉडल को स्वयं लोड नहीं कर रहे हैं, फिर उस मॉडल पर कॉल हटाएं ()।

यदि हम किसी मॉडल इंस्टेंस पर डिलीट फंक्शन का उपयोग करते हैं, तो ईवेंट केवल निकाल दिए जाते हैं।

तो, इस मधुमक्खी ने कहा:

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

उपयोगकर्ता को हटाते समय पोस्ट टैग को हटाने के लिए, हमें $user->postsकॉल और कॉल करना होगा$post->delete()

foreach($user->posts as $post) { $post->delete(); } -> यह पोस्ट पर डिलीट करने की घटना को आग देगा

वी.एस.

$user->posts()->delete()-> यह पोस्ट पर डिलीट करने वाले ईवेंट को आग नहीं देगा क्योंकि हम वास्तव में पोस्ट मॉडल को लोड नहीं करते हैं (हम केवल एक SQL चलाते हैं: DELETE * from posts where user_id = $user->idऔर इस प्रकार, पोस्ट मॉडल भी लोड नहीं होता है)


-2

आप इस विधि का विकल्प के रूप में उपयोग कर सकते हैं।

क्या होगा यह है कि हम उपयोगकर्ता तालिका से जुड़े सभी तालिकाओं को लेते हैं और लूपिंग का उपयोग करके संबंधित डेटा को हटाते हैं

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

मुझे नहीं लगता कि ये सभी प्रश्न आवश्यक हैं क्योंकि एलोकेंट ऑरम इसे संभाल सकते हैं यदि आप इसे स्पष्ट रूप से निर्दिष्ट करते हैं।
--१
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.