Clone an Eloquent object including all relationships?

后端 未结 10 606
我寻月下人不归
我寻月下人不归 2020-12-01 01:24

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          


        
相关标签:
10条回答
  • 2020-12-01 01: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();
            }
        }
    
    0 讨论(0)
  • 2020-12-01 01:34

    When you fetch an object by any relation you want, and replicate after that, all relations you retrieved are also replicated. for example:

    $oldUser = User::with('roles')->find(1);
    $newUser = $oldUser->replicate();
    
    0 讨论(0)
  • 2020-12-01 01:41

    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();
    
    0 讨论(0)
  • 2020-12-01 01:48

    Here is a trait that will recursively duplicate all the loaded relationships on an object. You could easily expand this for other relationship types like Sabrina's example for belongsToMany.

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

    Usage:

    //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 讨论(0)
  • 2020-12-01 01:49

    This is in laravel 5.8, havent tried in older version

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

    edit, just today 7 April 2019 laravel 5.8.10 launched

    can use replicate now

    $post = Post::find(1);
    $newPost = $post->replicate();
    $newPost->save();
    
    0 讨论(0)
  • 2020-12-01 01:49

    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.

    0 讨论(0)
提交回复
热议问题