การป้องกันไม่ให้ Laravel เพิ่มหลายระเบียนในตาราง Pivot


104

ฉันมีความสัมพันธ์หลายต่อหลายอย่างที่ตั้งค่าและทำงานเพื่อเพิ่มรายการลงในรถเข็นที่ฉันใช้:

$cart->items()->attach($item);

ซึ่งจะเพิ่มรายการลงในตาราง Pivot (ตามที่ควร) แต่หากผู้ใช้คลิกที่ลิงก์อีกครั้งเพื่อเพิ่มรายการที่ได้เพิ่มไปแล้วจะสร้างรายการที่ซ้ำกันในตาราง Pivot

มีวิธีในการเพิ่มระเบียนลงในตาราง Pivot เฉพาะในกรณีที่ไม่มีอยู่แล้วหรือไม่?

ถ้าไม่ฉันจะตรวจสอบตาราง Pivot เพื่อดูว่ามีระเบียนที่ตรงกันอยู่แล้วได้อย่างไร

คำตอบ:


77

คุณสามารถตรวจสอบการมีอยู่ของบันทึกที่มีอยู่ได้โดยเขียนเงื่อนไขง่ายๆเช่นนี้:

if (! $cart->items->contains($newItem->id)) {
    $cart->items()->save($newItem);
}

หรือ / และคุณสามารถเพิ่มเงื่อนไข unicity ในฐานข้อมูลของคุณได้ซึ่งจะทำให้เกิดข้อยกเว้นในระหว่างการพยายามบันทึก doublet

คุณควรดูคำตอบที่ตรงไปตรงมามากขึ้นจาก Barryvdh ด้านล่าง


1
พารามิเตอร์ $ id สำหรับวิธีการattach()ผสมอาจเป็น int หรืออินสแตนซ์ของโมเดลก็ได้) - ดูgithub.com/laravel/framework/blob/master/src/Illuminate/…
Rob Gordijn

ขอบคุณ @RobGordijn. วันนี้ฉันได้เรียนรู้อะไรบางอย่าง! แก้ไขคำตอบแล้ว
Alexandre Butynski

1
containsคำสั่ง@bagusflyer ตรวจสอบว่ามีคีย์อยู่ในวัตถุหนึ่งในคอลเล็กชันหรือไม่ คุณควรมีข้อผิดพลาดในรหัสของคุณ
Alexandre Butynski

5
ฉันไม่ชอบวิธีแก้ปัญหานี้เพราะมันบังคับให้มีการสืบค้นเพิ่มเติม ($ cart-> items) ฉันได้ทำสิ่งที่ชอบ: $cart->items()->where('foreign_key', $foreignKey)->count() ซึ่งก็ทำการค้นหาเพิ่มเติมด้วย '^^ แต่ฉันไม่จำเป็นต้องดึงข้อมูลและ เติมความชุ่มชื้นให้กับคอลเลกชันทั้งหมดเว้นแต่ว่าฉันต้องการจริงๆ
เงียบ

2
ถูกต้องโซลูชันของคุณได้รับการปรับให้เหมาะสมขึ้นเล็กน้อย คุณยังสามารถใช้exists()ฟังก์ชันแทนcount()การเพิ่มประสิทธิภาพที่ดีที่สุดได้อีกด้วย
Alexandre Butynski

273

คุณยังสามารถใช้$model->sync(array $ids, $detaching = true)วิธีการและปิดใช้งานการถอด (พารามิเตอร์ตัวที่สอง)

$cart->items()->sync([$item->id], false);

อัปเดต: ตั้งแต่ Laravel 5.3 หรือ 5.2.44 คุณยังสามารถเรียกใช้ syncWithoutDetaching:

$cart->items()->syncWithoutDetaching([$item->id]);

ซึ่งเหมือนกันทุกประการ แต่อ่านได้มากกว่า :)


2
พารามิเตอร์บูลีนตัวที่สองเพื่อป้องกันการปลด ID ที่ไม่แสดงไม่อยู่ในเอกสาร L5 หลัก เป็นสิ่งที่ดีมากที่ทราบ - ดูเหมือนจะเป็นวิธีเดียวที่จะ "แนบถ้ายังไม่ได้แนบ" ในคำสั่งเดียว
Jason

11
สิ่งนี้ควรได้รับการยอมรับว่าเป็นคำตอบมันเป็นวิธีที่ดีกว่าคำตอบที่ยอมรับ
Daniel

4
สำหรับมือใหม่อย่างผม: $cart->items()->sync([1, 2, 3])จะสร้างความสัมพันธ์ที่หลายต่อหลายคนเป็น ID ของในอาร์เรย์ที่กำหนด1, 2และ3และลบ (หรือ "ถอด") ทุกรหัสอื่น ๆ ที่ไม่ได้อยู่ในอาร์เรย์ วิธีนี้จะมีเพียง id ที่ระบุในอาร์เรย์เท่านั้นที่จะมีอยู่ในตาราง Brilliant @Barryvdh ใช้พารามิเตอร์ที่สองที่ไม่มีเอกสารเพื่อปิดใช้งานการแยกออกดังนั้นจึงไม่มีการลบความสัมพันธ์ที่ไม่ได้อยู่ในอาร์เรย์ที่กำหนด แต่จะแนบเฉพาะ id ที่ไม่ซ้ำกันเท่านั้น ตรวจสอบเอกสาร "การซิงค์เพื่อความมั่นใจ" (Laravel 5.2)
ไอคอนประจำตัว

3
FYI ฉันอัปเดตเอกสารดังนั้น 5.2 ขึ้นไป: laravel.com/docs/5.2/…และ Taylor ได้เพิ่มการเพิ่มทันทีsyncWithoutDetaching()ซึ่งเรียกการ sync () โดยมี false เป็นพารามิเตอร์ที่สอง
Barryvdh

Laravel 5.5 laravel.com/docs/5.5/… syncWithoutDetaching()ได้ผล!
Josh LaMar

2

วิธีการ @alexandre Butynsky ทำงานได้ดีมาก แต่ใช้แบบสอบถาม sql สองรายการ

หนึ่งในการตรวจสอบว่ารถเข็นมีสินค้าหรือไม่และอีกรายการที่จะบันทึก

ในการใช้แบบสอบถามเดียวให้ใช้สิ่งนี้:

try {
    $cart->items()->save($newItem);
}
catch(\Exception $e) {}

นี่คือสิ่งที่ฉันทำในโครงการของฉัน แต่ปัญหาคือคุณไม่ทราบว่าข้อยกเว้นนี้เกิดจากรายการที่ซ้ำกันหรือสิ่งอื่นใด
Bagusflyer

2
ทำไมมันถึงทิ้งข้อยกเว้น? มันจะเป็นถ้ามีคีย์ที่ไม่ซ้ำใคร
Seiji Manoan

1

คำตอบทั้งหมดนี้เป็นเพราะฉันได้ลองใช้ทั้งหมดแล้วมีสิ่งหนึ่งที่ยังไม่ได้รับการตอบรับหรือไม่ได้รับการดูแล: ปัญหาในการอัปเดตค่าที่เลือกไว้ก่อนหน้านี้ (ไม่เลือกช่องทำเครื่องหมาย [es]) ฉันมีบางอย่างที่คล้ายกับคำถามข้างต้นคาดว่าฉันต้องการตรวจสอบและยกเลิกการเลือกคุณสมบัติของผลิตภัณฑ์ในตารางคุณลักษณะผลิตภัณฑ์ของฉัน (ตาราง Pivot) ฉันเป็นมือใหม่และฉันไม่ได้ตระหนักถึงสิ่งที่กล่าวมาข้างต้น ทั้งสองอย่างดีเมื่อเพิ่มคุณสมบัติใหม่ แต่ไม่ใช่เมื่อฉันต้องการลบคุณสมบัติที่มีอยู่ (เช่นยกเลิกการเลือก)

ฉันจะขอบคุณการตรัสรู้ใด ๆ ในเรื่องนี้

$features = $request->get('features');

if (isset($features) && Count($features)>0){
    foreach ($features as $feature_id){
        $feature = Feature::whereId($feature_id)->first();
        $product->updateFeatures($feature);
    }
}

//product.php (extract)
public function updateFeatures($feature) {
        return $this->features()->sync($feature, false);
}

หรือ

public function updateFeatures($feature) {
   if (! $this->features->contains($features))
        return $this->features()->attach($feature);
}
//where my attach() is:
public function addFeatures($feature) {
        return $this->features()->attach($feature);
}

ขอโทษนะไม่แน่ใจว่าฉันควรจะลบคำถามหรือไม่เพราะการหาคำตอบด้วยตัวเองมันฟังดูโง่ไปหน่อยคำตอบข้างบนนั้นง่ายพอ ๆ กับการทำงาน @Barryvdh sync () ดังนี้; ได้อ่านมากขึ้นเกี่ยวกับ:

$features = $request->get('features');
if (isset($features) && Count($features)>0){
    $product->features()->sync($features);
}

0

มีบางคำตอบที่ยอดเยี่ยมโพสต์แล้ว ฉันอยากจะโยนอันนี้ทิ้งที่นี่เหมือนกัน

คำตอบจาก @AlexandreButynski และ @Barryvdh นั้นอ่านได้มากกว่าคำแนะนำของฉันสิ่งที่คำตอบนี้เพิ่มคือประสิทธิภาพบางอย่าง

จะดึงเฉพาะรายการสำหรับชุดค่าผสมปัจจุบัน (จริงๆแล้วมีเพียง id เท่านั้น) และมากกว่าที่จะแนบหากไม่มีอยู่ วิธีการซิงค์ (แม้จะไม่ได้ถอดออก) จะดึงรหัสที่แนบมาทั้งหมดในปัจจุบัน สำหรับชุดขนาดเล็กที่มีการทำซ้ำเพียงเล็กน้อยสิ่งนี้แทบจะไม่แตกต่าง ... คุณเข้าใจฉันแล้ว

อย่างไรก็ตามมันไม่สามารถอ่านได้อย่างแน่นอน แต่มันใช้เคล็ดลับ

if (is_null($book->authors()->find($author->getKey(), [$author->getQualifiedKeyName()])))
    $book->authors()->attach($author);
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.