Ethna 2.3.7とEthna 2.5.0preview5を共存させるとethnaコマンドがバグる

先日のEthna_Plugin_Debugtoolbarのインストールで、すでにインストールしてるEthna 2.3.7はそのままにしてEthna 2.5.0preview5をインストールしたらethnaコマンドがちゃんと動かなった。

再現手順

再現手順は以下のとおり。

以降長いので略記。

  • Ethna 2.3.7 -> 2.3.7
  • Ethna 2.5.0preview5 -> 2.5.0p5

2.3.7な環境で、

$ ethna -v
Ethna 2.3.7

プロジェクトを作成して、

$ ethna add-project sample

そのプロジェクトに2.5.0p5をインストール。

$ cd sample
$ ethna pear-local channel-discover pear.ethna.jp
$ ethna pear-local install -a http://pear.ethna.jp/pear/Ethna-2.5.2009062201.tgz
$ bin/ethna -v
Ethna 2.5.0-preview5 (using PHP 5.2.6-3ubuntu4.1)

インストールした2.5.0p5のethnaコマンドを引数なしで実行すると、

$ bin/ethna

Fatal error: Cannot redeclare class Ethna_Plugin_Handle_AddAction in /usr/share/php/Ethna/class/Plugin/Handle/Ethna_Plugin_Handle_AddAction.php on line 143

Call Stack:
    0.0037      89304   1. {main}() /home/okonomi/public_html/blog/sample/lib/Ethna/bin/ethna_handle.php:0
    0.1453    3294016   2. Ethna_Plugin_Handle_Help->perform() /home/okonomi/public_html/blog/sample/lib/Ethna/bin/ethna_handle.php:66
    0.1457    3294864   3. Ethna_Handle->getHandlerList() /home/okonomi/public_html/blog/sample/lib/Ethna/class/Plugin/Handle/Help.php:38
    0.1457    3294864   4. Ethna_Plugin->getPluginList() /home/okonomi/public_html/blog/sample/lib/Ethna/class/Ethna_Handle.php:80
    0.2917    6312800   5. Ethna_Plugin->getPlugin() /home/okonomi/public_html/blog/sample/lib/Ethna/class/Ethna_Plugin.php:115
    0.2917    6312800   6. Ethna_Plugin->_getPlugin() /home/okonomi/public_html/blog/sample/lib/Ethna/class/Ethna_Plugin.php:93
    0.2917    6312800   7. Ethna_Plugin->_loadPlugin() /home/okonomi/public_html/blog/sample/lib/Ethna/class/Ethna_Plugin.php:145
    0.2918    6312800   8. Ethna_Plugin->_includePluginSrc() /home/okonomi/public_html/blog/sample/lib/Ethna/class/Ethna_Plugin.php:176

ずぎゃーん。

原因を探る

どうやらプラグインをリストアップするときに何か失敗してるみたい。ソースを追いかけてみる。
Ethna_PluginクラスのgetPluginList()では、$src_registryの内容を元にgetPlugin()を呼んでプラグインをロードしてる。
試しに$src_registryの値をprint_rしてみると、

<?php

 Array
 (
     [Handle] => Array
	 (
	     [Help] => Array
		 (
		     [0] => Ethna_Plugin_Handle_Help
		     [1] => /home/okonomi/public_html/blog/sample/lib/Ethna/class/Plugin/Handle
		     [2] => Help.php
		 )

	     [AddAction] => Array
		 (
		     [0] => Ethna_Plugin_Handle_AddAction
		     [1] => /home/okonomi/public_html/blog/sample/lib/Ethna/class/Plugin/Handle
		     [2] => AddAction.php
		 )

	     [AddActionTest] => Array
		 (
		     [0] => Ethna_Plugin_Handle_AddActionTest
		     [1] => /home/okonomi/public_html/blog/sample/lib/Ethna/class/Plugin/Handle
		     [2] => AddActionTest.php
		 )

	     [AddAppManager] => Array
		 (
		     [0] => Ethna_Plugin_Handle_AddAppManager
		     [1] => /home/okonomi/public_html/blog/sample/lib/Ethna/class/Plugin/Handle
		     [2] => AddAppManager.php
		 )

	     [AddAppObject] => Array
		 (
		     [0] => Ethna_Plugin_Handle_AddAppObject
		     [1] => /home/okonomi/public_html/blog/sample/lib/Ethna/class/Plugin/Handle
		     [2] => AddAppObject.php
		 )

	     [AddEntryPoint] => Array
		 (
		     [0] => Ethna_Plugin_Handle_AddEntryPoint
		     [1] => /home/okonomi/public_html/blog/sample/lib/Ethna/class/Plugin/Handle
		     [2] => AddEntryPoint.php
		 )

	     [AddProject] => Array
		 (
		     [0] => Ethna_Plugin_Handle_AddProject
		     [1] => /home/okonomi/public_html/blog/sample/lib/Ethna/class/Plugin/Handle
		     [2] => AddProject.php
		 )

	     [AddTemplate] => Array
		 (
		     [0] => Ethna_Plugin_Handle_AddTemplate
		     [1] => /home/okonomi/public_html/blog/sample/lib/Ethna/class/Plugin/Handle
		     [2] => AddTemplate.php
		 )

	     [AddTest] => Array
		 (
		     [0] => Ethna_Plugin_Handle_AddTest
		     [1] => /home/okonomi/public_html/blog/sample/lib/Ethna/class/Plugin/Handle
		     [2] => AddTest.php
		 )

	     [AddView] => Array
		 (
		     [0] => Ethna_Plugin_Handle_AddView
		     [1] => /home/okonomi/public_html/blog/sample/lib/Ethna/class/Plugin/Handle
		     [2] => AddView.php
		 )

	     [AddViewTest] => Array
		 (
		     [0] => Ethna_Plugin_Handle_AddViewTest
		     [1] => /home/okonomi/public_html/blog/sample/lib/Ethna/class/Plugin/Handle
		     [2] => AddViewTest.php
		 )

	     [ClearCache] => Array
		 (
		     [0] => Ethna_Plugin_Handle_ClearCache
		     [1] => /home/okonomi/public_html/blog/sample/lib/Ethna/class/Plugin/Handle
		     [2] => ClearCache.php
		 )

	     [CreatePlugin] => Array
		 (
		     [0] => Ethna_Plugin_Handle_CreatePlugin
		     [1] => /home/okonomi/public_html/blog/sample/lib/Ethna/class/Plugin/Handle
		     [2] => CreatePlugin.php
		 )

	     [I18n] => Array
		 (
		     [0] => Ethna_Plugin_Handle_I18n
		     [1] => /home/okonomi/public_html/blog/sample/lib/Ethna/class/Plugin/Handle
		     [2] => I18n.php
		 )

	     [MakePluginPackage] => Array
		 (
		     [0] => Ethna_Plugin_Handle_MakePluginPackage
		     [1] => /home/okonomi/public_html/blog/sample/lib/Ethna/class/Plugin/Handle
		     [2] => MakePluginPackage.php
		 )

	     [PearLocal] => Array
		 (
		     [0] => Ethna_Plugin_Handle_PearLocal
		     [1] => /home/okonomi/public_html/blog/sample/lib/Ethna/class/Plugin/Handle
		     [2] => PearLocal.php
		 )

	     [Ethna_Plugin_Handle_AddAction] => Array
		 (
		     [0] => Ethna_Plugin_Handle_Ethna_Plugin_Handle_AddAction
		     [1] => /usr/share/php/Ethna/class/Plugin/Handle
		     [2] => Ethna_Plugin_Handle_AddAction.php
		 )

	     [Ethna_Plugin_Handle_AddActionTest] => Array
		 (
		     [0] => Ethna_Plugin_Handle_Ethna_Plugin_Handle_AddActionTest
		     [1] => /usr/share/php/Ethna/class/Plugin/Handle
		     [2] => Ethna_Plugin_Handle_AddActionTest.php
		 )

	     [Ethna_Plugin_Handle_AddAppManager] => Array
		 (
		     [0] => Ethna_Plugin_Handle_Ethna_Plugin_Handle_AddAppManager
		     [1] => /usr/share/php/Ethna/class/Plugin/Handle
		     [2] => Ethna_Plugin_Handle_AddAppManager.php
		 )

	     [Ethna_Plugin_Handle_AddAppObject] => Array
		 (
		     [0] => Ethna_Plugin_Handle_Ethna_Plugin_Handle_AddAppObject
		     [1] => /usr/share/php/Ethna/class/Plugin/Handle
		     [2] => Ethna_Plugin_Handle_AddAppObject.php
		 )

	     [Ethna_Plugin_Handle_AddEntryPoint] => Array
		 (
		     [0] => Ethna_Plugin_Handle_Ethna_Plugin_Handle_AddEntryPoint
		     [1] => /usr/share/php/Ethna/class/Plugin/Handle
		     [2] => Ethna_Plugin_Handle_AddEntryPoint.php
		 )

	     [Ethna_Plugin_Handle_AddProject] => Array
		 (
		     [0] => Ethna_Plugin_Handle_Ethna_Plugin_Handle_AddProject
		     [1] => /usr/share/php/Ethna/class/Plugin/Handle
		     [2] => Ethna_Plugin_Handle_AddProject.php
		 )

	     [Ethna_Plugin_Handle_AddTemplate] => Array
		 (
		     [0] => Ethna_Plugin_Handle_Ethna_Plugin_Handle_AddTemplate
		     [1] => /usr/share/php/Ethna/class/Plugin/Handle
		     [2] => Ethna_Plugin_Handle_AddTemplate.php
		 )

	     [Ethna_Plugin_Handle_AddTest] => Array
		 (
		     [0] => Ethna_Plugin_Handle_Ethna_Plugin_Handle_AddTest
		     [1] => /usr/share/php/Ethna/class/Plugin/Handle
		     [2] => Ethna_Plugin_Handle_AddTest.php
		 )

	     [Ethna_Plugin_Handle_AddView] => Array
		 (
		     [0] => Ethna_Plugin_Handle_Ethna_Plugin_Handle_AddView
		     [1] => /usr/share/php/Ethna/class/Plugin/Handle
		     [2] => Ethna_Plugin_Handle_AddView.php
		 )

	     [Ethna_Plugin_Handle_AddViewTest] => Array
		 (
		     [0] => Ethna_Plugin_Handle_Ethna_Plugin_Handle_AddViewTest
		     [1] => /usr/share/php/Ethna/class/Plugin/Handle
		     [2] => Ethna_Plugin_Handle_AddViewTest.php
		 )

	     [Ethna_Plugin_Handle_ChannelUpdate] => Array
		 (
		     [0] => Ethna_Plugin_Handle_Ethna_Plugin_Handle_ChannelUpdate
		     [1] => /usr/share/php/Ethna/class/Plugin/Handle
		     [2] => Ethna_Plugin_Handle_ChannelUpdate.php
		 )

	     [Ethna_Plugin_Handle_ClearCache] => Array
		 (
		     [0] => Ethna_Plugin_Handle_Ethna_Plugin_Handle_ClearCache
		     [1] => /usr/share/php/Ethna/class/Plugin/Handle
		     [2] => Ethna_Plugin_Handle_ClearCache.php
		 )

	     [Ethna_Plugin_Handle_InfoPlugin] => Array
		 (
		     [0] => Ethna_Plugin_Handle_Ethna_Plugin_Handle_InfoPlugin
		     [1] => /usr/share/php/Ethna/class/Plugin/Handle
		     [2] => Ethna_Plugin_Handle_InfoPlugin.php
		 )

	     [Ethna_Plugin_Handle_InstallPlugin] => Array
		 (
		     [0] => Ethna_Plugin_Handle_Ethna_Plugin_Handle_InstallPlugin
		     [1] => /usr/share/php/Ethna/class/Plugin/Handle
		     [2] => Ethna_Plugin_Handle_InstallPlugin.php
		 )

	     [Ethna_Plugin_Handle_ListPlugin] => Array
		 (
		     [0] => Ethna_Plugin_Handle_Ethna_Plugin_Handle_ListPlugin
		     [1] => /usr/share/php/Ethna/class/Plugin/Handle
		     [2] => Ethna_Plugin_Handle_ListPlugin.php
		 )

	     [Ethna_Plugin_Handle_MakePluginPackage] => Array
		 (
		     [0] => Ethna_Plugin_Handle_Ethna_Plugin_Handle_MakePluginPackage
		     [1] => /usr/share/php/Ethna/class/Plugin/Handle
		     [2] => Ethna_Plugin_Handle_MakePluginPackage.php
		 )

	     [Ethna_Plugin_Handle_PearLocal] => Array
		 (
		     [0] => Ethna_Plugin_Handle_Ethna_Plugin_Handle_PearLocal
		     [1] => /usr/share/php/Ethna/class/Plugin/Handle
		     [2] => Ethna_Plugin_Handle_PearLocal.php
		 )

	     [Ethna_Plugin_Handle_UninstallPlugin] => Array
		 (
		     [0] => Ethna_Plugin_Handle_Ethna_Plugin_Handle_UninstallPlugin
		     [1] => /usr/share/php/Ethna/class/Plugin/Handle
		     [2] => Ethna_Plugin_Handle_UninstallPlugin.php
		 )

	     [Ethna_Plugin_Handle_UpgradePlugin] => Array
		 (
		     [0] => Ethna_Plugin_Handle_Ethna_Plugin_Handle_UpgradePlugin
		     [1] => /usr/share/php/Ethna/class/Plugin/Handle
		     [2] => Ethna_Plugin_Handle_UpgradePlugin.php
		 )

	 )

 )

なんかクラス名とか重複してる?

$src_registryを組み立ててるのはsearchAllPluginSrc()から呼ばれてる_searchPluginSrc()で、引数の$typeと$nameが$src_registryの配列のキーになってる。で、$nameはプラグインのファイル名からつくられてる。

そういえばプラグインのファイル名の命名規則が変更されたんだった。そのあたりに原因が?
命名規則の変更を吸収するようなコードを1行追加してみた。

<?php

    /**
     *  指定された $type のプラグイン (のソース) をすべて検索する
     *
     *  @access public
     *  @param  string  $type   プラグインの種類
     */
    function searchAllPluginSrc($type)
    {
        // 後で見付かったもので上書きするので $this->appid_list の逆順とする
        $name_list = array();
        $ext = $this->ctl->getExt('php');

        foreach($this->_dirlist as $dir) {
            $files = glob($dir . DIRECTORY_SEPARATOR . $type . DIRECTORY_SEPARATOR . "/*." . $ext);
            if (!$files) {
                $this->logger->log(LOG_DEBUG, 'cannot open plugin directory: [%s/%s]', $dir, $type);
                continue;
            }
            $this->logger->log(LOG_DEBUG, 'plugin directory opened: [%s]', $dir);
            foreach ($files as $plugin_file) {
                $plugin_name = substr(basename($plugin_file), 0, - strlen($ext) - 1);
+               $plugin_name = preg_replace("/^[A-Z]+_Plugin_{$type}_(.+)/i", '$1', $plugin_name);
                $name_list[$plugin_name] = 0;
            }
        }

        foreach (array_keys($name_list) as $name) {
            // 冗長だがもう一度探しなおす
            $this->_searchPluginSrc($type, $name);
        }
    }
    // }}}

で、実行。

$ bin/ethna

usage: ethna [option] [command] [args...]

available options are as follows:

  -v, --version    show version and exit

available commands are as follows:

  add-action
  add-action-test
  add-app-manager
  add-app-object
  add-entry-point
  add-project
  add-template
  add-test
  add-view
  add-view-test
  clear-cache
  create-plugin
  help
  i18n
  make-plugin-package
  pear-local

おお、うまくいった!

さて

今回の対応が正しいのかよく分からないですが、起こった問題と対応方法を書いてみました。

こういうのってチケット投げた方がいいのかなー…?

Ethna 2.5.0 preview5とEthna_Plugin_Debugtoolbar 0.9.0を試してみたの続き

昨日の日記の続き。

Ethna_Plugin_Debugtoolbar 0.9.1がリリースされたので、id:sotarokさんの仕事のはやさにビビりつつ早速アップデートしてみました。
Ethna_Plugin_Debugtoolbar 0.9.1 をリリース - 肉とご飯と甘いもの @ sotarok

まずはパッケージを更新。

$ bin/ethna pear-local upgrade openpear/Ethna_Plugin_Debugtoolbar

ひとまずここでプロジェクトのページにアクセスしてみる。うん、問題なく動く。

次にビュークラスを修正。$hasDefaultHeaderの定義が要らなくなったので削除。

最後はテンプレート。smarty_debug.tplがパッケージに同梱されたので、手動で配置したものと入れ替え。

$ cd template
$ rm smarty_debug.tpl
$ ln -s ../lib/.pear/data/Ethna_Plugin_Debugtoolbar/data/lib/smarty_debug.tpl ./

これでアップデート完了!

というわけで

Ethna_Plugin_Debugtoolbarはかなりよさげです。特にすばらしいのがログの表示で、ログレベルによって色分けされるので問題の発生に気付きやすいし、ファイルにログを書き出すよりも格段に見やすいです。

Ethna 2.5.0といっしょにはやく実戦投入したいです!

Ethna 2.5.0 preview5とEthna_Plugin_Debugtoolbar 0.9.0を試してみた

ちょっとまえにEthna_Plugin_Debugtoolbarがリリースされたということで、いい機会なのでEthna 2.5.0 preview5といっしょに試してみた。

Ethna 2.5.0 preview5をインストールするにあたって、すでにインストールしてるEthna 2.3.7を上書きしたくないので、次のような手順でやってみた。

  1. Ethna 2.3.7でダミープロジェクトを作成
  2. ダミープロジェクトにEthna 2.5.0 preview5をインストール
  3. ダミープロジェクト内のEthna 2.5.0 preview5で改めてプロジェクトを作成
  4. プロジェクトにEthna 2.5.0 preview5をインストール
$ ethna add-project dummy
$ cd dummy
$ ethna pear-local channel-discover ethna.jp
$ ethna pear-local install http://pear.ethna.jp/pear/Ethna-2.5.2009062201.tgz
$ cd ..
$ dummy/bin/ethna add-project sample
$ cd sample
$ ../dummy/bin/ethna pear-local channel-discover ethna.jp
$ ../dummy/bin/ethna pear-local install -a http://pear.ethna.jp/pear/Ethna-2.5.2009062201.tgz

これでEthna 2.5.0 preview5によって作られたプロジェクトにEthna 2.5.0 preview5がインストールされた。

このプロジェクトにEthna_Plugin_Debugtoolbarをインストール。

$ bin/ethna pear-local channel-discover openpear.org
$ bin/ethna pear-local install openpaer/Ethna_Plugin_Debugtoolbar
$ cd www
$ ln -s ../lib/.pear/data/Ethna_Plugin_Debugtoolbar/data/www ./Debugtoolbar

このプロジェクトにブラウザでアクセスするとdebugtoolbarが表示…はまだされない。

Ethna_Plugin_Debugtoolbar 0.9.0 をリリース - 肉とご飯と甘いもの @ sotarokのHow to useのとおりにapp/Sample_Controller.phpとetc/sample-ini.phpを修正する。

再度ブラウザでアクセス。…まだdebugtoolbarは表示されない。

lib/Ethna/extlib/Plugin/Filter/Debugtoolbar.phpを見てみると

<?php
    function postFilter()
    {
        if (!$this->ctl->view->hasDefaultHeader) {
            return null;
        }

        $this->init();
        $this->dumpInfo();
        $this->dumpConfig();
        $this->dumpActionForm();
        $this->smartyDebug();

    }

ていうコードがあるので、ビュークラスに$hasDefaultHeaderの定義を追加してみる。

<?php
class Sample_View_Index extends Sample_ViewClass
{
    var $hasDefaultHeader = true;

ページの下になんかでた!どうやらdebugtoolbarが素のまま出てるみたい。

出力されたページ内容でここが気になる。

<link rel="stylesheet" href="/Debugtoolbar/css/ether.css" type="text/css" />

CSSのパスがおかしい?

これを出力してるのはlib/Ethna/extlib/Plugin/Filter/Debugtoolbar.phpのこのへん

<?php
    function init()
    {
        $url = $this->config->get('url');
        if (substr($url, -1) != '/') {
            $url .= '/';
        }
        // jquery がロードされてるかどうか調べる
        // なければ google.load
        // めんどくせー常にloadでいい?

        echo <<<EOL
<link rel="stylesheet" href="{$url}Debugtoolbar/css/ether.css" type="text/css" />
<script type="text/javascript" src="http://www.google.com/jsapi"></script>
<script type="text/javascript">
    google.load("jquery", "1.2");
</script>
<script type="text/javascript" src="{$url}Debugtoolbar/js/jquery.cookie.js"></script>
EOL;

etc/sample-ini.phpの'url'は空っぽなので、ここの$urlは「/」になってしまう。

etc/sample-ini.phpを修正。

$config = array(
    // site
    'url' => './',

こうすればCSSのパスは「./Debugtoolbar/css/ether.css」になって正しくなるはず。

再度ページにアクセス。ページの上にdebugtoolbarが表示された!

でもエラーログが出てる。

Sample[3194](WARNING): Smarty._fetch_resource_info(/lib/Smarty/Smarty.class.php:1415): [PHP] E_USER_WARNING: Smarty error: unable to read resource: "/home/okonomi/public_html/blog/sample/template/smarty_debug.tpl" in /home/okonomi/public_html/blog/sample/lib/Smarty/Smarty.class.php on line 1093

smarty_debug.tplが見つからないらしい。でもそんなファイルはEthna_Plugin_Debugtoolbarのインストールディレクトリにもopenpearリポジトリにもない…。ググったらnequalさんとこのリポジトリがでてきたのでそこからダウンロードさせてもらった。

$  wget -O template/smarty_debug.tpl http://svn.nequal.jp/public/library/Ether_Suite/trunk/template/smarty_debug.tpl

するとエラーログもなくなってちゃんと動作するようになった!

rhaco2でhello, world

なにやらrhacoがおもしろそう。ちょっと試してみた。無謀にも絶賛開発中のrhaco2で!

実行環境の確認。

$ uname -a
Linux thinkpad 2.6.28-11-generic #42-Ubuntu SMP Fri Apr 17 01:57:59 UTC 2009 i686 GNU/Linux

$ apache2 -V
Server version: Apache/2.2.11 (Ubuntu)
Server built:   Apr  1 2009 16:03:38
Server's Module Magic Number: 20051115:21
Server loaded:  APR 1.2.12, APR-Util 1.2.12
Compiled using: APR 1.2.12, APR-Util 1.2.12
Architecture:   32-bit
Server MPM:     Prefork
  threaded:     no
    forked:     yes (variable process count)
Server compiled with....
 -D APACHE_MPM_DIR="server/mpm/prefork"
 -D APR_HAS_SENDFILE
 -D APR_HAS_MMAP
 -D APR_HAVE_IPV6 (IPv4-mapped addresses enabled)
 -D APR_USE_SYSVSEM_SERIALIZE
 -D APR_USE_PTHREAD_SERIALIZE
 -D SINGLE_LISTEN_UNSERIALIZED_ACCEPT
 -D APR_HAS_OTHER_CHILD
 -D AP_HAVE_RELIABLE_PIPED_LOGS
 -D DYNAMIC_MODULE_LIMIT=128
 -D HTTPD_ROOT=""
 -D SUEXEC_BIN="/usr/lib/apache2/suexec"
 -D DEFAULT_PIDLOG="/var/run/apache2.pid"
 -D DEFAULT_SCOREBOARD="logs/apache_runtime_status"
 -D DEFAULT_LOCKFILE="/var/run/apache2/accept.lock"
 -D DEFAULT_ERRORLOG="logs/error_log"
 -D AP_TYPES_CONFIG_FILE="/etc/apache2/mime.types"
 -D SERVER_CONFIG_FILE="/etc/apache2/apache2.conf"

$ php --version
PHP 5.2.6-3ubuntu4.1 with Suhosin-Patch 0.9.6.2 (cli) (built: Apr 23 2009 14:35:05) 
Copyright (c) 1997-2008 The PHP Group
Zend Engine v2.2.0, Copyright (c) 1998-2008 Zend Technologies
    with Xdebug v2.0.4, Copyright (c) 2002-2008, by Derick Rethans

まずはrhacoリポジトリから取得。

$ svn co https://rhaco.svn.sourceforge.net/svnroot/rhaco/trunk/ rhaco2
$ svn info rhaco2
パス: rhaco2
URL: https://rhaco.svn.sourceforge.net/svnroot/rhaco/trunk
リポジトリのルート: https://rhaco.svn.sourceforge.net/svnroot/rhaco
リポジトリ UUID: 838d834a-3123-0410-b9a5-8bcc0e2cc032
リビジョン: 4627
ノード種別: ディレクトリ
準備中の処理: 特になし
最終変更者: rhaco
最終変更リビジョン: 4627
最終変更日時: 2009-05-29 17:40:15 +0900 (金, 29  5月 2009)

r4627だった。

__settings__.phpを作成。

<?php
require_once dirname(__FILE__).'/rhaco2/rhaco2';
application_settings(__FILE__, 'http://localhost/');

index.phpを作成。

<?php
require_once '__settings__.php';
$flow = new Flow();
$flow->output('index.html');

templates/index.htmlを作成。

hello, rhaco2.

http://localhost/にアクセスすると「hello, rhaco2.」と表示される。
ひとまず今日はここまで!

ためしたいこと

  • DBアクセス(O/Rマッパ)
  • 入力・確認・完了的なこと

いままで触ったフレームワークと違った感触でおもしろい、かも。

Net_UserAgent_Mobile_UserIDをリリースしました

携帯電話の固有IDを扱うライブラリをopenpearでリリースしました。

Net_UserAgent_Mobile_UserID \ Package \ Openpear

先日公開された「携帯各キャリアの固有IDについて (全キャリア対応) - ぱらめでぃうす」がすばらしかったので、このまとめをもとにPEARパッケージをでっちあげてみました。

基本的な使い方はこんな感じです。

<?php
require_once 'Net/UserAgent/Mobile/UserID.php';

try {
    $userid = Net_UserAgent_Mobile_UserID::factory();
} catch (Net_UserAgent_Mobile_UserID_Exception $e) {
    var_dump($e->getMessage());
    exit;
}

// ID本体
echo $userid->getID();
// 接頭文字
echo $userid->getPrefix();
// 解析前のデータ
echo $userid->getRawData();

以下のような値が取得されます。

DoCoMo
種類 接頭文字 ID本体
端末製造番号 ser 英数11桁or15桁
FOMAカード製造番号 icc 英数20桁
iモードID なし 英数7桁
EZweb
種類 接頭文字 ID本体
EZ番号 なし 英数14桁+_+英数2桁+.ezweb.ne.jp
SoftBank
種類 接頭文字 ID本体
UID なし 英数16桁
端末シリアル番号 SN 英数11桁or15桁
e-mobile
種類 接頭文字 ID本体
UID u 英数17桁

先頭の固定文字を接頭文字としてID本体と分離してますが、余計なお世話かもしれません..。

ほかにユーザーIDの判定方法を差し替えたりなど。

<?php
require_once 'Net/UserAgent/Mobile/UserID.php';
require_once 'Net/UserAgent/Mobile/UserID/Abstract.php';

// ?id=aa-12345みたいなの
class Hoge extends Net_UserAgent_Mobile_UserID_Abstract
{
    public function getID()
    {
        if (array_key_exists('id', $_REQUEST)) {
            return $_REQUEST['id'];
        } else {
            return null;
        }
    }

    public function validateID($id)
    {
        return preg_match('/^[a-z}{2}\-[0-9]{5}$/', $id);
    }

    protected function _parseID($id)
    {
        return array(
            substr($id, 0, 2),
            substr($id, 3),
        );
    }
}

Net_UserAgent_Mobile_UserID::setUserIDModules('DoCoMo', new Hoge());

バリデーションだけ単体で使うこともできます。

<?php
require_once 'Net/UserAgent/Mobile/DoCoMo/ImodeID.php';

if (Net_UserAgent_Mobile_ImodeID::validateID('xxxxxxx')) {
    echo "ok";
} else {
    echo "ng";
}

Net_UserAgent_MobileでもユーザーIDは取得できるんですが、インターフェースが統一されてたらうれしいかなーとか、NDAな処理は呼出し側で追加できたらいいなーとか、そんな感じで作ってみました。

プロジェクトを作ったらまずやること

Ethna 2.3.6で

$ ethna add-project sample

した後。

ローカルと本番サーバで配置するパスが違ったり、本番サーバに勝手にPEARパッケージをインストールすると他のプロジェクトに影響したりするかもしれないので、

  • プロジェクトの外のファイルに依存しない
  • 絶対パスは排除

な方向で。

ディレクトリ構成をいじる

$ mv sample project
$ mkdir sample
$ mv project sample/
$ mv sample/project/www/* sample
$ rm -r sample/project/www

こうなる。

.
`-- sample
    |-- css
    |-- js
    `-- project
        |-- app
        |   |-- action
        |   |-- action_cli
        |   |-- action_xmlrpc
        |   |-- filter
        |   |-- plugin
        |   |   |-- Filter
        |   |   `-- Validator
        |   |-- test
        |   `-- view
        |-- bin
        |-- etc
        |-- lib
        |-- locale
        |   `-- ja
        |       `-- LC_MESSAGES
        |-- log
        |-- schema
        |-- skel
        |-- template
        |   `-- ja
        `-- tmp

エントリポイントのrequire_onceのパスをいじる。

sample/index.php

<?php
require_once dirname(__FILE__).'/project/app/Sample_Controller.php'; // ここ

Sample_Controller::main('Sample_Controller', 'index');
?>

sample/project以下をアクセス禁止にする

sample/project/.htaccess

order deny,allow
deny from all

pear-localの設定

$ cd sample/project
$ ethna pear-local

プロジェクト内にPEARの設定ファイルが作成される。「ethna pear-local config-show 」して、フルパスになってるものを相対パスに変更、あとdownload_dirをlibからtmpに変更。

$ ethna pear-local config-set bin_dir bin
$ ethna pear-local config-set doc_dir lib/.pear/doc
$ ethna pear-local config-set ext_dir lib/.pear/ext
$ ethna pear-local config-set php_dir lib
$ ethna pear-local config-set cache_dir tmp/.pear/cache
$ ethna pear-local config-set data_dir lib/.pear/data
$ ethna pear-local config-set download_dir tmp/.pear/download
$ ethna pear-local config-set temp_dir tmp/.pear/temp
$ ethna pear-local config-set test_dir lib/.pear/test

必要なPEARパッケージをインストール

$ ethna pear-local channel-update pear.php.net
Updating channel "pear.php.net"
Update of Channel "pear.php.net" succeeded

$ ethna pear-local channel-discover pear.ethna.jp
Adding Channel "pear.ethna.jp" succeeded
Discovery of channel "pear.ethna.jp" succeeded

$ ethna pear-local install -a pear
downloading PEAR-1.7.2.tgz ...
Starting to download PEAR-1.7.2.tgz (302,744 bytes)
.....................done: 302,744 bytes
downloading Archive_Tar-1.3.2.tgz ...
Starting to download Archive_Tar-1.3.2.tgz (17,150 bytes)
...done: 17,150 bytes
downloading Structures_Graph-1.0.2.tgz ...
Starting to download Structures_Graph-1.0.2.tgz (30,947 bytes)
...done: 30,947 bytes
downloading Console_Getopt-1.2.3.tgz ...
Starting to download Console_Getopt-1.2.3.tgz (4,011 bytes)
...done: 4,011 bytes
downloading XML_RPC-1.5.1.tgz ...
Starting to download XML_RPC-1.5.1.tgz (32,215 bytes)
...done: 32,215 bytes
install ok: channel://pear.php.net/Archive_Tar-1.3.2
install ok: channel://pear.php.net/Structures_Graph-1.0.2
install ok: channel://pear.php.net/Console_Getopt-1.2.3
install ok: channel://pear.php.net/XML_RPC-1.5.1
install ok: channel://pear.php.net/PEAR-1.7.2
PEAR: Optional feature webinstaller available (PEAR's web-based installer)
PEAR: Optional feature gtkinstaller available (PEAR's PHP-GTK-based installer)
PEAR: Optional feature gtk2installer available (PEAR's PHP-GTK2-based installer)
PEAR: To install optional features use "pear install pear/PEAR#featurename"

$ ethna pear-local install -a ethna/ethna
WARNING: "pear/DB" is deprecated in favor of "pear/MDB2"
downloading Ethna-2.3.6.tgz ...
Starting to download Ethna-2.3.6.tgz (183,262 bytes)
......................................done: 183,262 bytes
downloading DB-1.7.13.tgz ...
Starting to download DB-1.7.13.tgz (132,246 bytes)
...done: 132,246 bytes
downloading Smarty-2.6.22.tgz ...
Starting to download Smarty-2.6.22.tgz (69,414 bytes)
...done: 69,414 bytes
downloading simpletest-1.0.1.tgz ...
Starting to download simpletest-1.0.1.tgz (273,378 bytes)
...done: 273,378 bytes
install ok: channel://pear.ethna.jp/Ethna-2.3.6
install ok: channel://pear.php.net/DB-1.7.13
install ok: channel://pear.ethna.jp/Smarty-2.6.22
install ok: channel://pear.ethna.jp/simpletest-1.0.1

コントローラのインクルードパス設定をいじる

sample/project/app/Sample_Controller.php

<?php

$app = BASE . "/app";
$lib = BASE . "/lib";
//ini_set('include_path', implode(PATH_SEPARATOR, array($app, $lib)) . PATH_SEPARATOR . ini_get('include_path'));
ini_set('include_path', implode(PATH_SEPARATOR, array($app, $lib)));

プロジェクト内のファイルだけ参照するようにする。
できあがり。

動作確認

projectディレクトリに移動して

$ bin/ethna -v

して動けばOK。今後はこのethnaコマンドを使う。

GUIでHello, World

いろんなGUIライブラリで、「Hello, World」ていうタイトルのウィンドウを表示するサンプル。
環境はVista/VC2008Express。

GTK+

#include <gtk/gtk.h>
#include <windows.h>

int main(int argc, char *argv[])
{
	gtk_init(&argc, &argv);

	GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
	gtk_window_set_title((GtkWindow *)window, "Hello, World");

	g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);

	gtk_widget_show_all(window);
	gtk_main();

	return 0;
}

gtkmm

#include <gtkmm.h>
#include <windows.h>

int main(int argc, char *argv[])
{
	Gtk::Main kit(argc, argv);

	Gtk::Window window;
	window.set_title("Hello, World");
	Gtk::Main::run(window);

	return 0;
}

Qt

#include <QtGui/QApplication>
#include <QtGui/QWidget>
#include <windows.h>

int main(int argc, char *argv[])
{
	QApplication app(argc, argv);

	QWidget window;
	window.setWindowTitle("Hello, World");
	window.show();

	return app.exec();
}

wxWidgets

#include <wx/wx.h>

class MyApp: public wxApp
{
	virtual bool OnInit()
	{
		wxFrame *frame = new wxFrame(NULL, -1, wxT("Hello, World"));
		frame->Show();
		SetTopWindow(frame);

		return true;
	}
};

IMPLEMENT_APP(MyApp);

WTL

#include <atlbase.h>
#include <atlapp.h>
CAppModule _Module;
#include <atlwin.h>

class CMyWindow: public CWindowImpl<CMyWindow>
{
public:
	DECLARE_WND_CLASS(_T("Hello"));

private:
	BEGIN_MSG_MAP(CMyWindow)
		MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
	END_MSG_MAP()

	LRESULT OnDestroy(UINT, WPARAM, LPARAM, BOOL &)
	{
		PostQuitMessage(0);
		return 0;
	}
};

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	_Module.Init(NULL, hInstance);

	CMessageLoop loop;
	_Module.AddMessageLoop(&loop);

	CMyWindow window;
	window.Create(NULL, CWindow::rcDefault, _T("Hello, World"), WS_OVERLAPPEDWINDOW | WS_VISIBLE);

	int ret = loop.Run();
	
	_Module.RemoveMessageLoop();
	_Module.Term();

	return ret;
}

ほかにeGUIを試そうとしたけど、リソースエディタ必須みたいな感じだったのでサンプルだけ動かして挫折。