2014-11-29 24 views
6

Nếu tôi có mối quan hệ nhiều-nhiều thì siêu dễ dàng cập nhật mối quan hệ với phương pháp sync của nó.Đồng bộ hóa mối quan hệ một-nhiều trong Laravel

Nhưng tôi sẽ sử dụng điều gì để đồng bộ hóa mối quan hệ một-nhiều?

  • bảng posts: id, name
  • bảng links: id, name, post_id

Ở đây, mỗi Post có thể có nhiều Link s.

Tôi muốn đồng bộ hóa các liên kết được liên kết với một bài đăng cụ thể trong cơ sở dữ liệu, chống lại tập hợp các liên kết đã nhập (ví dụ: từ biểu mẫu CRUD nơi tôi có thể thêm, xóa và sửa đổi liên kết).

Các liên kết trong cơ sở dữ liệu không có trong bộ sưu tập đầu vào của tôi sẽ bị xóa. Các liên kết tồn tại trong cơ sở dữ liệu và trong đầu vào của tôi phải được cập nhật để phản ánh đầu vào và các liên kết chỉ xuất hiện trong đầu vào của tôi sẽ được thêm dưới dạng bản ghi mới trong cơ sở dữ liệu.

Để tóm tắt hành vi mong muốn:

  • inputArray = true/db = false --- CREATE
  • inputArray = false/db = true --- DELETE
  • inputArray = true/db = đúng ---- UPDATE

Trả lời

6

Thật không may là không có phương pháp sync cho quan hệ một-nhiều. Nó khá đơn giản để làm điều đó một mình. Ít nhất nếu bạn không có bất kỳ khóa ngoại nào, hãy tham khảo links. Bởi vì sau đó bạn có thể đơn giản xóa các hàng và chèn chúng lại.

$links = array(
    new Link(), 
    new Link() 
); 

$post->links()->delete(); 
$post->links()->saveMany($links); 

Nếu bạn thực sự cần cập nhật cái hiện có (vì lý do gì) bạn cần làm chính xác những gì bạn đã mô tả trong câu hỏi.

+0

ah ok cảm ơn, vì vậy tôi cố gắng đi với bạn cách – user2834172

0

Vấn đề với việc xóa và readding các thực thể liên quan, là nó sẽ phá vỡ bất kỳ ràng buộc khóa ngoại bạn có thể có trên các thực thể con đó.

Một giải pháp tốt hơn là để thay đổi mối quan hệ HasMany Laravel để bao gồm một phương pháp sync:

<?php 

namespace App\Model\Relations; 

use Illuminate\Database\Eloquent\Relations\HasMany; 

/** 
* @link https://github.com/laravel/framework/blob/5.4/src/Illuminate/Database/Eloquent/Relations/HasMany.php 
*/ 
class HasManySyncable extends HasMany 
{ 
    public function sync($data, $deleting = true) 
    { 
     $changes = [ 
      'created' => [], 'deleted' => [], 'updated' => [], 
     ]; 

     $relatedKeyName = $this->related->getKeyName(); 

     // First we need to attach any of the associated models that are not currently 
     // in the child entity table. We'll spin through the given IDs, checking to see 
     // if they exist in the array of current ones, and if not we will insert. 
     $current = $this->newQuery()->pluck(
      $relatedKeyName 
     )->all(); 

     // Separate the submitted data into "update" and "new" 
     $updateRows = []; 
     $newRows = []; 
     foreach ($data as $row) { 
      // We determine "updateable" rows as those whose $relatedKeyName (usually 'id') is set, not empty, and 
      // match a related row in the database. 
      if (isset($row[$relatedKeyName]) && !empty($row[$relatedKeyName]) && in_array($row[$relatedKeyName], $current)) { 
       $id = $row[$relatedKeyName]; 
       $updateRows[$id] = $row; 
      } else { 
       $newRows[] = $row; 
      } 
     } 

     // Next, we'll determine the rows in the database that aren't in the "update" list. 
     // These rows will be scheduled for deletion. Again, we determine based on the relatedKeyName (typically 'id'). 
     $updateIds = array_keys($updateRows); 
     $deleteIds = []; 
     foreach ($current as $currentId) { 
      if (!in_array($currentId, $updateIds)) { 
       $deleteIds[] = $currentId; 
      } 
     } 

     // Delete any non-matching rows 
     if ($deleting && count($deleteIds) > 0) { 
      $this->getRelated()->destroy($deleteIds); 

      $changes['deleted'] = $this->castKeys($deleteIds); 
     } 

     // Update the updatable rows 
     foreach ($updateRows as $id => $row) { 
      $this->getRelated()->where($relatedKeyName, $id) 
       ->update($row); 
     } 

     $changes['updated'] = $this->castKeys($updateIds); 

     // Insert the new rows 
     $newIds = []; 
     foreach ($newRows as $row) { 
      $newModel = $this->create($row); 
      $newIds[] = $newModel->$relatedKeyName; 
     } 

     $changes['created'][] = $this->castKeys($newIds); 

     return $changes; 
    } 


    /** 
    * Cast the given keys to integers if they are numeric and string otherwise. 
    * 
    * @param array $keys 
    * @return array 
    */ 
    protected function castKeys(array $keys) 
    { 
     return (array) array_map(function ($v) { 
      return $this->castKey($v); 
     }, $keys); 
    } 

    /** 
    * Cast the given key to an integer if it is numeric. 
    * 
    * @param mixed $key 
    * @return mixed 
    */ 
    protected function castKey($key) 
    { 
     return is_numeric($key) ? (int) $key : (string) $key; 
    } 
} 

Bạn có thể ghi đè lên lớp Model hùng biện để sử dụng HasManySyncable thay vì tiêu chuẩn HasMany mối quan hệ:

<?php 

namespace App\Model; 

use App\Model\Relations\HasManySyncable; 
use Illuminate\Database\Eloquent\Model; 

abstract class MyBaseModel extends Model 
{ 
    /** 
    * Overrides the default Eloquent hasMany relationship to return a HasManySyncable. 
    * 
    * {@inheritDoc} 
    * @return \App\Model\Relations\HasManySyncable 
    */ 
    public function hasMany($related, $foreignKey = null, $localKey = null) 
    { 
     $instance = $this->newRelatedInstance($related); 

     $foreignKey = $foreignKey ?: $this->getForeignKey(); 

     $localKey = $localKey ?: $this->getKeyName(); 

     return new HasManySyncable(
      $instance->newQuery(), $this, $instance->getTable().'.'.$foreignKey, $localKey 
     ); 
    } 

Giả rằng mô hình Post của bạn mở rộng MyBaseModel và có links()hasMany r elationship, bạn có thể làm điều gì đó như:

$post->links()->sync([ 
    [ 
     'id' => 21, 
     'name' => "LinkedIn profile" 
    ], 
    [ 
     'id' => null, 
     'label' => "Personal website" 
    ] 
]); 

hồ sơ Bất kỳ trong mảng đa chiều này có một id phù hợp với bảng thực thể con (links) sẽ được cập nhật.Các bản ghi trong bảng không có trong mảng này sẽ bị xóa. Các bản ghi trong mảng không có trong bảng (Có một số không phù hợp id hoặc id của null) sẽ được coi là bản ghi "mới" và sẽ được chèn vào cơ sở dữ liệu.

+0

nó có thể ảnh hưởng đến mặc định của laravel có nhiều mối quan hệ trong hoạt động tiếp theo? –

0

tôi đã làm như thế này, và nó được tối ưu hóa cho các cập nhật truy vấn và tối thiểu tối thiểu:

đầu tiên, đặt id liên kết để đồng bộ hóa trong một mảng: $linkIds và mô hình bài trong biến riêng của mình: $post

Link::where('post_id','=',$post->id)->whereNotIn('id',$linkIds)//only remove unmatching 
    ->update(['post_id'=>null]); 
if($linkIds){//If links are empty the second query is useless 
    Link::whereRaw('(post_id is null OR post_id<>'.$post->id.')')//Don't update already matching, I am using Raw to avoid a nested or, you can use nested OR 
     ->whereIn('id',$linkIds)->update(['post_id'=>$post->id]); 
} 
Các vấn đề liên quan