はじめに
@polidogさんに誘われてshizuoka.php#1で登壇しました。
https://shizuokaphp.connpass.com/event/26794/
その時の補足というか、事前の試行錯誤とか書きます。
取り留めがない上にポエム感が拭えませんが(特に後半)、ご容赦のほど。。。
登壇資料
資料とデモで使ったコードは以下に上がってます
https://github.com/toniokatanuki/shizuokaphp.20190914
特にpptxは以下にあります
https://github.com/toniokatanuki/shizuokaphp.20190914/blob/master/shizuoka.php.pptx?raw=true
オプション機能実装のための試行錯誤
このテーマの準備をするにあたって、コマンドラインオプションについて結構手こずったのでこの場を借りてどんなことやってきたかを紹介します
候補1.getopt関数
ググるとgetopt関数が用意されてるよって記事を見かけたけど、どうも直感的じゃなかったので採用見送り。
※以下、getopt関数マニュアル
https://www.php.net/manual/ja/function.getopt.php
例えば、以下のスクリプト(cli.php)を書いたとします
<?php
print "start.\n";
$options = getopt("a:b:c::");
var_dump($options);
で、マニュアルいわく
単一の文字 (値を受け付けない)
文字の後にコロンをひとつ続けたもの (値が必須であるパラメータ)
文字の後にコロンをふたつ続けたもの (値がオプションであるパラメータ)
とのことなので、aとbとcというオプションが設定可能で、aおよびbは値必須、cは値が必須でない・という認識をしたわけですが、
コマンド叩くと以下みたいになるんですね。
$ php cli.php -a "hogehoge" -b "fugafuga" -c "55"
start.
array(3) {
["a"]=>
string(8) "hogehoge"
["b"]=>
string(8) "fugafuga"
["c"]=>
bool(false)
}
$options配列のcの中には55 が入ってほしかったのに、それを行うためには以下の書き方をしないと期待した挙動にならない・という。
$ php cli.php -a "hogehoge" -b "fugafuga" -c="55"
start.
array(3) {
["a"]=>
string(8) "hogehoge"
["b"]=>
string(8) "fugafuga"
["c"]=>
string(2) "55"
}
なので、別の方法を考えよう・・・と、思ったわけです。
が、ここまで書いていて気づいたのですが(せめて登壇前に気づきたかった。。。)、公式では
注意: オプションの値で、" " (空白) を区切り文字として使用することはできません。
と紹介されてるので、前者のコードで動かないのは仕様っぽいですね。
いずれにせよ、とっつきにくかったのでこれを採用するのは見送りました。
候補2.既存のコマンドのソースを読んで仕組みを参考にする
候補1のでうまくいかなかったので、「既存のコマンドたちはどうやってオプションを実現しているのかしら?」と、catコマンドのソースを読んでみることに。
で、該当部分が以下のようなコードだったのですが。
/* Parse command line options. */
while ((c = getopt_long (argc, argv, "benstuvAET", long_options, NULL)) != -1)
{
switch (c)
{
case 'b':
number = true;
number_nonblank = true;
break;
case 'e':
show_ends = true;
show_nonprinting = true;
break;
case 'n':
number = true;
break;
case 's':
squeeze_blank = true;
break;
case 't':
show_tabs = true;
show_nonprinting = true;
break;
case 'u':
/* We provide the -u feature unconditionally. */
break;
case 'v':
show_nonprinting = true;
break;
case 'A':
show_nonprinting = true;
show_ends = true;
show_tabs = true;
break;
case 'E':
show_ends = true;
break;
case 'T':
show_tabs = true;
break;
case_GETOPT_HELP_CHAR;
case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
default:
usage (EXIT_FAILURE);
}
}
これでやってることって、結局c版のgetopt(および長いオプション版のgetopt_long)を実行してるようなものだったので、ここで挫折。
多分実態としては、コマンド全文を取得してから単語で区切って、「これは引数」「これはオプション」「これはオプションの引数」みたいなループがまわってパースしてるんじゃないかな・とは思うんですが、ちゃんと確認しきれず。
候補3.既存ライブラリを使う
結局、以下の記事を見つけたので、この中から使いやすいものを選ぶことに。
https://qiita.com/tanakahisateru/items/785a56fb6950d8a52006
なんとなく特定のFWのいち機能を使うようなものは避けたかった(コンポーネント単体で使用可能と謳われていたとしても)のと、使い心地を踏まえた結果、Console_CommandLineを採用。
https://github.com/pear/Console_CommandLine
これでやりたかったことはできたので、そのまま取りまとめて本番に望んだかたちになります。
(続きは先の登壇資料のURLより。。。)
強引にまとめ
最終的にはどうにか形になったものの、やっぱり勉強不足は否めないというか。。。
なので、次回の勉強会では皆さんを唸らせられるような話ができればなあと考えております。
2月にshizuoka.php#2 を予定しているので、そこでリベンジできたらいいなあ。