2014-11-29 6 views
6

Wenn ich eine Viele-zu-Viele-Beziehung habe, ist es super einfach, die Beziehung mit ihrer sync Methode zu aktualisieren.Synchronisieren einer Eins-zu-Viele-Beziehung in Laravel

Aber was würde ich verwenden, um eine Eins-zu-viele-Beziehung zu synchronisieren?

  • Tabelle posts: id, name
  • Tabelle links: id, name, post_id

Hier kann jeder Post mehrere Link s haben.

Ich möchte die Links, die mit einem bestimmten Post in der Datenbank verknüpft sind, gegen eine eingegebene Sammlung von Links synchronisieren (z. B. von einem CRUD-Formular, wo ich Links hinzufügen, entfernen und ändern kann).

Links in der Datenbank, die nicht in meiner Eingabesammlung enthalten sind, sollten entfernt werden. Links, die in der Datenbank und in meiner Eingabe vorhanden sind, sollten aktualisiert werden, um die Eingabe widerzuspiegeln, und Links, die nur in meiner Eingabe vorhanden sind, sollten als neue Datensätze in der Datenbank hinzugefügt werden.

das gewünschte Verhalten zu fassen zusammen:

  • inputArray = true/db = false --- CREATE
  • inputArray = false/db = true --- DELETE
  • inputArray = true/db = true ---- UPDATE

Antwort

6

Leider gibt es keine sync Methode für Eins-zu-viele Beziehungen. Es ist ziemlich einfach, es selbst zu machen. Zumindest, wenn Sie keinen Fremdschlüssel haben, der auf links verweist. Denn dann können Sie die Zeilen einfach löschen und alle wieder einfügen.

Wenn Sie wirklich aktualisieren müssen (aus welchem ​​Grund auch immer), müssen Sie genau das tun, was Sie in Ihrer Frage beschrieben haben.

+0

ah ok danke, also versuche ich mit dir gehen Weg – user2834172

0

Das Problem beim Löschen und Lesen der zugehörigen Entitäten besteht darin, dass dadurch alle Fremdschlüsseleinschränkungen aufgehoben werden, die Sie möglicherweise für diese untergeordneten Entitäten haben.

Eine bessere Lösung ist Laravel des HasMany Beziehung gehören ein sync Verfahren zu ändern:

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

Sie können außer Kraft setzen Eloquent die Model Klasse zu verwenden HasManySyncable anstelle der Standard-HasMany Beziehung:

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

Gesetzt dass Ihr Post Modell erweitert MyBaseModel und hat eine links()hasMany r EZIEHUNG, können Sie so etwas wie tun:

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

alle Datensätze in dieser mehrdimensionalen Matrix, die ein id hat, das das Kind Objekt-Tabelle übereinstimmt (links) aktualisiert werden.Datensätze in der Tabelle, die in diesem Array nicht vorhanden sind, werden entfernt. Datensätze in dem Array, die nicht in der Tabelle vorhanden sind (eine nicht übereinstimmende id oder eine id von null) werden als "neue" Datensätze betrachtet und in die Datenbank eingefügt.

+0

kann es Laravel die Standard hat has-viele Beziehungen in weiteren Operationen? –

0

ich so tat, und es wird optimiert für minimale Abfrage und minimalen Updates:

zuerst, setzte Link-IDs in einem Array zu synchronisieren: $linkIds und das Post-Modell in einer eigenen Variable: $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]); 
} 
Verwandte Themen