Sao chép một đối tượng Eloquent bao gồm tất cả các mối quan hệ?


86

Có cách nào để dễ dàng sao chép một đối tượng Eloquent, bao gồm tất cả các mối quan hệ của nó không?

Ví dụ: nếu tôi có các bảng này:

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

Ngoài việc tạo một hàng mới trong usersbảng, với tất cả các cột đều giống nhau id, ngoại trừ , nó cũng nên tạo một hàng mới trong user_rolesbảng, gán vai trò tương tự cho người dùng mới.

Một cái gì đó như thế này:

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

Trường hợp mô hình Người dùng có

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

Câu trả lời:


74

đã được kiểm tra trong laravel 4.2 cho các mối quan hệ thuộc về ToMany

nếu bạn đang ở trong mô hình:

    //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
Làm việc ở Laravel 7
Daniyal Javani

Nó cũng hoạt động trên phiên bản trước Laravel 6. (Tôi đoán dự kiến ​​dựa trên nhận xét trước đó :)) Cảm ơn!
mmmdearte

Làm việc trong Laravel 7.28.4. Tôi đã nhận thấy rằng mã sẽ khác nếu bạn đang cố gắng chạy nó bên ngoài mô hình. Cảm ơn
Roman Grinev

56

Bạn cũng có thể thử chức năng nhân bản được cung cấp bởi 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
Trên thực tế, bạn cũng phải tải các mối quan hệ mà bạn muốn tái tạo. Mã đã cho sẽ chỉ sao chép mô hình cơ sở mà không có quan hệ của nó. Để sao chép các mối quan hệ là tốt, bạn có thể nhận được các người dùng với các mối quan hệ của mình: $user = User::with('roles')->find(1);hoặc tải chúng sau khi bạn có Model: vì vậy hai dòng đầu tiên sẽ là$user = User::find(1); $user->load('roles');
Alexander Taubenkorb

2
Việc tải các mối quan hệ dường như cũng không tái tạo các mối quan hệ, ít nhất là không có trong 4.1. Tôi phải sao chép cha mẹ, sau đó lặp qua các con của bản sao ban đầu và cập nhật chúng lần lượt để trỏ đến cha mẹ mới.
Rex Schrader

replicate()sẽ thiết lập các quan hệ và push()đệ quy vào các quan hệ và lưu chúng.
Matt K

Cũng trong 5.2, bạn cần lặp qua các phần tử con và lưu chúng sau khi sao chép từng phần một; $new_user->roles()->save($oldRole->replicate)
Inside

28

Bạn có thể thử điều này ( Nhân bản đối tượng ):

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

clonekhông sao chép sâu nên các đối tượng con sẽ không được sao chép nếu có sẵn bất kỳ đối tượng con nào và trong trường hợp này, bạn cần sao chép đối tượng con bằng cách sử dụng clonethủ công. Ví dụ:

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

Trong trường hợp của bạn rolessẽ là một tập hợp các Roleđối tượng vì vậy mỗi đối tượng Role objecttrong tập hợp cần được sao chép thủ công bằng cách sử dụng clone.

Ngoài ra, bạn cần lưu ý rằng, nếu bạn không tải rolesbằng cách sử dụng withthì các đối tượng đó sẽ không được tải hoặc sẽ không có sẵn trong $uservà khi bạn gọi $user->rolesthì các đối tượng đó sẽ được tải vào thời gian chạy sau cuộc gọi đó của $user->rolesvà cho đến nay, những người roleskhông được nạp.

Cập nhật:

Câu trả lời này là cho Larave-4và bây giờ Laravel cung cấp replicate()phương pháp, ví dụ:

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

2
Hãy cẩn thận, chỉ là một bản sao nông, không phải các đối tượng phụ / con :-)
The Alpha

1
@TheShiftExchange, Bạn có thể thấy thú vị , tôi đã làm một thử nghiệm cách đây rất lâu. Cảm ơn vì đã ủng hộ :-)
The Alpha

1
Điều này cũng không sao chép id của đối tượng? Làm cho nó vô ích để tiết kiệm?
Tosh

@Tosh, Vâng, chính xác và đó là lý do tại sao bạn cần đặt một id khác hoặc null:-)
The Alpha

1
plus1 để tiết lộ bí mật php: P
Metabolic

21

Đối với Laravel 5. Đã kiểm tra với quan hệ 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

Dưới đây là phiên bản cập nhật của giải pháp từ @ sabrina-gelbart sẽ sao chép tất cả các mối quan hệ của hasMany thay vì chỉ có Thuộc tính như cô ấy đã đăng:

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

Khéo léo nếu some_parent_idkhông giống nhau cho tất cả các mối quan hệ. Điều này rất hữu ích, cảm ơn.
Dustin Graham

5

Đây là trong laravel 5.8, không thử ở phiên bản cũ hơn

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

chỉnh sửa, mới hôm nay ngày 7 tháng 4 năm 2019 laravel 5.8.10 ra mắt

có thể sử dụng bản sao ngay bây giờ

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

2

Nếu bạn có một bộ sưu tập có tên $ user, bằng cách sử dụng mã bên dưới, nó sẽ tạo một Bộ sưu tập mới giống hệt bộ sưu tập cũ, bao gồm tất cả các mối quan hệ:

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

mã này dành cho laravel 5.


1
Bạn có thể không chỉ làm $new = $old->slice(0)?
fubar

2

Khi bạn tìm nạp một đối tượng theo bất kỳ quan hệ nào bạn muốn và sao chép sau đó, tất cả các quan hệ mà bạn truy xuất cũng được sao chép. ví dụ:

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

Tôi đã thử nghiệm trong Laravel 5.5
elyas.m

2

Đây là một đặc điểm sẽ sao chép một cách đệ quy tất cả những gì đã tải mối quan hệ được trên một đối tượng. Bạn có thể dễ dàng mở rộng điều này cho các loại mối quan hệ khác như ví dụ của Sabrina cho Thuộc vềToMany.

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

Sử dụng:

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

Đây là một cách khác để làm điều đó nếu các giải pháp khác không làm bạn hài lòng:

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

Bí quyết là xóa idexists thuộc tính để Laravel sẽ tạo một bản ghi mới.

Nhân bản các mối quan hệ bản thân hơi khó nhưng tôi đã đưa vào một ví dụ. Bạn chỉ cần tạo ánh xạ các id cũ sang id mới và sau đó đồng bộ hóa lại.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.