2014-11-29 15 views
6

Jeśli mam relację wiele do wielu, bardzo łatwo zaktualizować związek z jego metodą sync.Synchronizowanie relacji jeden-do-wielu w Laravel

Ale w jaki sposób mogę zsynchronizować relację jeden-do-wielu?

  • stół posts: id, name
  • stół links: id, name, post_id

Tutaj każdy Post może mieć wiele Link s.

Chciałbym zsynchronizować linki powiązane z konkretnym wpisem w bazie danych, z wprowadzonym zbiorem linków (na przykład z formularza CRUD, w którym mogę dodawać, usuwać i modyfikować linki).

Łącza w bazie danych, które nie są obecne w kolekcji wejściowej, powinny zostać usunięte. Łącza, które istnieją w bazie danych i moich danych wejściowych, powinny być aktualizowane w celu odzwierciedlenia danych wejściowych, a łącza, które są obecne tylko w moim pliku wejściowym, powinny zostać dodane jako nowe rekordy w bazie danych.

Podsumowując pożądane właściwości:

  • inputArray = prawda/fałsz db = --- TWORZENIE
  • inputArray = fałszywy/prawdziwy db = --- USUŃ
  • inputArray = PRAWDA/db = true ---- UPDATE

Odpowiedz

6

Niestety nie ma metody sync dla relacji jeden-do-wielu. Zwykle jest to proste. Przynajmniej jeśli nie masz żadnego klucza obcego, który odwołuje się do links. Ponieważ wtedy możesz po prostu usunąć wiersze i wstawić je wszystkie ponownie.

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

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

Jeśli naprawdę potrzebujesz zaktualizować istniejący (z jakiegokolwiek powodu), musisz zrobić dokładnie to, co opisałeś w swoim pytaniu.

+0

ah ok dzięki, więc próbuję iść z tobą, jestem w stanie – user2834172

0

Problem z usuwaniem i odczytywaniem powiązanych obiektów polega na tym, że będzie łamał wszelkie ograniczenia klucza obcego, które mogą występować w tych elementach podrzędnych.

Lepszym rozwiązaniem jest modyfikacja HasMany relacji laravel do obejmują sync metody:

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

Można zastąpić Model klasę wymowny do korzystania HasManySyncable zamiast standardowego HasMany relacji:

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

Przypuśćmy Twój model Post rozciąga się na MyBaseModel i ma links()hasMany r elationship, można zrobić coś takiego:

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

żadnych rekordów w tej wielowymiarowej tablicy, które wywierają id pasujący stół podmiotu dziecko (links) zostaną zaktualizowane.Rekordy w tabeli, które nie są obecne w tej tablicy, zostaną usunięte. Rekordy w tablicy, które nie są obecne w tabeli (mają niepasujący id lub id z wartości null) będą traktowane jako "nowe" rekordy i zostaną wstawione do bazy danych.

+0

czy może wpłynąć na domyślne powiązania laravel z wieloma innymi operacjami? –

0

Zrobiłem tak, i to jest zoptymalizowany do minimalnych zapytań i minimalnych aktualizacjach:

najpierw umieścić identyfikatory linku do synchronizacji w tablicy: $linkIds i modelu postu we własnej zmiennej: $post

Powiązane problemy