Clone an Eloquent object including all relationshi

2019-01-14 00:14发布

Is there any way to easily clone an Eloquent object, including all of its relationships?

For example, if I had these tables:

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

In addition to creating a new row in the users table, with all columns being the same except id, it should also create a new row in the user_roles table, assigning the same role to the new user.

Something like this:

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

Where the User model has

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

7条回答
倾城 Initia
2楼-- · 2019-01-14 00:29

Here is an updated version of the solution from @sabrina-gelbart that will clone all hasMany relationships instead of just the belongsToMany as she posted:

    //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();
        }
    }
查看更多
叛逆
3楼-- · 2019-01-14 00:30

tested in laravel 4.2 for belongsToMany relationships

if you're in the model:

    //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);
    }
查看更多
淡お忘
4楼-- · 2019-01-14 00:32

You may try this (Object Cloning):

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

Since clone doesn't deep copy so child objects won't be copied if there is any child object available and in this case you need to copy the child object using clone manually. For example:

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

In your case roles will be a collection of Role objects so each Role object in the collection needs to be copied manually using clone.

Also, you need to be aware of that, if you don't load the roles using with then those will be not loaded or won't be available in the $user and when you'll call $user->roles then those objects will be loaded at run time after that call of $user->roles and until this, those roles are not loaded.

Update:

This answer was for Larave-4 and now Laravel offers replicate() method, for example:

$user = User::find(1);
$newUser = $user->replicate();
// ...
查看更多
smile是对你的礼貌
5楼-- · 2019-01-14 00:33

If you have a collection named $user, using the code bellow, it creates a new Collection identical from the old one, including all the relations:

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

this code is for laravel 5.

查看更多
走好不送
6楼-- · 2019-01-14 00:36

Here's another way to do it if the other solutions don't appease you:

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

The trick is to wipe the id and exists properties so that Laravel will create a new record.

Cloning self-relationships is a little tricky but I've included an example. You just have to create a mapping of old ids to new ids and then re-sync.

查看更多
淡お忘
7楼-- · 2019-01-14 00:49

You may also try the replicate function provided by eloquent:

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

$user = User::find(1);
$new_user = $user->replicate();
$new_user->push();
查看更多
登录 后发表回答