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

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

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

前回は、Aura.Cli_Projectをインストールし、helloコマンドをクラス化しました。

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

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

コマンドファイル

さて、コマンドファイルはどのようになっているか確認してみましょう。

cli/console.php

<?php
/**
 * 
 * This file is part of Aura for PHP.
 * 
 * @package Aura.Cli_Project
 * 
 * @license http://opensource.org/licenses/bsd-license.php BSD
 * 
 */
$path = dirname(__DIR__);  // (1)
require "{$path}/vendor/autoload.php";  // (2)
$kernel = (new \Aura\Project_Kernel\Factory)->newKernel(  // (3)
    $path,                                                // (3)
    'Aura\Cli_Kernel\CliKernel'                           // (3)
);                                                        // (3)
$status = $kernel();  // (4)
exit($status);
  1. $pathにプロジェクトのルートディレクトリを設定
  2. Composerのオートローダの読み込み
  3. Aura\Project_Kernel\Factoryクラスからカーネルを生成
  4. カーネルの実行

をしていることがわかります。

CodeIgnirerへの統合

Auraを使いCodeIgniterのコマンドラインツールを作成したわけですが、統合といってもできるだけAuraそのままで統合しています。

フォルダ構成は以下のようになってます。clicli/console.phpに相当します。

codeigniter/
├── application/
├── ci_instance.php ... CodeIgniterインスタンスを生成するスクリプト
├── cli             ... コマンドファイル
├── config/         ... 設定を配置
├── src/            ... ソースを配置
└── vendor/

ソースは以下にあります。

CodeIgniter用コマンドファイル

コマンドファイルは以下のようになりました。

cli

#!/usr/bin/env php
<?php
/**
 * Part of Cli for CodeIgniter
 *
 * @author     Kenji Suzuki <https://github.com/kenjis>
 * @license    MIT License
 * @copyright  2015 Kenji Suzuki
 * @link       https://github.com/kenjis/codeigniter-cli
 */

$path = __DIR__;
chdir($path);  // (1)

/** @const ROOTPATH CodeIgniter project root directory */
define('ROOTPATH', __DIR__ . '/');  // (2)

require "{$path}/vendor/autoload.php";

// generate CodeIgniter instance
$ci = require "{$path}/ci_instance.php";  // (3)

class_alias('Kenjis\CodeIgniter_Cli\Command\Command', 'Command');  // (4)
class_alias('Kenjis\CodeIgniter_Cli\Command\Seed',    'Seeder');   // (4)
class_alias('Aura\Cli\Help', 'Help');                              // (4)

$kernel = (new \Aura\Project_Kernel\Factory)->newKernel(
    $path,
    'Aura\Cli_Kernel\CliKernel'
);
$status = $kernel();
exit($status);

Auraのものとほとんど変わらないのですが、

  1. プロジェクトのルートディレクトリへのchdir()
  2. 定数ROOTPATHの追加
  3. CodeIgniterインスタンスの生成
  4. クラスエイリアスの設定

が追加されています。

「CodeIgniterインスタンス」というのは、いわゆる「神オブジェクト」的なCodeIgniterのオブジェクトで、このオブジェクトのプロパティにCodeIgniterのライブラリなどがロードされます。

以下のようなCodeIgniterのコントローラやモデルのコードではこのCodeIgniterインスタンスを利用しています。

$this->load->library('form_validation');
$this->form_validation->set_rules('username', 'Username', 'required');

また、CodeIgniterインスタンスは以下のようにget_instance()関数で取得できるシングルトン的なオブジェクトです。

$CI =& get_instance();

CodeIgniterインスタンスの注入

そして、CodeIgniterインスタンスをコマンドクラスで使えるように、DIコンテナに設定します。

config/Common.php

    public function define(Container $di)
    {
        ...

        /* @var $ci \CI_Controller */
        $ci =& get_instance();

        // register built-in command classes
        foreach ($this->commands as $command) {
            $class = 'Kenjis\CodeIgniter_Cli\Command\\' . $command;
            $di->params[$class] = [
                'context' => $di->lazyGet('aura/cli-kernel:context'),
                'stdio' => $di->lazyGet('aura/cli-kernel:stdio'),
                'ci' => $ci,  // ここ
            ];
        }

        ...
    }

これで、コマンドクラスのコンストラクタにCodeIgniterインスタンスが注入されます。

コマンドクラス

コマンドクラス(コマンドを実装するクラス)では、コンストラクタ引数にCodeIgniterインスタンスが注入されるので、以下のようにプロパティに代入すればクラス内で使えます。

以下は、親クラスとなる抽象クラスのコードです。

src/Command/Command.php

    public function __construct(Context $context, Stdio $stdio, CI_Controller $ci)
    {
        $this->context = $context;
        $this->stdio = $stdio;
        $this->ci = $ci;

        $this->ci->load->database();
        $this->db = $this->ci->db;
        $this->ci->load->dbforge();
        $this->dbforge = $this->ci->dbforge;
    }

さらに、CodeIgniterインスタンスのプロパティがコマンドクラスでも使えるように、マジックメソッドを追加しました。

src/Command/Command.php

public function __get($property)
    {
        if (! property_exists($this->ci, $property)) {
            ob_start();
            var_dump(debug_backtrace());
            $backtrace = ob_get_clean();
            file_put_contents(ROOTPATH . '/tmp/backtrace.log', $backtrace, LOCK_EX);
            $this->stdio->errln(
                '<<red>>No such property: ' . $property . ' in CodeIgniter instance<<reset>>'
            );
            $this->stdio->errln('Backtrace was saved in tmp/backtrace.log');
            throw new RuntimeException('Property does not exist');
        }

        return $this->ci->$property;
    }

これで、コマンドクラス内でも以下のようにCodeIgniterのライブラリが使えます。

        $this->load->library('migration');
        $this->load->config('migration');

以下は、マイグレーションを実行するコマンドクラスです。

src/Command/Migrate.php

<?php
/**
 * Part of Cli for CodeIgniter
 *
 * @author     Kenji Suzuki <https://github.com/kenjis>
 * @license    MIT License
 * @copyright  2015 Kenji Suzuki
 * @link       https://github.com/kenjis/codeigniter-cli
 */

namespace Kenjis\CodeIgniter_Cli\Command;

use Aura\Cli\Status;

/**
 * @property \CI_Migration $migration
 * @property \CI_Loader    $load
 * @property \CI_Config    $config
 */
class Migrate extends Command
{
    public function __invoke($command = null)
    {
        $this->load->library('migration');
        $this->load->config('migration');

        if ($command === 'status') {
            $this->listMigrationFiles();
            return;
        }

        if ($command === 'version') {
            $this->showVersions();
            return;
        }

        // if argument is digits, migrate to the version
        if (ctype_digit($command)) {
            if ($this->migrateToVersion($command) === false) {
                return Status::FAILURE;
            } else {
                $this->showVersions();
                return;
            }
        }

        if ($command !== null) {
            $this->stdio->errln(
                '<<red>>No such command: ' . $command . '<<reset>>'
            );
            return Status::USAGE;
        }

        // if no argument, migrate to current
        if ($this->migration->current() === false) {
            $this->stdio->errln(
                '<<red>>' . $this->migration->error_string() . '<<reset>>'
            );
            return Status::FAILURE;
        } else {
            $this->showVersions();
        }
    }

    ...
}

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

関連

Date: 2015/12/04

Tags: aura, cli, codeigniter, php