あらすじ
Laravelにはseederがありテスト時などにサンプルデータを気軽に作ることができます。
ただartisanコマンドを用いて呼び出すのですが、この時にshellscriptのように作成するデータ数などを引数として与えられたらなあ...と思っていました。
そんなところartisanのコマンド自作で行けそうといアドバイスをいただいたので、コマンド作成の練習もかねて作成してみることにしました。
通常のSeeder
今回は例として、Laravelのプロジェクトに最初からついてくるUserテーブルのSeederをもとに作っていきます。似た構造のレコードを複数追加するのでFactoryを記述して(されていて)、
<?php
/** @var \Illuminate\Database\Eloquent\Factory $factory */
use App\User;
use Illuminate\Support\Str;
use Faker\Generator as Faker;
$factory->define(User::class, function (Faker $faker) {
return [
'name' => $faker->name,
'email' => $faker->unique()->safeEmail,
'email_verified_at' => now(),
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
'remember_token' => Str::random(10),
];
});
これをSeederから呼び出します。
factoryの第二引数を変えることで生成するレコードの数を変えられるので、ここにartisanコマンドで受け取った引数を渡すことにします。
<?php
use Illuminate\Database\Seeder;
class UsersTableSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
factory(User::class, 10)->create();
}
}
artisanコマンドで引数を使用する
引数の扱いは、
https://qiita.com/nenokido2000/items/abbf70c87c9ad86a2b89
などを参考にしました。
まずはターミナルから雛形を生成します。
$ php artisan make:command BulkUserSeed
作成されたファイルを変更して、
・呼び出すためのコマンド
・引数
・呼ばれた際の処理
を記述していきます。
雛形そのままで動く部分は省略してあります。
<?php
namespace App\Console\Commands;
use App\User;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Schema;
class BulkUserSeed extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'command:BulkUserSeed {number=10 : number of users}';
// 〜〜(中略)〜〜
public function handle()
{
print("Start seeding.\n");
$numberOfUsers = (int)$this->argument("number");
factory(User::class, $numberOfUsers)->create();
print("Seeding complete.\n");
}
}
また、オプションとしての引数も与えられるので、今回は試しにmigrate:refreshのようにテーブルをまず全消去するかを選べるようにしたいと思います。
// ... ...
protected $signature = 'command:BulkUserSeed
{number=10 : number of users}
{--refresh : drop all existing users}';
// ... ...
public function handle()
{
$refresh = $this->option("refresh");
if ($refresh){
print("Are you sure you want to delete all users? [Press y] \n");
$input = trim(fgets(STDIN));
if ($input === 'y') {
print("User table truncated.\n");
User::truncate();
} else {
print("Seeding canceled.\n");
return;
}
print("Start seeding.\n");
$numberOfUsers = (int)$this->argument("number");
factory(User::class, $numberOfUsers)->create();
print("Seeding complete.\n");
}
}
ログなどはお好みで。
これで
> php artisan command:BulkUserSeed
> php artisan command:BulkUserSeed 100
> php artisan command:BulkUserSeed 100 --refresh
などのコマンドが選択できるようになりました。
おまけ 連番でデータを作成する
実は元々作っていたプロジェクトでは(と言っても練習用の仮想の物ですが)、日にちごとに担当者を決めて管理するという機能を付けようとしていました。そこで用いるテーブルには以下のようなものがあります。
例えば1月1日から12月31日までの仮データを一気に作りたい!と思った時は、先ほどと同じ方法ではうまくいかず、dateの値を加算しながら与える必要があります。
1日ずつずれた日付を与えるにはfor文を回しながら
・基準となる日にちを決め、+i日加算した日付を与える
・ループごとに変数を1日ずつインクリメントする
などで達成できます。下のコードでは後者を用いています。
なお、+1 monthなど月単位で加算する場合、月末の日付のズレなどに気を使う必要があるようです。
(参考: https://www.p-nt.com/technicblog/archives/11 など)
<?php
namespace App\Console\Commands;
use App\Assignment;
use Illuminate\Console\Command;
class BulkAssignmentSeed extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'command:BulkAssignmentSeed
{number=10 : number of assignments}
{first_date=1970-01-01 : the date of first row}
{--refresh : drop all existing assignments}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Seeding arbitrary number of assignments.';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$refresh = $this->option("refresh");
if ($refresh){
print("Are you sure you want to delete all Assignments? [Press y] \n");
$input = trim(fgets(STDIN));
if ($input === 'y') {
print("Assignment table truncated.\n");
Assignment::truncate();
} else {
print("Seeding canceled.\n");
return;
}
}
print("Start seeding.\n");
$numberOfAssignments = (int)$this->argument("number");
$base = $this->argument("first_date");
for($i = 0; $i < $numberOfAssignments; $i++){
// Log::debug($baseDate);
$date = date("Y-m-d", strtotime($base . "+($i) day"));
factory(Assignment::class)->create([
'date' => $date,
]);
}
print("Seeding complete.\n");
}
}
<?php
/** @var \Illuminate\Database\Eloquent\Factory $factory */
use App\Assignment;
use Faker\Generator as Faker;
$factory->define(Assignment::class, function (Faker $faker) {
return [
'staff' => $faker->name,
];
});
終わりに
コマンド拡張の楽しさを知ったので、これからも色々やってみたいと思います。