2016-02-17 17 views
10

In Laravel, il seeding del database viene generalmente eseguito tramite le fabbriche del modello. Così si definisce un progetto per il vostro modello utilizzando dati Faker, e dire quante istanze è necessario:Laravel - Relazioni di semina

$factory->define(App\User::class, function (Faker\Generator $faker) { 
    return [ 
     'name' => $faker->name, 
     'email' => $faker->email, 
     'password' => bcrypt(str_random(10)), 
     'remember_token' => str_random(10), 
    ]; 
}); 

$user = factory(App\User::class, 50)->create(); 

Tuttavia, diciamo il vostro modello User ha un rapporto hasMany con molti altri modelli, come un modello Post ad esempio:

Post: 
    id 
    name 
    body 
    user_id 

Quindi, in questa situazione, si vuole seminare il vostro tavolo messaggi con effettivi gli utenti che sono stati seminati nella tabella utenti. Questo non sembra essere esplicitamente discusso, ma ho trovato il seguente nella documentazione laravel:

$users = factory(App\User::class, 3) 
    ->create() 
    ->each(function($u) { 
     $u->posts()->save(factory(App\Post::class)->make()); 
    }); 

Quindi nel tuo fabbrica d'uso, si crea un numero X di messaggi per ogni utente che si crea. Tuttavia, in una grande applicazione in cui forse 50 - 75 modelli condividono relazioni con il modello utente, la vostra seminatrice di utenti finirebbe fondamentalmente per seminare l'intero database con tutte le sue relazioni.

La mia domanda è: è questo il modo migliore per gestire questo? L'unica altra cosa che mi viene in mente è Seme degli utenti prima (senza seminare alcuna relazione), quindi estrarre gli utenti casuali dal DB in base alle esigenze mentre si stanno seminando altri modelli. Tuttavia, nei casi in cui devono essere unici, è necessario tenere traccia di quali utenti sono stati utilizzati. Inoltre, sembra che questo aggiunga un sacco di query-bulk in più al processo di seeding.

risposta

4
$factory->define(App\User::class, function (Faker\Generator $faker) { 
    return [ 
     'name' => $faker->name, 
     'email' => $faker->email, 
     'password' => bcrypt(str_random(10)), 
     'remember_token' => str_random(10), 
    ]; 
}); 

$factory->define(App\Post::class, function (Faker\Generator $faker) { 
    return [ 
     'name' => $faker->name, 
     'body' => $faker->paragraph(1), 
     'user_id' => factory(App\User::class)->create()->id, 
    ]; 
}); 

Così ora se si esegue questa operazione factory(App\Post::class, 4)->create() creerà 4 messaggi diversi e nel processo di creare anche 4 utenti diversi.

Se si desidera che lo stesso utente per tutti i post quello che faccio di solito è:

$user = factory(App\User::class)->create(); 
$posts = factory(App\Posts::class, 40)->create(['user_id' => $user->id]); 
+0

Grazie per la risposta. Ho provato cose del genere. Tuttavia, se voglio usare lo stesso insieme di utenti per, diciamo, altri 20 modelli, sento che i miei seminatori si confondono abbastanza rapidamente. Essenzialmente, tutta la mia logica di seeding finisce in un file, perché un 'utente' è associato a tanti altri modelli. Ma forse non c'è un modo migliore – djt

5

Personalmente penso che una classe usate per gestire queste relazioni è più bello poi le classi seminatrice separate, perché avete tutta la logica in un posto, quindi in uno sguardo puoi vedere cosa sta succedendo. (Chiunque conosca un approccio migliore: per favore condividi) :)

Una soluzione potrebbe essere: un DatabaseSeeder e metodi privati ​​all'interno della classe per mantenere il metodo "run" un po 'più pulito. Ho questo esempio qui sotto, che ha un Utente, Link, LinkUser (many-to-many) e un Note (many-to-one).

Per le relazioni molti-a-molti, prima creo tutti i collegamenti e ottengo gli ID inseriti. (dato che gli ID sono auto-inc penso che gli ID possano essere recuperati più facilmente (ottieni il massimo), ma non importa in questo esempio). Quindi crea gli utenti e associa alcuni collegamenti casuali a ciascun utente (molti a molti). Crea anche note casuali per ciascun utente (esempio molti-a-uno). Usa i metodi "factory".

Se si sostituisce il 'Link' per il tuo 'Post' questo dovrebbe funzionare. (È possibile rimuovere la sezione 'Nota', allora ...)

(c'è anche un metodo per assicurarsi di avere 1 utente valido con le proprie credenziali di accesso.)

<?php 

use Illuminate\Database\Seeder; 

class DatabaseSeeder extends Seeder 
{ 
    /** 
    * Run the database seeds. 
    * 
    * @return void 
    */ 
    public function run() 
    { 
     // Create random links 
     factory(App\Link::class, 100)->create(); 

     // Fetch the link ids 
     $link_ids = App\Link::all('id')->pluck('id')->toArray(); 

     // Create random users 
     factory(App\User::class, 50)->create()->each(function ($user) use ($link_ids) { 

      // Example: Many-to-many relations 
      $this->attachRandomLinksToUser($user->id, $link_ids); 

      // Example: Many-to-one relations 
      $this->createNotesForUserId($user->id); 
     }); 

     // Make sure you have a user to login with (your own email, name and password) 
     $this->updateCredentialsForTestLogin('[email protected]', 'John Doe', 'my-password'); 
    } 

    /** 
    * @param $user_id 
    * @param $link_ids 
    * @return void 
    */ 
    private function attachRandomLinksToUser($user_id, $link_ids) 
    { 
     $amount = random_int(0, count($link_ids)); // The amount of links for this user 
     echo "Attach " . $amount . " link(s) to user " . $user_id . "\n"; 

     if($amount > 0) { 
      $keys = (array)array_rand($link_ids, $amount); // Random links 

      foreach($keys as $key) { 
       DB::table('link_user')->insert([ 
        'link_id' => $link_ids[$key], 
        'user_id' => $user_id, 
       ]); 
      } 
     } 
    } 

    /** 
    * @param $user_id 
    * @return void 
    */ 
    private function createNotesForUserId($user_id) 
    { 
     $amount = random_int(10, 50); 
     factory(App\Note::class, $amount)->create([ 
      'user_id' => $user_id 
     ]); 
    } 

    /** 
    * @param $email 
    * @param $name 
    * @param $password 
    * @return void 
    */ 
    private function updateCredentialsForTestLogin($email, $name, $password) 
    { 
     $user = App\User::where('email', $email)->first(); 
     if(!$user) { 
      $user = App\User::find(1); 
     } 
     $user->name = $name; 
     $user->email = $email; 
     $user->password = bcrypt($password); // Or whatever you use for password encryption 
     $user->save(); 
    } 
} 
8

È possibile utilizzare saveMany anche.Per esempio:

factory(User::class, 10)->create()->each(function ($user) { 
    $user->posts()->saveMany(factory(Posts::class, 5)->make()); 
}); 
+0

Non sapevo di 'saveMany()' fino a quando non ho visto questa risposta. Grazie. :) – Vaughany

4

si può fare questo usando le chiusure all'interno del ModelFactory come discusso here.

Questa soluzione funziona in modo pulito ed elegante anche con seminatrici.

$factory->define(App\User::class, function (Faker\Generator $faker) { 
    return [ 
     'name' => $faker->name, 
     'email' => $faker->email, 
     'password' => bcrypt(str_random(10)), 
     'remember_token' => str_random(10), 
    ]; 
}); 

$factory->define(App\Post::class, function (Faker\Generator $faker) { 
    return [ 
     'name' => $faker->name, 
     'body' => $faker->paragraph(1), 
     'user_id' => function() { 
      return factory(App\User::class)->create()->id; 
     }, 
    ]; 
}); 

Per la vostra seminatrice, usare qualcosa di semplice come questo:

//create 10 users 
factory(User::class, 10)->create()->each(function ($user) { 
    //create 5 posts for each user 
    factory(Post::class, 5)->create(['user_id'=>$user->id]); 
}); 

Nota: questo metodo non crea le voci non necessarie nel database, invece gli attributi passati vengono assegnati prima della creazione del record associati.

Problemi correlati