最近仕事でLaravelを使い始めました。
で、バッチ用にコマンドライン処理を作成する機会がありました。
公式ドキュメント見れば大体分かるのですが、忘れっぽいので備忘としてやったことをまとめておきます。
環境
Laravel Framework 5.4.0
PHP 7.0.13
Commandクラスの雛形生成
CLIで実行するクラスは、LaravelのCommandクラスを継承して作成します。
これをartisanコマンドとして追加し、それを実行させることになります。
このクラスの雛形は、artisanを使用して生成できます。
$ php artisan make:command SampleCommand
上記の場合はSampleCommandというクラスを作成しています。
これを叩くと、app/Console/Commands配下に、以下のようにクラスが生成されます。
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
class SampleCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'command:name';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Command description';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
//
}
}
コメント見れば大体わかりますが、
- $signature:artisanコマンドとして登録するときの識別子
- $description:コマンドの説明
- コンストラクタ
- handle:実際の処理を記述する
という感じです。
ちなみにこんな感じで叩けば
$ php artisan make:command sample/SampleCommand
app/Console/Commands/sample配下にクラスが生成され、名前空間もそれに応じた形に設定されます。
<?php
namespace App\Console\Commands\sample;
use Illuminate\Console\Command;
class SampleCommand extends Command
...以下略
Commandクラスをartisanコマンドとして登録する
先程作成したCommandクラスをartisanコマンドとして登録します。
app/Console/Kernel.phpの$commands配列に、作成したクラスを追加します。
<?php
namespace App\Console;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel
{
/**
* The Artisan commands provided by your application.
*
* @var array
*/
protected $commands = [
Commands\SampleCommand::class
];
...以下略
追加できたら、artisan listでコマンド登録がされているかを確認。
$ php artisan list
Laravel Framework 5.4.0
...中略
command
command:name Command description
登録したクラスのsignature、descriptionに基づいて登録したコマンドが表示されます。
これで、
$ php artisan command:name
と叩くことで、先程のCommandクラスが実行できる状態になっています。
次にCommandクラスの必要な箇所を変更してみます。
signatureの変更
artisanコマンドとして登録するときの識別子になります。
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'command:name';
名前空間
コロンで区切ることで、名前空間としてコマンドのグルーピングができます。
上記の例であれば「command」という名前空間になるので、artisan listでは
command
command:name Command description
「command」でグルーピングして表示されます。
もちろん指定しなくても問題なく、
protected $signature = 'name';
その場合は、Available Commandsとして表示されます。
Available commands:
...中略
name Command description
実行時引数の指定
signatureには、実行時引数の定義をすることもできます。
必須引数の指定
protected $signature = 'sample:sample {name}';
第1引数必須、引数に指定された値は、プログラム中では「name」というキーで取得できるようになります。
引数は複数指定することが可能です。
protected $signature = 'sample:sample {name} {age}';
引数指定無しで実行すると、こんな感じでエラーとなります。
$ php artisan sample:sample
[Symfony\Component\Console\Exception\RuntimeException]
Not enough arguments (missing: "name").
引数の数が合わない場合もエラーとなります。
$ php artisan sample:sample laravel hoge fuga
[Symfony\Component\Console\Exception\RuntimeException]
Too many arguments, expected arguments "command" "name" "age".
引数の取得はこんな感じ。
public function handle()
{
$name = $this->argument("name");
$this->info("Hello $name");
}
引数指定して実行するとこんな感じになります。
$ php artisan sample:sample laravel
Hello laravel
任意引数の指定
protected $signature = 'sample:sample {name?}';
第1引数は任意になります。
値の取得方法は先程と同じです。
任意指定かつ、指定されなかった場合のデフォルト値を指定することもできます。
protected $signature = 'sample:sample {name=laravel}';
引数に説明をつける
Help用に説明をつけることができます。
引数定義に、コロン区切りで記述します。
protected $signature = 'sample:sample {name=laravel : 名前を指定} {age? : 年齢を指定}';
指定すると、Help表示時に説明も表示されるようになります。
$ php artisan sample:sample -h
Usage:
command:sample [<name>] [<age>]
Arguments:
name 名前を指定 [default: "laravel"]
age 年齢を指定
Options:
...以下略
オプションの指定
signatureには、実行時オプションの定義をすることもできます。
基本的な指定
protected $signature = 'sample:sample {--dry-run}';
$ php artisan sample:sample --dry-run
オプションの取得はこんな感じ。
public function handle()
{
$dry_run = $this->option("dry-run");
}
該当オプションの指定があった場合はtrue、なければfalseが取得されます。
値を指定するオプションの指定
protected $signature = 'sample:sample {--greeting=}';
$ php artisan sample:sample --greeting=Hello
値の取得方法は先程と同じです。
オプションを省略しても、実行時エラーにはなりません。
値を指定してデフォルト値も設定するオプションの指定
protected $signature = 'sample:sample {--greeting=Hello}';
この指定の場合、実行時にオプションを設定しなくても値が取得できます。
$ php artisan sample:sample '--greeting=Good morning'
$ php artisan sample:sample --greeting=
$ php artisan sample:sample --greeting
$ php artisan sample:sample
オプションに説明をつける
引数と同じく、Help用に説明をつけることができます。
オプション定義に、コロン区切りで記述します。
protected $signature = 'sample:sample {--greeting=Hello : 挨拶を指定}';
指定すると、Help表示時に説明も表示されるようになります。
$ php artisan sample:sample -h
Usage:
command:sample [options]
Options:
--greeting[=GREETING] 挨拶を指定 [default: "Hello"]
...以下略
オプションのショートカットを設定する
オプションの完全名の前に、パイプ区切りでショートカット名を指定できます。
protected $signature = 'sample:sample {--g|greeting=Hello : 挨拶を指定}';
$ php artisan sample:sample '-gGood evening'
descriptionの変更
artisanコマンドの説明になります。
/**
* The console command description.
*
* @var string
*/
protected $description = 'サンプルコマンドアプリケーションです';
artisan list表示時、Help表示時に説明も表示されるようになります。
$ php artisan list
Laravel Framework 5.4.0
...中略
command
command:sample サンプルコマンドアプリケーションです
...中略
$ php artisan command:sample -h
Usage:
command:sample [options]
...中略
Help:
サンプルコマンドアプリケーションです
コンストラクタ
インジェクションしたい場合は、コンストラクタで行っておけばいいですね。
private $messageCreator;
/**
* Create a new command instance.
*
* @return void
*/
public function __construct(MessageCreator $messageCreator)
{
parent::__construct();
$this->messageCreator = $messageCreator;
}
コンストラクタの段階では、実行時引数やオプションの値は取得できません。
処理の記述
具体的な処理はhandleに記述していきます。
実際に使ってみた機能についてまとめてみます。
対話的処理
コンソールで色々できます。
<?php
class SampleInteractiveCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'sample:interactive';
...中略
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$this->info('start');
$name = $this->ask('名前を入力してください');
$age = $this->ask('年齢を入力してください');
$this->info("名前 : $name");
$this->info("年齢 : $age");
if ($this->confirm('この内容で実行してよろしいですか?')) {
$this->info("$name $age years old");
} else {
$this->info('cancel');
}
$this->info('end');
}
}
$ php artisan sample:interactive
start
名前を入力してください:
> laravel
年齢を入力してください:
> 5
名前 : laravel
年齢 : 5
この内容で実行してよろしいですか? (yes/no) [no]:
> yes
laravel 5 years old
end
$ php artisan sample:interactive
start
名前を入力してください:
> Taro
年齢を入力してください:
> 10
名前 : Taro
年齢 : 10
この内容で実行してよろしいですか? (yes/no) [no]:
> no
cancel
end
コンソールへのメッセージ出力
class SampleConsoleOutputCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'sample:console-output';
... 中略
public function handle()
{
$this->info('info');
$this->line('line');
$this->comment('comment');
$this->question('question');
$this->error('error');
$this->table(
['名前', '年齢'],
[
['Taro', 10],
['Laravel', 5],
]
);
}
}
これ実行すると
色つけたり、テーブル形式で整形した結果を画面に表示してくれます。
実行終了時の戻り値
シェルで実行して、Commandの戻り値をみて云々したい、みたいな場合は、handleから整数値を戻すことで対応できます。
class SampleExitCodeCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'sample:exit-code {--force-error : 強制的にエラー扱いにする}';
... 中略
public function handle()
{
$this->info('start');
if ($this->option('force-error')) {
$this->error('error!');
return config('command.exit_code.ERROR');
}
$this->info('end');
return config('command.exit_code.SUCCESS');
}
}
<?php
return [
'exit_code' => [
'SUCCESS' => 0,
'ERROR' => 1
],
];
$ php artisan sample:exit-code
start
end
$ echo $?
0
$ php artisan sample:exit-code --force-error
start
error!
$ echo $?
1
実行時引数のvalidation
どうやるのが望ましいものなのかよく分からなかったので、Validatorファサード使いました。
class SampleValidateCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'sample:validate {name : 名前を指定} {age? : 年齢を指定}';
... 中略
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$this->info('start');
try {
$this->validate();
} catch (ValidationException $e) {
$this->error('validation error!');
foreach ($e->validator->getMessageBag()->all() as $error) {
$this->error($error);
}
return config('command.exit_code.ERROR');
}
$this->info('end');
return config('command.exit_code.SUCCESS');
}
/**
* Validation
*/
private function validate()
{
\Validator::validate(
array_filter($this->arguments()),
[
'name' => 'max:10',
'age' => 'numeric|min:20',
],
[
'name.max' => '名前は10文字以内で入力してください',
'age.numeric' => '年齢は数値を入力してください',
'age.min' => '年齢は20才以上で入力してください'
]
);
}
}
$ php artisan sample:validate laravel 20
start
end
$ php artisan sample:validate laravel123! 19
start
validation error!
名前は10文字以内で入力してください
年齢は20才以上で入力してください
こういうのもありました。
使ってみてもいいかもしれないですね。
プログレスバー
class SampleProgressBarCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'sample:progress-bar';
... 中略
public function handle()
{
$count = 5;
// 標準的なプログレスバー
{
$progressBar = $this->output->createProgressBar($count);
$i = 0;
while ($i++ < $count) {
$this->processing();
$progressBar->advance();
}
$progressBar->finish();
}
echo PHP_EOL;
// プログレスバーのデザイン変更
{
$progressBar = $this->output->createProgressBar($count);
$progressBar->setBarCharacter('>');
$progressBar->setEmptyBarCharacter('-');
$progressBar->setProgressCharacter('!');
$i = 0;
while ($i++ < $count) {
$this->processing();
$progressBar->advance();
}
$progressBar->finish();
}
echo PHP_EOL;
// プレースホルダーでメッセージを定義して表示
{
$i = 0;
$progressBar = $this->output->createProgressBar($count);
$progressBar->setFormatDefinition('custom', ' %current%/%max% -- %message% (%currentNo%)');
$progressBar->setFormat('custom');
while ($i++ < $count) {
$this->processing();
$progressBar->setMessage('processing...');
$progressBar->setMessage('No.' . $i, 'currentNo');
$progressBar->advance();
}
$progressBar->finish();
}
echo PHP_EOL;
}
/**
* 擬似的な処理 サンプルなのでバーの表示を見やすくするために遅延をさせる
*/
private function processing()
{
sleep(1);
}
}
これ実行すると、こんな感じで進捗状況が表示されます。
上述してきたように、既にLaravelで用意されている機能を組み合わせるだけで、簡単にコマンドラインアプリケーションが作成できます。
サンプル
作成したサンプルソース群を以下に置いてあります。
参考ページ
以下を参考にさせていただきました。
ありがとうございました。