Aura.Cli_ProjectでCodeIgniter 3.0用のコマンドラインツールを作成した話①

Aura for PHP Advent Calendar 2015 の3日目、および、CodeIgniter Advent Calendar 2015 の13日目です。どちらもまだ、空きがありますので、興味のある方は気軽に参加してください。

みなさん、Aura for PHP 使ってますか?

Auraとは、高品質で、よくテストされ、セマンティックバージョンイングに従い、標準に準拠した、独立したライブラリパッケージを提供しているプロジェクトです。

たぶん、モダンなコードを書いているPHPユーザは何かしらAuraのライブラリを使っていることでしょう。

PHPのライブラリとしてはSymfonyコンポーネントが有名ですが、それ以外の有力な選択肢の一つとしてAuraがあります。キーワードはDecouplingです。

Auraは、コマンドラインから実行するスクリプトの作成を支援するライブラリやフレームワークも提供しています。

CLIプロジェクトの雛形としてAura.Cli_Projectがあります。

今日は、このAura.Cli_Projectを使い、CodeIgniterのコマンドラインツールを作成した話です。

Aura.Cli_Projectのインストール

Aura.Cli_ProjectはCLIプロジェクトの雛形です。

インストールしてみましょう。なお、PHP 5.4以上が必要です。

$ composer create-project aura/cli-project
Installing aura/cli-project (2.0.2)
  - Installing aura/cli-project (2.0.2)
    Downloading: 100%         

Created project in /home/kenji/tmp/cli-project
Loading composer repositories with package information
Installing dependencies (including require-dev)
  - Installing aura/dispatcher (2.0.3)
    Loading from cache

  - Installing aura/cli (2.1.1)
    Loading from cache

  - Installing psr/log (1.0.0)
    Loading from cache

  - Installing aura/di (2.2.3)
    Loading from cache

  - Installing aura/project-kernel (2.1.1)
    Loading from cache

  - Installing aura/cli-kernel (2.0.3)
    Loading from cache

  - Installing monolog/monolog (1.17.2)
    Loading from cache

monolog/monolog suggests installing graylog2/gelf-php (Allow sending log messages to a GrayLog2 server)
monolog/monolog suggests installing raven/raven (Allow sending log messages to a Sentry server)
monolog/monolog suggests installing doctrine/couchdb (Allow sending log messages to a CouchDB server)
monolog/monolog suggests installing ruflin/elastica (Allow sending log messages to an Elastic Search server)
monolog/monolog suggests installing videlalvaro/php-amqplib (Allow sending log messages to an AMQP server using php-amqplib)
monolog/monolog suggests installing ext-amqp (Allow sending log messages to an AMQP server (1.0+ required))
monolog/monolog suggests installing ext-mongo (Allow sending log messages to a MongoDB server)
monolog/monolog suggests installing aws/aws-sdk-php (Allow sending log messages to AWS services like DynamoDB)
monolog/monolog suggests installing rollbar/rollbar (Allow sending log messages to Rollbar)
monolog/monolog suggests installing php-console/php-console (Allow sending log messages to Google Chrome)
Writing lock file
Generating autoload files

フォルダ構成は以下のようになっています。

cli-project/
├── cli/
│   └── console.php ... コマンドファイル
├── config/          ... 設定を配置
│   ├── Common.php
│   ├── Dev.php
│   ├── Prod.php
│   ├── Test.php
│   └── _env.php
├── src/             ... ソースを配置
├── tests/           ... テストを配置
├── tmp/
│   ├── cache/
│   └── log/        ... ログファイル
└── vendor/

依存コンポーネントは以下です。

$ composer show -i
aura/cli            2.1.1  Provides the equivalent of request (Context) and response (Stdio) classes for a command line environm...
aura/cli-kernel     2.0.3  The kernel files for an Aura CLI project.
aura/di             2.2.3  Provides a dependency injection container system with native support for constructor- and setter-base...
aura/dispatcher     2.0.3  Creates objects from a factory and invokes methods using named parameters; also provides a trait for ...
aura/project-kernel 2.1.1  The shared kernel files for an Aura project.
monolog/monolog     1.17.2 Sends your logs to files, sockets, inboxes, databases and various web services
psr/log             1.0.0  Common interface for logging libraries

Auraのライブラリ以外には、monologに依存しているだけです。

aura/diはDIコンテナです。「PHP - auraで覚える Dependency Injection - Qiita」を参照してください。

コマンドの使い方

早速コマンドを実行してみましょう。

$ cd cli-project/
$ php cli/console.php
hello
    A demonstration 'hello world' command.
help
    Gets the available commands, or the help for one command.

php cli/console.phpと引数なしで実行すると、コマンドの一覧が表示されます。

helloコマンドとhelpコマンドがあることがわかります。

helloコマンドを実行してみましょう。

$ php cli/console.php hello
Hello World!

引数を追加してみましょう。

$ php cli/console.php hello Aura
Hello Aura!

とまあ、使い方はこんな感じです。

helloコマンドのソース

さて、helloコマンドのソースを探してみましょう。以下にあります。

config/Common.php

    protected function modifyCliDispatcher(Container $di)
    {
        $context = $di->get('aura/cli-kernel:context');
        $stdio = $di->get('aura/cli-kernel:stdio');
        $logger = $di->get('aura/project-kernel:logger');
        $dispatcher = $di->get('aura/cli-kernel:dispatcher');
        $dispatcher->setObject(
            'hello',
            function ($name = 'World') use ($context, $stdio, $logger) {
                $stdio->outln("Hello {$name}!");
                $logger->debug("Said hello to '{$name}'");
            }
        );
    }

$diはDIコンテナですから、DIコンテナからcontext、stdio、logger、dispatcherを取得して、dispatcherにhelloをクロージャで登録していることがわかります。

以下で標準出力に出力していることがわかります。

                $stdio->outln("Hello {$name}!");

このように、コマンドを書く場合は、DIコンテナからcontext、stdio、loggerなどを取得して処理をすればいいということがわかります。

ちなみに、config/Common.phpはプロジェクトの設定ファイルです。なぜ、そんなところにコマンドを書いているのか?と疑問に思われると思いますが、これは、ルーティング設定にコントローラのコードを書くような感じです。

クラスファイルにするまでもないので、設定ファイルに書いてしまっているということです。もちろん、実際に使うコードでは通常はクラスにすることになるでしょう。

helloコマンドをクラス化

それでは、helloコマンドをクラス化してみましょう。

HelloCommandクラスは以下のようになります。

src/HelloCommand.php

<?php

use Aura\Cli\Stdio;
use Aura\Cli\Context;
use Psr\Log\LoggerInterface;

class HelloCommand
{
    public function __construct(
        Context $context, Stdio $stdio, LoggerInterface $logger
    )
    {
        $this->context = $context;
        $this->stdio = $stdio;
        $this->logger = $logger;
    }

    public function __invoke($name = 'World')
    {
        $this->stdio->outln("Hello {$name}!");
        $this->logger->debug("Said hello to '{$name}'");
    }
}

そして、設定を上記のクラスを使うように変更します。

--- a/config/Common.php
+++ b/config/Common.php
@@ -9,6 +9,12 @@ class Common extends Config
     public function define(Container $di)
     {
         $di->set('aura/project-kernel:logger', $di->newInstance('Monolog\Logger'));
+
+        $di->params['HelloCommand'] = [
+            'context' => $di->lazyGet('aura/cli-kernel:context'),
+            'stdio' => $di->lazyGet('aura/cli-kernel:stdio'),
+            'logger' => $di->lazyGet('aura/project-kernel:logger'),
+        ];
     }

     public function modify(Container $di)
@@ -35,16 +41,11 @@ class Common extends Config

     protected function modifyCliDispatcher(Container $di)
     {
-        $context = $di->get('aura/cli-kernel:context');
-        $stdio = $di->get('aura/cli-kernel:stdio');
-        $logger = $di->get('aura/project-kernel:logger');
         $dispatcher = $di->get('aura/cli-kernel:dispatcher');
+
         $dispatcher->setObject(
             'hello',
-            function ($name = 'World') use ($context, $stdio, $logger) {
-                $stdio->outln("Hello {$name}!");
-                $logger->debug("Said hello to '{$name}'");
-            }
+            $di->lazyNew('HelloCommand')
         );
     }

以下の最初の変更箇所は、コンストラクタインジェクションです。

        $di->params['HelloCommand'] = [
            'context' => $di->lazyGet('aura/cli-kernel:context'),
            'stdio' => $di->lazyGet('aura/cli-kernel:stdio'),
            'logger' => $di->lazyGet('aura/project-kernel:logger'),
        ];

これで完了です。本当はヘルプクラスも必要ですが、とりあえず省略です。

$ php cli/console.php hello
Hello World!
$ php cli/console.php hello Aura
Hello Aura!

はい、同じように動作しました。

長くなりそうなので、続きは次回にしたいと思います。

関連

Date: 2015/12/03

Tags: aura, cli, codeigniter, php