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

おお、うまくいった!

さて

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

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