ログ日記

作業ログと日記とメモ

PHPでTracのようなプラグインの仕組みを作る

Pythonを知らずにTracを触ってたので気付かなかったけど、Tracのソースに書いてあるInterfaceとかimplementsっていう仕組みはTrac独自なんだね。
だから自由にInterfaceの挙動を操作できる。


Interfaceが組み込みでも、リフレクションを使えば似たものが作れる。
任意のクラスでInterfaceを実装すればプラグインとして扱われるという仕組みを考える。


リフレクションを使ってインターフェースを取得するPluginクラス。

<?php
// Plugin.php
class Plugin
{
    /** @var array array('InterfaceName' => array(plugins)) */
    private $plugins = array();

    /**
     * iniファイルを解析してプラグインを登録する。
     *
     * Pluginで利用するコンポーネントからPluginを呼び出せるように
     * コンストラクタと登録を分離している。
     *
     * @param string $IniFile
     */
    public function parseIni($iniFile)
    {
        $iniFile = dirname(__FILE__) . '/' . $iniFile;
        $plugins = parse_ini_file($iniFile);

        foreach ($plugins as $className => $value){
            if (!$value)
                continue;

            $this->add($className, $value);
        }
    }

    /**
     * クラス名からプラグインを登録する
     *
     * @param string $className
     * @param int $priority = 100 プラグイン同士の優先順位
     */
    public function add($className, $priority = 100)
    {
        $obj = new $className();
        $ref = new ReflectionClass($obj);
        $interfaces = $ref->getInterfaces();

        if (!is_array($interfaces))
            continue;
        foreach ($interfaces as $interface)
            $this->plugins[$interface->name][$priority] = $obj;

        // 設定ファイルの優先度でソート
        foreach ($this->plugins as $k => &$v)
            ksort($v, SORT_NUMERIC);
    }

    /**
     * インターフェース名を受け取り、それを実装しているオブジェクトの配列を返却する。
     *
     * @param string $interface
     * @return array
     */
    public function gets($interface)
    {
        if (isset($this->plugins[$interface]))
            return $this->plugins[$interface];
        else
            return array();
    }
}

例えばメニューをプラグインで表示する場合

メニューのインターフェースを作る。

<?php
// Menu.php
interface Menu
{
    public function getMenuUrl();
    public function getMenuText();
}


インターフェースを実装したクラスをいくつか作る。

<?php
// HogeMenu.php
class HogeMenu implements Menu
{
    public function getMenuUrl(){
        return 'hoge';
    }
    public function getMenuText(){
        return 'ほげ';
    }
}
<?php
// FugaMenu.php
class FugaMenu implements Menu
{
    public function getMenuUrl(){
        return 'fuga';
    }

    public function getMenuText(){
        return 'ふが';
    }
}


プラグインを読み込むための設定ファイルを作る。

; Plugin.ini
HogeMenu = 2
FugaMenu = 1

最後に呼び出し元のPHPファイル。

<?php
// index.php
/**
 * require_once $className.php
 *
 * @param string $name
 */
function autoload($name)
{
    $file = $name . '.php';
    if (file_exists($file))
        require_once $file;
}
spl_autoload_register('autoload');

function getMenus(){
    $plugin = new Plugin();
    $plugin->parseIni('Plugin.ini');

    $menus = $plugin->gets('Menu');
    $result = array();
    foreach ($menus as $menu)
        $result[$menu->getMenuUrl()] = $menu->getMenuText();
    return $result;
}
?>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>View Menu by Plugins</title>
</head>
<body>

<ul>
    <?php foreach (getMenus() as $url => $text){
             echo <<<END
    <li><a href="$url">$text</a></li>
END;
          }
    ?>
</ul>
</body>
</html>