Clonare un oggetto eloquente comprese tutte le relazioni?


87

Esiste un modo per clonare facilmente un oggetto eloquente, comprese tutte le sue relazioni?

Ad esempio, se avessi queste tabelle:

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

Oltre a creare una nuova riga nella userstabella, con tutte le colonne uguali tranne id, dovrebbe anche creare una nuova riga nella user_rolestabella, assegnando lo stesso ruolo al nuovo utente.

Qualcosa come questo:

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

Dove ha il modello User

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

Risposte:


77

testato in laravel 4.2 per le relazioni appartiene a molti

se sei nel modello:

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

3
Ha lavorato a Laravel 7
Daniyal Javani il

Funziona anche sulla versione precedente di Laravel 6. (Immagino sia previsto in base al commento precedente :)) Grazie!
mmmdearte

Ha lavorato in Laravel 7.28.4. Ho notato che il codice dovrebbe essere diverso se stai cercando di eseguirlo al di fuori del modello. Grazie
Roman Grinev

56

Puoi anche provare la funzione di replica fornita da 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();

7
In realtà devi caricare anche le relazioni che vuoi replicare. Il codice fornito replicherà solo il modello di base senza le sue relazioni. Per clonare anche le relazioni, puoi ottenere l'utente con le sue relazioni: $user = User::with('roles')->find(1);oppure caricarle dopo aver ottenuto il Modello: quindi le prime due righe sarebbero$user = User::find(1); $user->load('roles');
Alexander Taubenkorb

2
Il caricamento delle relazioni non sembra replicare anche le relazioni, almeno non in 4.1. Ho dovuto replicare il genitore, quindi scorrere i figli dell'originale replicandoli e aggiornandoli uno alla volta per indicare il nuovo genitore.
Rex Schrader

replicate()imposterà le relazioni e le push()ricorsioni nelle relazioni e le salva.
Matt K

Anche in 5.2 è necessario scorrere i figli e salvarli dopo averli replicati uno alla volta; all'interno di un foreach:$new_user->roles()->save($oldRole->replicate)
d.grassi84

28

Puoi provare questo ( clonazione di oggetti ):

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

Poiché clonenon esegue la copia profonda, gli oggetti figlio non verranno copiati se è disponibile un oggetto figlio e in questo caso è necessario copiare l'oggetto figlio clonemanualmente. Per esempio:

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

Nel tuo caso rolessarà una raccolta di Roleoggetti, quindi ciascuno Role objectnella raccolta deve essere copiato manualmente utilizzando clone.

Inoltre, devi essere consapevole di questo, se non carichi il rolesusing withallora quelli non saranno caricati o non saranno disponibili in $usere quando chiamerai $user->rolesquegli oggetti verranno caricati in fase di esecuzione dopo quella chiamata di $user->rolese fino a questo momento, quelli rolesnon vengono caricati.

Aggiornare:

Questa risposta era per Larave-4e ora Laravel offre un replicate()metodo, ad esempio:

$user = User::find(1);
$newUser = $user->replicate();
// ...

2
Attenzione, solo una copia superficiale, non gli oggetti sub / figli :-)
L'Alpha

1
@TheShiftExchange, potresti trovarlo interessante , ho fatto un esperimento molto tempo fa. Grazie per il pollice in alto :-)
L'Alpha

1
Questo non copia anche l'id dell'oggetto? Rendendolo inutile per il risparmio?
Tosh

@Tosh, Sì, esattamente ed è per questo che devi impostare un altro ID o null:-)
L'Alpha

1
plus1 per la rivelazione del segreto php: P
Metabolic

23

Per Laravel 5. Testato con relazione hasMany.

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

funziona perfettamente laravel 5.6 Grazie mille
Ali Abbas

7

Ecco una versione aggiornata della soluzione di @ sabrina-gelbart che clonerà tutte le relazioni hasMany invece che solo appartiene a molti come ha pubblicato:

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

Difficile se some_parent_idnon è lo stesso per tutte le relazioni. Questo è utile però, grazie.
Dustin Graham

6

Questo è in laravel 5.8, non è stato provato nella versione precedente

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

modifica, solo oggi 7 aprile 2019 è stato lanciato laravel 5.8.10

può usare la replica ora

$post = Post::find(1);
$newPost = $post->replicate();
$newPost->save();

2

Se hai una collezione chiamata $ user, usando il codice qui sotto, crea una nuova collezione identica a quella vecchia, includendo tutte le relazioni:

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

questo codice è per laravel 5.


1
Non potresti semplicemente fare $new = $old->slice(0)?
fubar

2

Quando si recupera un oggetto in base a qualsiasi relazione desiderata e si replica successivamente, vengono replicate anche tutte le relazioni recuperate. per esempio:

$oldUser = User::with('roles')->find(1);
$newUser = $oldUser->replicate();

Ho testato in Laravel 5.5
elyas.m

2

Ecco un tratto che duplicherà ricorsivamente tutte le relazioni caricate su un oggetto. Potresti facilmente espandere questo per altri tipi di relazione come l'esempio di Sabrina per appartiene a molti.

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

Utilizzo:

//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

Ecco un altro modo per farlo se le altre soluzioni non ti soddisfano:

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

Il trucco è cancellare le proprietà ide existsin modo che Laravel crei un nuovo record.

La clonazione delle relazioni personali è un po 'complicata, ma ho incluso un esempio. Devi solo creare una mappatura dei vecchi ID ai nuovi ID e quindi risincronizzarli.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.