Ein eloquentes Objekt einschließlich aller Beziehungen klonen?

87

Gibt es eine Möglichkeit, ein eloquentes Objekt einschließlich aller seiner Beziehungen einfach zu klonen?

Wenn ich zum Beispiel diese Tabellen hätte:

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

Zusätzlich zum Erstellen einer neuen Zeile in der usersTabelle, mit Ausnahme aller Spalten id, sollte auch eine neue Zeile in der user_rolesTabelle erstellt werden, die dem neuen Benutzer dieselbe Rolle zuweist.

Etwas wie das:

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

Wo das Benutzermodell hat

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

Antworten:

76

In Laravel 4.2 auf Zugehörigkeiten zu vielen Beziehungen getestet

Wenn Sie im Modell sind:

    //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);
    }
Sabrina Leggett
quelle
3
Arbeitete in Laravel 7
Daniyal Javani
Es funktioniert auch mit der vorherigen Version Laravel 6. (Ich denke, es wird aufgrund des vorherigen Kommentars erwartet :)) Danke!
mmmdearte
Arbeitete in Laravel 7.28.4. Ich habe festgestellt, dass Code anders sein sollte, wenn Sie versuchen, ihn außerhalb des Modells auszuführen. Danke
Roman Grinev
56

Sie können auch die Replikationsfunktion von eloquent ausprobieren:

http://laravel.com/api/4.2/Illuminate/Database/Eloquent/Model.html#method_replicate

$user = User::find(1);
$new_user = $user->replicate();
$new_user->push();
Piotr Borek
quelle
7
Eigentlich müssen Sie auch die Beziehungen laden, die Sie replizieren möchten. Der angegebene Code repliziert nur das Basismodell ohne seine Beziehungen. Um auch die Beziehungen zu klonen, können Sie entweder den Benutzer mit seinen Beziehungen abrufen: $user = User::with('roles')->find(1);oder sie laden, nachdem Sie das Modell haben: Die ersten beiden Zeilen wären also$user = User::find(1); $user->load('roles');
Alexander Taubenkorb
2
Das Laden der Beziehungen scheint die Beziehungen nicht zu replizieren, zumindest nicht in 4.1. Ich musste das übergeordnete Element replizieren, dann die untergeordneten Elemente des Originals replizieren und sie einzeln aktualisieren, um auf das neue übergeordnete Element zu verweisen.
Rex Schrader
replicate()setzt die Relationen und push()rekursiv in die Relationen und speichert sie.
Matt K
Außerdem müssen Sie in 5.2 die untergeordneten Elemente durchlaufen und sie speichern, nachdem Sie sie einzeln repliziert haben. in einem foreach:$new_user->roles()->save($oldRole->replicate)
d.grassi84
28

Sie können dies versuchen ( Objektklonen ):

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

Da clonenicht tief kopiert wird, werden untergeordnete Objekte nicht kopiert, wenn ein untergeordnetes Objekt verfügbar ist. In diesem Fall müssen Sie das untergeordnete Objekt clonemanuell kopieren . Zum Beispiel:

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

In Ihrem Fall handelt roleses sich um eine Sammlung von RoleObjekten, sodass jedes Objekt Role objectin der Sammlung manuell mit kopiert werden muss clone.

Außerdem müssen Sie sich dessen bewusst sein. Wenn Sie die rolesVerwendung nicht laden , withwerden diese nicht geladen oder sind im nicht verfügbar. $userWenn Sie aufrufen, werden $user->rolesdiese Objekte zur Laufzeit nach diesem Aufruf geladen von $user->rolesund bis dahin werden diese rolesnicht geladen.

Aktualisieren:

Diese Antwort war für Larave-4und jetzt bietet Laravel replicate()Methode an, zum Beispiel:

$user = User::find(1);
$newUser = $user->replicate();
// ...
Das Alpha
quelle
2
Seien Sie vorsichtig, nur eine flache Kopie, nicht die Sub / Child-Objekte :-)
The Alpha
1
@TheShiftExchange, Sie finden es vielleicht interessant , ich habe vor langer Zeit ein Experiment durchgeführt. Danke für die Daumen hoch :-)
The Alpha
1
Kopiert dies nicht auch die ID des Objekts? Zum Speichern unbrauchbar machen?
Tosh
@Tosh, ja, genau und deshalb musst du eine andere ID setzen oder null:-)
The Alpha
1
plus1 für die Enthüllung des PHP-Geheimnisses: P
Metabolic
22

Für Laravel 5. Getestet mit hasMany Beziehung.

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

Hier ist eine aktualisierte Version der Lösung von @ sabrina-gelbart, die alle hasMany-Beziehungen klonen wird, anstatt nur die gehörenden ToMany, wie sie gepostet hat:

    //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();
        }
    }
Davidethell
quelle
Tricky, wenn some_parent_idnicht für alle Beziehungen gleich ist. Dies ist jedoch nützlich, danke.
Dustin Graham
5

Dies ist in Laravel 5.8, in älteren Versionen nicht ausprobiert

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

bearbeiten, erst heute 7. April 2019 Laravel 5.8.10 gestartet

kann jetzt replizieren verwenden

$post = Post::find(1);
$newPost = $post->replicate();
$newPost->save();
David Valentino
quelle
2

Wenn Sie eine Sammlung mit dem Namen $ user haben, die den folgenden Code verwendet, wird eine neue Sammlung erstellt, die mit der alten identisch ist, einschließlich aller Beziehungen:

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

Dieser Code ist für Laravel 5.

Mihai Crăiță
quelle
1
Könnten Sie nicht einfach tun $new = $old->slice(0)?
Fubar
2

Wenn Sie ein Objekt mit einer beliebigen Beziehung abrufen und anschließend replizieren, werden auch alle abgerufenen Beziehungen repliziert. zum Beispiel:

$oldUser = User::with('roles')->find(1);
$newUser = $oldUser->replicate();
elyas.m
quelle
Ich habe in Laravel 5.5
elyas.m
2

Hier ist ein Merkmal, das alle geladenen Beziehungen auf einem Objekt rekursiv dupliziert . Sie können dies leicht für andere Beziehungstypen wie Sabrinas Beispiel für Gehört zu Viele erweitern.

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

Verwendung:

//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);
Sean Berce
quelle
0

Hier ist eine andere Möglichkeit, dies zu tun, wenn die anderen Lösungen Sie nicht beruhigen:

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

Der Trick besteht darin, die Eigenschaften idund zu existslöschen, damit Laravel einen neuen Datensatz erstellt.

Das Klonen von Selbstbeziehungen ist etwas schwierig, aber ich habe ein Beispiel beigefügt. Sie müssen nur eine Zuordnung alter IDs zu neuen IDs erstellen und dann erneut synchronisieren.

mpen
quelle