Est-il possible de cloner facilement un objet Eloquent, y compris toutes ses relations?
Par exemple, si j'avais ces tables:
users ( id, name, email )
roles ( id, name )
user_roles ( user_id, role_id )
En plus de créer une nouvelle ligne dans la table users
, toutes les colonnes étant identiques, à l'exception de id
, elle doit également créer une nouvelle ligne dans la table user_roles
, en attribuant le même rôle au nouvel utilisateur.
Quelque chose comme ça:
$user = User::find(1);
$new_user = $user->clone();
Où le modèle utilisateur a
class User extends Eloquent {
public function roles() {
return $this->hasMany('Role', 'user_roles');
}
}
testé dans laravel 4.2 pour les relations d'appartements personnels
si vous êtes dans le modèle:
//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);
}
Vous pouvez également essayer la fonction de réplication fournie par éloquent:
http://laravel.com/api/4.2/Illuminate/Database/Eloquent/Model.html#method_replicate
$user = User::find(1);
$new_user = $user->replicate();
$new_user->Push();
Vous pouvez essayer ceci ( Object Cloning ):
$user = User::find(1);
$new_user = clone $user;
Étant donné que clone
ne copie pas en profondeur, les objets enfants ne seront pas copiés si un objet enfant est disponible. Dans ce cas, vous devez copier l'objet enfant à l'aide de clone
manuellement. Par exemple:
$user = User::with('role')->find(1);
$new_user = clone $user; // copy the $user
$new_user->role = clone $user->role; // copy the $user->role
Dans votre cas, roles
sera une collection d'objets Role
. Chaque Role object
de la collection doit donc être copié manuellement à l'aide de clone
.
Aussi, vous devez être conscient de cela, si vous ne chargez pas roles
avec with
, ceux-ci ne seront pas chargés ou ne seront pas disponibles dans le $user
et lorsque vous appelerez $user->roles
, ces objets seront chargés à l'exécution heure après cet appel de $user->roles
et jusqu’à cette date, ces roles
ne sont pas chargés.
Cette réponse concernait Larave-4
et Laravel propose désormais la méthode replicate()
, par exemple:
$user = User::find(1);
$newUser = $user->replicate();
// ...
Pour Laravel 5. Testé avec de nombreuses relations.
$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());
}
}
Voici une version mise à jour de la solution de @ sabrina-gelbart qui clonera toutes les relations hasMany au lieu de la seule, comme elle l'a posté:
//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();
}
}
Si vous avez une collection nommée $ user, en utilisant le code ci-dessous, il crée une nouvelle collection identique à l'ancienne, incluant toutes les relations:
$new_user = new \Illuminate\Database\Eloquent\Collection ( $user->all() );
ce code est pour laravel 5.
Lorsque vous récupérez un objet selon la relation souhaitée et que vous le répliquez par la suite, toutes les relations que vous avez extraites sont également répliquées. par exemple:
$oldUser = User::with('roles')->find(1);
$newUser = $oldUser->replicate();
C'est dans laravel 5.8, je n'ai pas essayé dans une version plus ancienne
//# this will clone $eloquent and asign all $eloquent->$withoutProperties = null
$cloned = $eloquent->cloneWithout(Array $withoutProperties)
modifier, seulement aujourd'hui 7 avril 2019 Laravel 5.8.10 lancé
peut utiliser répliquer maintenant
$post = Post::find(1);
$newPost = $post->replicate();
$newPost->save();
Voici une autre façon de le faire si les autres solutions ne vous apaisent pas:
<?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);
}
L'astuce consiste à effacer les propriétés id
et exists
afin que Laravel crée un nouvel enregistrement.
Le clonage des relations de soi est un peu délicat, mais j'ai inclus un exemple. Il vous suffit de créer un mappage d'anciens identifiants sur de nouveaux identifiants, puis de le resynchroniser.