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);
$path
にプロジェクトのルートディレクトリを設定- Composerのオートローダの読み込み
Aura\Project_Kernel\Factory
クラスからカーネルを生成- カーネルの実行
をしていることがわかります。
CodeIgnirerへの統合
Auraを使いCodeIgniterのコマンドラインツールを作成したわけですが、統合といってもできるだけAuraそのままで統合しています。
フォルダ構成は以下のようになってます。cli
がcli/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のものとほとんど変わらないのですが、
- プロジェクトのルートディレクトリへの
chdir()
- 定数
ROOTPATH
の追加 - CodeIgniterインスタンスの生成
- クラスエイリアスの設定
が追加されています。
「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