Клонувати красномовний об’єкт, включаючи всі стосунки?


86

Чи є спосіб легко клонувати красномовний об’єкт, включаючи всі його взаємозв’язки?

Наприклад, якби у мене були такі таблиці:

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

На додаток до створення нового рядка в usersтаблиці, причому всі стовпці однакові, за винятком id, він також повинен створити новий рядок у user_rolesтаблиці, призначивши ту саму роль новому користувачеві.

Щось на зразок цього:

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

Де є модель користувача

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

Відповіді:


74

протестовано в laravel 4.2 на відносини належності до багатьох

якщо ви в моделі:

    //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
Працював у Laravel 7
Daniyal Javani

Він також працює на попередній версії Laravel 6. (Гадаю, очікується на основі попереднього коментаря :)) Дякую!
mmmdearte

Працював у Laravel 7.28.4. Я помітив, що код повинен бути іншим, якщо ви намагаєтеся запустити його поза моделлю. Дякую
Роман Гриньов

56

Ви також можете спробувати функцію реплікації, надану красномовним:

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
Насправді вам також доведеться завантажувати відносини, які ви хочете відтворити. Наведений код буде тиражувати лише базову модель без її зв'язків. Щоб також клонувати відносини, ви можете або отримати користувача з його відносинами: $user = User::with('roles')->find(1);або завантажити їх після того, як у вас є Модель: так перші два рядки будуть$user = User::find(1); $user->load('roles');
Олександр Таубенкорб

2
Схоже, завантаження відносин також не повторює відносини, принаймні не в 4.1. Мені довелося тиражувати батька, а потім прокрутити дочірні елементи оригінальних реплікацій та оновлювати їх по черзі, щоб вказати на нового батька.
Рекс Шредер

replicate()встановить відносини і push()повториться у відносини та збереже їх.
Matt K

Також у 5.2 вам потрібно прокрутити дітей та зберегти їх після тиражування по одному; всередині $new_user->roles()->save($oldRole->replicate)
переділу

28

Ви можете спробувати це ( Клонування об’єкта ):

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

Оскільки cloneглибоке копіювання не виконується, тому дочірні об'єкти не будуть скопійовані, якщо доступний будь-який дочірній об'єкт, і в цьому випадку вам потрібно скопіювати дочірній об'єкт за допомогою cloneвручну. Наприклад:

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

У вашому випадку rolesбуде колекція Roleоб'єктів, тому кожен Role objectз колекції потрібно скопіювати вручну за допомогою clone.

Крім того, ви повинні пам’ятати, що якщо ви не завантажуєте те, що rolesвикористовує, withтоді вони не будуть завантажені або будуть недоступні в, $userі коли ви будете телефонувати, $user->rolesці об’єкти будуть завантажуватися під час виконання після цього дзвінка від $user->rolesі до цього, тим rolesНЕ незавантажені.

Оновлення:

Ця відповідь була для, Larave-4і тепер Laravel пропонує replicate()метод, наприклад:

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

2
Будьте обережні, лише неглибока копія, а не суб / дочірні об'єкти :-)
Альфа

1
@TheShiftExchange, Вам може бути цікаво , я давно зробив експеримент. Дякую за великі пальці :-)
Альфа

1
Чи це також не копіює ідентифікатор об'єкта? Зробити його марним для економії?
Тош

@Tosh, так, саме тому вам потрібно встановити інший ідентифікатор або null:-)
The Alpha

1
плюс1 за розкриття php-секрету: P
Метаболічний

21

Для Laravel 5. Перевірено за допомогою відношення 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());
    }
}

7

Ось оновлена ​​версія рішення від @ sabrina-gelbart, яка клонуватиме всі відносини hasMany, а не лише належністьToMany, як вона писала:

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

Хитро, якщо some_parent_idне однаково для всіх стосунків. Однак це корисно, дякую.
Дастін Грем

5

Це у laravel 5.8, не пробували у попередній версії

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

редагувати, тільки сьогодні, 7 квітня 2019 р. запущено laravel 5.8.10

можна використовувати реплікацію зараз

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

2

Якщо у вас є колекція з іменем $ user, використовуючи наведений нижче код, вона створює нову колекцію, ідентичну старої, включаючи всі відносини:

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

цей код призначений для laravel 5.


1
Не могли б ви просто так зробити $new = $old->slice(0)?
fubar

2

Коли ви отримуєте об’єкт за будь-яким потрібним вам відношенням і реплікуєте після цього, усі відновлення, які ви отримали, також копіюються. наприклад:

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

Я тестував у Laravel 5.5
elyas.m,

2

Ось риса, яка рекурсивно продублює всі завантажені зв’язки на об’єкті. Ви можете легко розширити це для інших типів стосунків, як приклад Сабріни для belongToMany.

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

Використання:

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

Ось ще один спосіб зробити це, якщо інші рішення вас не задобрюють:

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

Фокус полягає в тому, щоб стерти idі existsвластивості, щоб Laravel створив новий запис.

Клонування власних стосунків трохи складно, але я навів приклад. Вам просто потрібно створити зіставлення старих ідентифікаторів з новими, а потім повторно синхронізувати.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.