โคลนวัตถุที่เก่งกาจรวมถึงความสัมพันธ์ทั้งหมด?


87

มีวิธีใดในการโคลนวัตถุ Eloquent รวมถึงความสัมพันธ์ทั้งหมดได้อย่างง่ายดาย?

ตัวอย่างเช่นถ้าฉันมีตารางเหล่านี้:

users ( id, name, email )
roles ( id, name )
user_roles ( user_id, role_id )

นอกเหนือจากการสร้างแถวใหม่ในusersตารางโดยที่คอลัมน์ทั้งหมดเหมือนกันยกเว้น idควรสร้างแถวใหม่ในuser_rolesตารางโดยกำหนดบทบาทเดียวกันให้กับผู้ใช้ใหม่

สิ่งนี้:

$user = User::find(1);
$new_user = $user->clone();

ที่รุ่นผู้ใช้มี

class User extends Eloquent {
    public function roles() {
        return $this->hasMany('Role', 'user_roles');
    }
}

คำตอบ:


77

ทดสอบใน laravel 4.2 สำหรับความสัมพันธ์ belongToMany

หากคุณอยู่ในรุ่น:

    //copy attributes
    $new = $this->replicate();

    //save model before you recreate relations (so it has an id)
    $new->push();

    //reset relations on EXISTING MODEL (this way you can control which ones will be loaded
    $this->relations = [];

    //load relations on EXISTING MODEL
    $this->load('relation1','relation2');

    //re-sync everything
    foreach ($this->relations as $relationName => $values){
        $new->{$relationName}()->sync($values);
    }

3
ทำงานใน Laravel 7
Daniyal Javani

นอกจากนี้ยังใช้งานได้กับ Laravel 6 เวอร์ชันก่อนหน้า (ฉันเดาว่าน่าจะเป็นไปตามความคิดเห็นก่อนหน้านี้ :)) ขอบคุณ
mmmdearte

ทำงานใน Laravel 7.28.4 ฉันสังเกตเห็นว่ารหัสควรจะแตกต่างกันหากคุณพยายามเรียกใช้งานนอกโมเดล ขอบคุณ
Roman Grinev

56

นอกจากนี้คุณยังสามารถลองใช้ฟังก์ชั่นจำลองที่จัดทำโดยฝีปากกล้า:

http://laravel.com/api/4.2/Illuminate/Database/Eloquent/Model.html#method_replicate

$user = User::find(1);
$new_user = $user->replicate();
$new_user->push();

7
จริงๆแล้วคุณต้องโหลดความสัมพันธ์ที่คุณต้องการจำลองด้วย รหัสที่ระบุจะจำลองแบบจำลองพื้นฐานโดยไม่มีความสัมพันธ์ ในการโคลนความสัมพันธ์คุณสามารถรับผู้ใช้ด้วยความสัมพันธ์: $user = User::with('roles')->find(1);หรือโหลดหลังจากที่คุณมี Model: ดังนั้นสองบรรทัดแรกจะเป็น$user = User::find(1); $user->load('roles');
Alexander Taubenkorb

2
การโหลดความสัมพันธ์ดูเหมือนจะไม่จำลองความสัมพันธ์ด้วยอย่างน้อยก็ไม่ใช่ใน 4.1 ฉันต้องจำลองพาเรนต์จากนั้นวนซ้ำเด็ก ๆ ของต้นฉบับที่จำลองแบบพวกเขาและอัปเดตทีละครั้งเพื่อชี้ไปที่พาเรนต์ใหม่
Rex Schrader

replicate()จะตั้งค่าความสัมพันธ์และเรียกpush()ซ้ำเข้าไปในความสัมพันธ์และบันทึกไว้
Matt K

นอกจากนี้ในข้อ 5.2 คุณต้องวนลูปเด็ก ๆ และช่วยพวกเขาหลังจากทำซ้ำทีละคน ข้างใน foreach:$new_user->roles()->save($oldRole->replicate)
d.grassi84

28

คุณสามารถลองสิ่งนี้ ( การโคลนวัตถุ ):

$user = User::find(1);
$new_user = clone $user;

เนื่องจากcloneไม่ได้คัดลอกลึกดังนั้นวัตถุลูกจะไม่ถูกคัดลอกหากมีวัตถุลูกใด ๆ และในกรณีนี้คุณต้องคัดลอกวัตถุลูกโดยใช้cloneด้วยตนเอง ตัวอย่างเช่น:

$user = User::with('role')->find(1);
$new_user = clone $user; // copy the $user
$new_user->role = clone $user->role; // copy the $user->role

ในกรณีของคุณrolesจะเป็นคอลเลกชันของRoleวัตถุเพื่อให้แต่ละคอลเลกชันจะต้องมีการคัดลอกด้วยตนเองโดยใช้Role objectclone

นอกจากนี้คุณต้องระวังด้วยว่าหากคุณไม่โหลดrolesโดยใช้สิ่งwithเหล่านั้นจะไม่ถูกโหลดหรือจะไม่สามารถใช้งานได้ใน$userและเมื่อคุณเรียกใช้$user->rolesวัตถุเหล่านั้นจะถูกโหลดในเวลาทำงานหลังจากการโทรนั้น ของ$user->rolesจนนี้ผู้ที่rolesยังไม่ได้โหลด

อัปเดต:

คำตอบนี้ใช้สำหรับLarave-4และตอนนี้ Laravel เสนอreplicate()วิธีการตัวอย่างเช่น:

$user = User::find(1);
$newUser = $user->replicate();
// ...

2
ระวังเป็นเพียงสำเนาตื้น ๆ ไม่ใช่วัตถุย่อย / ลูก :-)
อัลฟ่า

1
@TheShiftExchange คุณอาจคิดว่ามันน่าสนใจฉันทำการทดลองเมื่อนานมาแล้ว ขอขอบคุณที่ยกนิ้วให้ :-)
The Alpha

1
นี่ไม่ได้คัดลอก id ของวัตถุด้วยหรือ? ทำให้ไม่มีประโยชน์สำหรับการออม?
Tosh

@Tosh ใช่แน่นอนและนั่นคือเหตุผลที่คุณต้องตั้งรหัสอื่นหรือnull:-)
The Alpha

1
plus1 สำหรับการเปิดเผยความลับ php: P
Metabolic

23

สำหรับ Laravel 5. ทดสอบด้วย hasMany รีเลชัน

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

$model->load('invoices');

$newModel = $model->replicate();
$newModel->push();


foreach($model->getRelations() as $relation => $items){
    foreach($items as $item){
        unset($item->id);
        $newModel->{$relation}()->create($item->toArray());
    }
}

ทำงานได้อย่างสมบูรณ์แบบ 5.6 ขอบคุณมาก
Ali Abbas

7

นี่คือเวอร์ชันอัปเดตของโซลูชันจาก @ sabrina-gelbart ที่จะโคลน hasMany ความสัมพันธ์ทั้งหมดแทนที่จะเป็นเพียงแค่ belongToMany ตามที่เธอโพสต์:

    //copy attributes from original model
    $newRecord = $original->replicate();
    // Reset any fields needed to connect to another parent, etc
    $newRecord->some_id = $otherParent->id;
    //save model before you recreate relations (so it has an id)
    $newRecord->push();
    //reset relations on EXISTING MODEL (this way you can control which ones will be loaded
    $original->relations = [];
    //load relations on EXISTING MODEL
    $original->load('somerelationship', 'anotherrelationship');
    //re-sync the child relationships
    $relations = $original->getRelations();
    foreach ($relations as $relation) {
        foreach ($relation as $relationRecord) {
            $newRelationship = $relationRecord->replicate();
            $newRelationship->some_parent_id = $newRecord->id;
            $newRelationship->push();
        }
    }

ยุ่งยากหากsome_parent_idไม่เหมือนกันสำหรับความสัมพันธ์ทั้งหมด สิ่งนี้มีประโยชน์ขอบคุณ
Dustin Graham

6

นี่อยู่ใน laravel 5.8 ยังไม่ได้ลองในเวอร์ชันเก่ากว่า

//# this will clone $eloquent and asign all $eloquent->$withoutProperties = null
$cloned = $eloquent->cloneWithout(Array $withoutProperties)

แก้ไขเพียงวันนี้ 7 เมษายน 2019 laravel 5.8.10 เปิดตัว

สามารถใช้การจำลองได้เลย

$post = Post::find(1);
$newPost = $post->replicate();
$newPost->save();

2

หากคุณมีคอลเล็กชันชื่อ $ user โดยใช้โค้ดต่อไปนี้จะสร้างคอลเล็กชันใหม่ที่เหมือนกับคอลเล็กชันเก่ารวมถึงความสัมพันธ์ทั้งหมด:

$new_user = new \Illuminate\Database\Eloquent\Collection ( $user->all() );

รหัสนี้ใช้สำหรับ laravel 5


1
คุณอย่าเพิ่งทำ$new = $old->slice(0)?
fubar

2

เมื่อคุณดึงวัตถุตามความสัมพันธ์ที่คุณต้องการและทำซ้ำหลังจากนั้นความสัมพันธ์ทั้งหมดที่คุณดึงมาจะถูกจำลองแบบด้วย ตัวอย่างเช่น:

$oldUser = User::with('roles')->find(1);
$newUser = $oldUser->replicate();

ฉันได้ทดสอบใน Laravel 5.5
elyasm

2

นี่คือลักษณะที่จะทำซ้ำความสัมพันธ์ที่โหลดทั้งหมดบนวัตถุซ้ำ ๆ คุณสามารถขยายสิ่งนี้สำหรับความสัมพันธ์ประเภทอื่น ๆ ได้อย่างง่ายดายเช่นตัวอย่างของ Sabrina สำหรับ belongToMany

trait DuplicateRelations
{
    public static function duplicateRelations($from, $to)
    {
        foreach ($from->relations as $relationName => $object){
            if($object !== null) {
                if ($object instanceof Collection) {
                    foreach ($object as $relation) {
                        self::replication($relationName, $relation, $to);
                    }
                } else {
                    self::replication($relationName, $object, $to);
                }
            }
        }
    }

    private static function replication($name, $relation, $to)
    {
        $newRelation = $relation->replicate();
        $to->{$name}()->create($newRelation->toArray());
        if($relation->relations !== null) {
            self::duplicateRelations($relation, $to->{$name});
        }
    }
}

การใช้งาน:

//copy attributes
$new = $this->replicate();

//save model before you recreate relations (so it has an id)
$new->push();

//reset relations on EXISTING MODEL (this way you can control which ones will be loaded
$this->relations = [];

//load relations on EXISTING MODEL
$this->load('relation1','relation2.nested_relation');

// duplication all LOADED relations including nested.
self::duplicateRelations($this, $new);

0

นี่เป็นอีกวิธีหนึ่งที่ทำได้หากวิธีแก้ปัญหาอื่นไม่ถูกใจคุณ:

<?php
/** @var \App\Models\Booking $booking */
$booking = Booking::query()->with('segments.stops','billingItems','invoiceItems.applyTo')->findOrFail($id);

$booking->id = null;
$booking->exists = false;
$booking->number = null;
$booking->confirmed_date_utc = null;
$booking->save();

$now = CarbonDate::now($booking->company->timezone);

foreach($booking->segments as $seg) {
    $seg->id = null;
    $seg->exists = false;
    $seg->booking_id = $booking->id;
    $seg->save();

    foreach($seg->stops as $stop) {
        $stop->id = null;
        $stop->exists = false;
        $stop->segment_id = $seg->id;
        $stop->save();
    }
}

foreach($booking->billingItems as $bi) {
    $bi->id = null;
    $bi->exists = false;
    $bi->booking_id = $booking->id;
    $bi->save();
}

$iiMap = [];

foreach($booking->invoiceItems as $ii) {
    $oldId = $ii->id;
    $ii->id = null;
    $ii->exists = false;
    $ii->booking_id = $booking->id;
    $ii->save();
    $iiMap[$oldId] = $ii->id;
}

foreach($booking->invoiceItems as $ii) {
    $newIds = [];
    foreach($ii->applyTo as $at) {
        $newIds[] = $iiMap[$at->id];
    }
    $ii->applyTo()->sync($newIds);
}

เคล็ดลับคือการเช็ดidและexistsคุณสมบัติเพื่อให้ Laravel สร้างสถิติใหม่

การโคลนนิ่งความสัมพันธ์ด้วยตนเองเป็นเรื่องยุ่งยากเล็กน้อย แต่ฉันได้รวมตัวอย่างไว้แล้ว คุณต้องสร้างการจับคู่รหัสเก่ากับรหัสใหม่จากนั้นซิงค์ใหม่

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.