この記事は Symfony Advent Calendar 2017 10日目の記事です。

はじめに

普段、CLIで利用するようなツールは、シェルスクリプトかGoで書くことが多いのですが、社内で利用するツールならPHPで作った方がメンテナンス性が上がってみんなハッピーかな?と思ったのが始まりです。
イメージ的にはComposerの様にPHPと意識しなくても使える感じが好ましいなと思ったので、Pharファイルで配布する形を目指すことにしました。

簡単なコンソールアプリを作成する

みんな大好き symfony/consoleコンポーネントを利用して、簡単なコンソールアプリケーションを作成します。

引数無しで実行したら、

Hello World!

引数に文字列foobarを渡したら

Hello Foobar!

みたいな感じのhelloコマンドを作成したいと思います。

Helloコマンド

<?php

namespace Qcmnagai\Hello;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class HelloCommand extends Command
{
    protected function configure()
    {
        $this
            ->setName('hello')
            ->addArgument('name', InputArgument::OPTIONAL, 'Name for greeting.', 'world');
    }
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $output->writeln(sprintf('Hello %s.', ucfirst($input->getArgument('name'))));
    }
}

続いて、上記コマンドを登録&実行するアプリケーションを作成します。
ファイル名はmain.phpとします。

<?php
require __DIR__ . '/vendor/autoload.php';
use Symfony\Component\Console\Application;
$application = new Application();
$application->add(new \Qcmnagai\Hello\HelloCommand());
$application->run();

この状態で実行してみると、以下のようになります。

$ php ./main.php hello
Hello World.

$ php ./main.php nagai
Hello Nagai.

Pharファイルを作ってみる

Pharファイルの作成ですが、Boxというツールを利用して作成するのが簡単です。

Boxのインストール

公式サイトに書かれているまま、以下のコマンドを実行します。

$ curl -LSs https://box-project.github.io/box2/installer.php | php

Box Installer
=============

Environment Check
-----------------

"-" indicates success.
"*" indicates error.

 - You have a supported version of PHP (>= 5.3.3).
 - You have the "phar" extension installed.
 - You have a supported version of the "phar" extension.
 - You have the "openssl" extension installed.
 * Notice: The "phar.readonly" setting needs to be off to create Phars.
 - The "detect_unicode" setting is off.
 - The "allow_url_fopen" setting is on.

Everything seems good!

Download
--------

 - Downloading manifest...
 - Reading manifest...
 - Downloading Box v2.7.5...
 - Checking file checksum...
 - Checking if valid Phar...
 - Making Box executable...

Box installed!

ここで注目してほしいのが、以下の文章です。

  • Notice: The “phar.readonly” setting needs to be off to create Phars.

phar.readonlyというのがオンになっているから、オフにしてねというメッセージなのですが、今回はphp.iniを触らずに実行時に解決したいと思いますので無視したいと思います。

box.jsonの作成

Pharファイルをどういう風に作成するかはbox.jsonというファイルに設定を書く必要があります。
今回は以下のようなファイルを用意しました。

{
    "alias": "hello.phar",
    "chmod": "0755",
    "compactors": [],
    "directories": ["src", "vendor"],
    "main": "main.php",
    "output": "hello.phar",
    "stub": true
}

大体内容を見ればわかると思います。
ちなみにstubという項目は、ローダスタブを設定するかどうかという項目らしく、これをtrueにしておくと、Phar拡張が有効になっていない環境でもPharファイルを直接読み込んだり、実行したりしてくれるらしいです。

参考
PHP: Phar ファイルのスタブ - Manual

Pharファイルのビルド

$ ./box.phar build

で、通常は実行可能なのですが、phar.readonlyがオンになっている場合は、エラーになってしまうので、以下のように実行時に解決するようにします。

$ php -d phar.readonly=0 ./box.phar build

これで無事、hello.pharファイルが生成されたと思います。
実行してみましょう。

$ ./hello.phar hello
Hello World.

無事実行することが出来ました。

シングルコマンドアプリケーションにする

今回のhelloコマンドの場合、サブコマンドは必要ありません。
なので、本来なら以下のように実行したいところです。

$ ./hello.phar
Hello World.

この場合は、アプリケーションを作成するところを以下のように変更すればOKです。

 <?php
 require __DIR__ . '/vendor/autoload.php';
 use Symfony\Component\Console\Application;
 $application = new Application();
-$application->add(new \Qcmnagai\Hello\HelloCommand());
+$command = new \Qcmnagai\Hello\HelloCommand();
+$application->add($command);
+$application->setDefaultCommand($command->getName(), true);
 $application->run();

肝はsetDefaultCommand()ですね。
第2引数をtrueにすることで、シングルコマンドアプリケーションかどうかを設定しています。

また、以下のようにアプリケーション名とバージョンを設定することもできます。

<?php
-$application = new Application();
+$application = new Application('hello', '1.0.0');

この状態で、buildするとサブコマンドを指定しなくても、helloコマンドが実行されるようになりました!

$ ./hello.phar
Hello World.

$ ./hello.phar quartet
Hello Quartet.

イメージ通りのCLIツールの出来上がりです!

参考
Building a single Command Application (The Console Component - Symfony Docs)

おわりに

Pharファイルを初めて作ってみましたが、Boxのおかげでとても簡単に作成することが出来ました。
使い慣れたsymfony/consoleコンポーネントを利用して作成することもでき、個人的に満足度が高かったです。
これからCLIツールを作成する時は、PHPで作ることも選択肢に入れていきたいなと思いました!