There is no standard way to do this so whatever suits you will be ok.
I'm still stuck on this. This is what I have so far. I get the car and then...
$car_copy = $car->replicate();
$car_copy->save();
if ($car->parts->count() > 0)
{
$parts_array[] = null;
foreach ($car->parts as $part) {
$part_copy = $part->replicate();
$part_copy->car_id = $car_copy->id;
array_push($parts_array, $part_copy);
}
$car_copy->parts()->saveMany($parts_array);
}
I get an error on the saveMany
"Argument 1 passed to Illuminate\Database\Eloquent\Relations\HasOneOrMany::save() must be an instance of Illuminate\Database\Eloquent\Model, null given"
$parts_array[] = null; // Pushes null into the first key of the array.
This is why you are getting the error specific to the first element of the $parts_array.
$parts_array = []; // Probably what you intended.
This should fix your problem!
Actually, there IS a way to do it with Laravel. There is a little-known and not-documented method that can do this for you.
View the method here: https://github.com/laravel/framework/blob/4.2/src/Illuminate/Database/Eloquent/Model.php#L2600-L2619
$user->load('all', 'of', 'the.relationships');
$newUser = $user->replicate();
$newUser->save();
Disclaimer: I haven't tried this before, but I came across it while browsing the source. Good luck!
Just read your current situation a little bit more. Looks like what you need to do is eager load the relationships into the model before calling replicate()
. As you can see on line 2618, it pulls $this->relations
, which is an empty array until loaded into the model.
adamgoose said:
Just read your current situation a little bit more. Looks like what you need to do is eager load the relationships into the model before calling
replicate()
. As you can see on line 2618, it pulls$this->relations
, which is an empty array until loaded into the model.
This would probably be the best solution to this if this is true! Nice find!
Unfortunately this won't create related objects just like that.
And it also won't create relations on the existing ones, despite it will copy them to $replicated->relations
.
After some tinkering I made that replicate recursive and it does basically what OP needs. However it's raw code that does sooooo many queries, that it requires some real improvements and refactoring.
I'll update this, when it's done.
jarektkaczyk said:
After some tinkering I made that replicate recursive and it does basically what OP needs. However it's raw code that does sooooo many queries, that it requires some real improvements and refactoring.
I'll update this, when it's done.
I have to disagree.... If you use $user->load('relation.one.two.three')
, it assigns $user->relations
to a nested array. Then, if you call replicate()
on the user, it will replicate all of the nested relations as well.
Adam, not really, no. It will just set the relations, but it won't really link them (associate, attach or whatever). No push method or anything alike can make it work as expected, so basically they are there only till the end of the request.
My code above worked just by fixing my $parts_array variable just like as jlaswell recommended.
All the properties are intact and the associations are correct.
And for informational purposes I was getting the car like this:
$car = Car:with('parts.piece')->where('id', $car_id)->first();
Now I'm going to put another foreach within the foreach for the pieces.
Thanks jlaswell, I can start moving forward again!
I use DebugBar to monitor the SQL queries and just discovered that saveMany() does a separate INSERT statement for each item in the array. I hate that.
I've done everything in my power to not have to do this but I'm just going to have to dynamically create a raw SQL statement.
The bad thing about this is that you have to list each column and if your columns change you have to remember to come back to this method and update the SQL. But, oh well. It is what it is.
You don't need to use raw query. Instead you can do something like below, for hasMany:
$parentKey = $parent->getKey();
$parentKeyName = $parent->relation()->getForeignKey();
$except = [
$this->getKeyName(),
$this->getCreatedAtColumn(),
$this->getUpdatedAtColumn()
];
$newModels = [];
foreach ($models as $model)
{
$attributes = $model->getAttributes();
$attributes[$parentKeyName] = $parentKey;
$newModels[] = array_except($attributes, $except);
}
$table = $parent->relation()->getRelated()->getTable();
DB::table($table)->insert($newModels);
This way you insert all the related objects with single query (per each parent), which can make it much better.
However there is one thing you need to consider. It means that you cannot save deeper level of the relation, because you never get inserted id back, so those deeper models 'don't know' what they should be associated to..
If I understand your question, I think that would be useful an implementation of the Prototype Pattern for your request.
See the following link, could be useful for your needs:
http://sourcemaking.com/design_patterns/prototype/php
Hope it helps you.
I'm still slowly working through this. Current error is:
SQLSTATE[42000]: Syntax error or access violation: 1110 Column 'car_id' specified twice
jarektkaczyk,
Thank you for the code. I got it to work by changing the $parentKeyName line otherwise it would leave car_id and add parts.car_id and then throw the "specified twice" error.
$parentKey = $car->getKey();
$parentKeyName = 'car_id'; //$car->parts()->getForeignKey();
$except = [
$car->getKeyName(),
$car->getCreatedAtColumn(),
$car->getUpdatedAtColumn()
];
$newModels = [];
foreach ($car->parts as $part) {
$attributes = $part->getAttributes();
$attributes[$parentKeyName] = $parentKey;
$newModels[] = array_except($attributes, $except);
}
$table = $car->parts()->getRelated()->getTable();
DB::table($table)->insert($newModels);
Next is to figure out how to copy the next relationship down.
I did it by this way
<pre> // question $question = Question::with('answers')->find($question_id)->first(); $question_replicate = $question->replicate(); $question_replicate->save(); //question answer (many) $question_answers = $question->answers->each(function($answer) use($question_replicate){ $answer->question_id = $question_replicate->id; $answer->save(); }); </pre>I did it this way FROM WITHIN THE MODEL
I only needed to do with belongsToMany (with hasMany I guess you'd actually clone the models..?)
//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);
}
@sgelbart: >sgelbart said:
I did it this way FROM WITHIN THE MODEL
I only needed to do with belongsToMany (with hasMany I guess you'd actually clone the models..?)
//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); }
I just tested it, and your right about the hasMany relations. All hasMany related models are cloned by calling the replicate() function.
Sign in to participate in this thread!
The Laravel portal for problem solving, knowledge sharing and community building.
The community