Reputation: 25509
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');
}
}
Upvotes: 124
Views: 166647
Reputation: 3383
I added this function in BaseModel
to duplicate data with relations. It works in Laravel 9.
public function replicateWithRelationsAttributes(): static
{
$model = clone $this->replicate();
foreach ($this->getRelations() as $key => $relation) {
$model->setRelation($key, clone $relation);
}
return $model;
}
Upvotes: 2
Reputation: 804
For Laravel 11
Based on @Sabrina Leggett answer
use Illuminate\Database\Eloquent\Collection;
$cloned_model = $model->replicate();
$cloned_model ->push();
$model->load([ ... relations ... ]);
foreach ($model->getRelations() as $relationName => $values) {
$data = $values->toArray();
if ($values instanceof Collection) {
$cloned_model ->{$relationName}()->createMany($data);
} else {
$cloned_model ->{$relationName}()->create($data);
}
}
I tested it on HasOne and HasMany relations
Upvotes: 1
Reputation: 1931
In Laravel v5.8.10+ (Currently Laravel v9.x) If you need to work with Laravel replicate()
model with relationships this could be a solution. Let's see two simple example.
app/Models/Product.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
use HasFactory;
/**
* The attributes that are mass assignable.
*
* @var array<string>
*/
protected $fillable = [
'name', 'price', 'slug', 'category_id'
];
}
app/Models/Category.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Category extends Model
{
use HasFactory;
/**
* Get all the products for the Category.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function products()
{
return $this->hasMany(Product::class);
}
/**
* Clone the model into a new, non-existing instance with all the products.
*
* @return \App\Models\Category
*/
public function replicateRow()
{
$clon = $this->replicate();
$clon->push();
$this->products->each(
fn ($product) => $clon->products()->create($product->toArray())
);
return $clon;
}
}
Controller Code
<?php
namespace App\Http\Controllers;
use App\Models\Category;
class ReplicateController extends Controller
{
/**
* Handle the incoming request.
*
* @param \App\Models\Category $category
* @return void
*/
public function index(Category $category)
{
$newCategory = $category->replicateRow();
dd($newCategory);
}
}
Upvotes: 0
Reputation: 480
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);
Upvotes: 3
Reputation: 1540
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();
Upvotes: 5
Reputation: 936
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();
Upvotes: 7
Reputation: 282845
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.
Upvotes: 0
Reputation: 146191
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.
This answer was for Larave-4
and now Laravel offers replicate()
method, for example:
$user = User::find(1);
$newUser = $user->replicate();
// ...
Upvotes: 31
Reputation: 3417
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.
Upvotes: 2
Reputation: 500
For Laravel 5. Tested with hasMany relation.
$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());
}
}
Upvotes: 37
Reputation: 12018
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();
}
}
Upvotes: 8
Reputation: 9495
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);
}
Upvotes: 123
Reputation: 864
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();
Upvotes: 57