Ethna_Renderer_Php.phpを作ってみた

CakePHPも、昨日勉強会に行ってきたsymfonyも、Viewはphpを素で使う形式だ。
一方、EthnaはデフォルトでSmartyを使用し、Rendererを拡張することで、他のテンプレートエンジンを使うことも出来るようになっている。


ということで、EthnaでViewのテンプレートエンジンの代わりに素のphpを使用するRenderer拡張を作ってみました。
元にしたのは、Ethna 2.3.2です。


で、コードが長いので、その前にまとめを書きます。

先にまとめ

phpのコードがhtmlにまざるのが気持ち悪いです。
Smartyでなくてもいいが、なんらかのテンプレートエンジンを使いたいと、個人的には思いました。
それと、Smartyの froeachelse とか、変数の修飾子のdefaultとかは便利で、無いとちょっとめんどくさい。
http://www.smarty.net/manual/ja/language.function.foreach.php
http://www.smarty.net/manual/ja/language.modifier.default.php


他のフレームワークがどうかはわかりませんが、たぶん、うちが採用するとしたら、使い捨てになる短期のバッチやページ以外は、テンプレートエンジンを使うだろうと思います。

ついでに

info.tplを、これで動くように修正していて、いくつか気づいたところがありました。
まず、Controllerの var $ext にて、tplの拡張子をphpにしたとしても、info.tplが呼ばれているようです。


また、2.3.2のinfo.tplにhtmlのタグミスがありました。
ライン91,96で iタグの閉じがliになってます。
「<i>(未定義)</li>」ってなってる。

<tr>
    <td class="e">アクションクラス</td>
    <td class="v" colspan="2">
        {$action.action_class}{if $action.action_class_info.undef}<i>(未定義)</li>{/if}
    </td>
</tr>
<tr>
    <td class="e">アクションフォーム</td>
    <td class="v" colspan="2">{$action.action_form|default:"<i>(未定義)</i>"}{if $action.action_form_info.undef}<i>(未定義)</li>{/if}</td>
</tr>


きっと、次期リリース版では修正されていると思うので、今度、開発中のバージョンとか落として見てみようと思います。どこで落とすんだろう?

使い方

で、肝心の使い方です。
Ethna_Renderer_Php.phpは、libの下か、Ethna/class/Renderer/ あたりに置いてください。
app以下の、{appId}_Controller.phpの中で使用するクラス定義をします。


142行目あたり

    /**
     *  @var    array   クラス定義
     */
    var $class = array(
        /*
         *  TODO: 設定クラス、ログクラス、SQLクラスをオーバーライド
         *  した場合は下記のクラス名を忘れずに変更してください
         */
        'class'         => 'Ethna_ClassFactory',
       (略)
        'view'          => 'Mm_ViewClass',
        'renderer'      => 'Ethna_Renderer_Php',  // <-ここを変更
        'url_handler'   => 'Mm_UrlHandler',


テンプレートを .phpの拡張子にする場合には、もう一箇所変更します。
119行目あたり

    /**
     *  @var    array       拡張子設定
     */
    var $ext = array(
        'php'           => 'php',
        'tpl'           => 'php', // <-ここを変更
    );


テンプレート内では、app、app_ne、formは普通に変数としてアクセスできます。
それぞれ、配列になっているので、そのままアクセスします。
例)

<?php echo $app['name'] ?>
<?php echo $app_ne['name'] ?>
<?php echo $form['name'] ?>


フォーム関連は、例によって未作成。


実際に、これを使ってみた例の一つとして、info.tplphpに書き直してみました。
foreachelseのところとか実装が変かもしれませんが、とりあえず。


以下、実際のファイルです。

Ethna_Renderer_Php.php
<?php
// vim: foldmethod=marker
/**
 *  Ethna_Renderer_Php.php
 *
 *  @author     maru_cc <tomoyuki [dot] maruta [at] gmail [dot] com>
 *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
 *  @package    Ethna
 *  @version    $Id:  $
 */

// {{{ Ethna_Renderer_Php
/**
 *  Phpレンダラクラスって(笑
 *
 *  @author     Kazuhiro Hosoi <hosoi@gree.co.jp>
 *  @access     public
 *  @package    Ethna
 */
class Ethna_Renderer_Php extends Ethna_Renderer
{
    /** @var    string compile directory  */
    var $compile_dir;

    /** @var    array template data */
    var $_data=array();

    /**
     *  Ethna_Renderer_Phpクラスのコンストラクタ
     *
     *  @access public
     */
    function Ethna_Renderer_Php(&$controller)
    {
        parent::Ethna_Renderer($controller);

        $template_dir = $controller->getTemplatedir();

        $this->setTemplateDir($template_dir);

        ///TODO

        $this->_setDefaultPlugin();
    }

    /**
     *  デフォルトの設定.
     *
     *  @access public
     */
    function _setDefaultPlugin()
    {
        ///TODO
    }

    /**
     *  ビューを出力する
     *
     *  @param  string  $template   テンプレート名
     *  @param  bool    $capture    true ならば出力を表示せずに返す
     *
     *  @access public
     */
    function perform($template = null, $capture = false)
    {
        if ($template === null && $this->template === null) {
            return Ethna::raiseWarning('template is not defined');
        }

        if ($template !== null) {
            $this->template = $template;
        }

        $config = $this->controller->config->config;
        $app = $this->_data['app'];
        $app_ne = $this->_data['app_ne'];
        $form = $this->_data['form'];
        $data = $this->_data;

        if ((is_absolute_path($this->template) && is_readable($this->template))
                || is_readable($this->template_dir . $this->template)) {

            $template_path = $this->template;
            if (!is_file($template_path)) {
                $template_path = $this->template_dir . '/' . $this->template;
            }
            if ($capture === true) {
                ob_start();
                include $template_path;
                $captured = ob_get_contents();
                return $captured;
            } else {
                include $template_path;
            }
        } else {
            return Ethna::raiseWarning('template not found ' . $this->template);
        }
    }

    /**
     * テンプレート変数を取得する
     *
     *  @param string $name  変数名
     *
     *  @return mixed 変数
     *
     *  @access public
     */
    function &getProp($name = null)
    {
        if (is_set($this->_data[$name])) {
            return $this->_data[$name];
        }
        return null;
    }

    /**
     *  テンプレート変数を削除する
     *
     *  @param name    変数名
     *
     *  @access public
     */
    function removeProp($name)
    {
      unset($this->_data[$name]);
    }

    /**
     *  テンプレート変数に配列を割り当てる
     *
     *  @param array $array
     *
     *  @access public
     */
    function setPropArray($array)
    {
        if (is_array($array)) {
            foreach ($array as $key=>$val) {
                $this->_data[$key] = $val;
            }
        }
    }

    /**
     *  テンプレート変数に配列を参照として割り当てる
     *
     *  @param array $array
     *
     *  @access public
     */
    function setPropArrayByRef(&$array)
    {
        if (is_array($array)) {
            foreach ($array as $key=>$val) {
                $this->_data[$key] =& $array[$key];
            }
        }
    }

    /**
     *  テンプレート変数を割り当てる
     *
     *  @param string $name 変数名
     *  @param mixed $value 値
     *
     *  @access public
     */
    function setProp($name, $value)
    {
        $this->_data[$name] = $value;
    }

    /**
     *  テンプレート変数に参照を割り当てる
     *
     *  @param string $name 変数名
     *  @param mixed $value 値
     *
     *  @access public
     */
    function setPropByRef($name, &$value)
    {
        $this->_data[$name] =& $value;
    }

    /**
     *  プラグインをセットする
     *
     *  @param string $name プラグイン名
     *  @param string $type プラグインタイプ
     *  @param mixed $plugin プラグイン本体
     *
     *  @access public
     */
    function setPlugin($name, $type, $plugin)
    {
        ///TODO
    }
}
// }}}
?>
Ethna/tpl/info.tpl
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "DTD/xhtml1-transitional.dtd">
<html>
    <head>
        <meta http-equiv="content-type" content="text/html;charset=EUC-JP" />
        <style type="text/css">
        <!--
            body {
                margin: auto;
                background-color: #ffffff;
                color: #000000;
            }
            body, td, th, h1, h2 {font-family: sans-serif;}
            pre {margin: 0px; font-family: monospace;}
            a:link {color: #000099; text-decoration: none; background-color: #ffffff;}
            a:hover {text-decoration: underline;}
            table { margin: auto; border-collapse: collapse;}
            .center {text-align: center;}
            .center table { text-align: left;}
            .center th { text-align: center !important; }
            td, th { border: 1px solid #000000; font-size: 75%; vertical-align: top;}
            h1 {font-size: 150%;}
            h2 {font-size: 125%;}
            .p {text-align: left;}
            .e {background-color: #ccccff; font-weight: bold; color: #000000;}
            .h {background-color: #9999cc; font-weight: bold; color: #000000;}
            .v {background-color: #cccccc; color: #000000;}
            i {color: #666666; background-color: #cccccc;}
            img {float: right; border: 0px;}
            hr {width: 600px; background-color: #cccccc; border: 0px; height: 1px; color: #000000;}
            .header {
                margin: auto;
                width:600px;
                background-color: #9999cc;
                padding:0.2em;
                border:solid 1px black;
            }
        //-->
        </style>
        <title><?php echo $app['app_id'] ?> - Ethna Info</title>
    </head>
    <body>
        <div class="center">
            <div class="header">
                <h1><?php echo $app['app_id'] ?></h1>
            </div>
            <br />
            <hr />
            Contents
            <hr />
            <table border="0" cellpadding="3" width="600">
                <tr>
                    <td style="border:0px">
                        <ul>
                            <li><a href="#actions">Actions</a></li>
                                <ol>
                                <?php foreach ($app['action_list'] as $action_name=>$action): ?>
                                    <li><a href="#action_<?php echo $action_name ?>"><?php echo $action_name?></a></li>
                                <?php endforeach; ?>
                                </ol>
                            <li><a href="#forwards">Forwards</a></li>
                                <ol>
                                <?php foreach ($app['forward_list'] as $forward_name=>$forward): ?>
                                    <li><a href="#action_<?php echo $forward_name ?>"><?php echo $forward_name?></a></li>
                                <?php endforeach; ?>
                                </ol>
                            <li><a href="#configuration">Configuration</a></li>
                            <li><a href="#plugins">Plugins</a></li>
                                <ol>
                                <?php foreach ($app['plugin_list'] as $plugin_type=>$plugin): ?>
                                    <li><a href="#action_<?php echo $plugin_type ?>"><?php echo $plugin_type?></a></li>
                                <?php endforeach; ?>
                                </ol>
                        </ul>
                    </td>
                </tr>
            </table>
            <br />
            <a name="actions"></a>
            <h1>Actions</h1>

            <table border="0" cellpadding="3" width="600">
                <?php foreach ($app['action_list'] as $action_name=>$action): ?>
                <tr class="h">
                    <th colspan="3"><a name="action_<?php echo $action_name ?>"></a><?php echo $action_name ?></th>
                </tr>
                <tr>
                    <td class="e">アクションクラス</td>
                    <td class="v" colspan="2">
                        <?php echo $action['action_class'] ?><?php if ($action['action_class_info']['undef']): ?><i>(未定義)</i><?php endif; ?>
                    </td>
                </tr>
                <tr>
                    <td class="e">アクションフォーム</td>
                    <td class="v" colspan="2"><?php echo isset($action['action_form'])?$action['action_form']:'<i>(未定義)</i>' ?><?php if ($action['action_form_info']['undef']): ?><i>(未定義)</i><?php endif; ?></td>
                </tr>
                <tr>
                    <?php if (count($action['action_form_info']['form']) == 0): ?>
                    <td class="e">フォーム値</td>
                    <?php else: ?>
                    <td class="e" rowspan="<?php echo count($action['action_form_info']['form']) ?>">フォーム値</td>
                    <?php endif; ?>
                    <?php $cnt=0; $loopcnt=count($action['action_form_info']['form']); if ($loopcnt): foreach ($action['action_form_info']['form'] as $form_name=>$form): $cnt++; ?>
                        <?php if ($cnt!=1): ?><tr><?php endif; ?>
                        <td class="v"><?php echo $form_name ?></td>
                        <td class="v">
                            <table cellpadding="1">
                                <tr>
                                    <td style="border:0px; font-size:100%;">表示名</td>
                                    <td style="border:0px; font-size:100%;"><?php echo isset($form['name'])?$form['name']:"<i>未定義</i>" ?></td>
                                </tr>
                                <tr>
                                    <td style="border:0px; font-size:100%;">必須</td>
                                    <td style="border:0px; font-size:100%;"><?php echo isset($form['required'])?$form['required']:"false" ?></td>
                                </tr>
                                <tr>
                                    <td style="border:0px; font-size:100%;">最大値</td>
                                    <td style="border:0px; font-size:100%;"><?php echo isset($form['max'])?$form['max']:"<i>未定義</i>" ?></td>
                                </tr>
                                <tr>
                                    <td style="border:0px; font-size:100%;">最小値</td>
                                    <td style="border:0px; font-size:100%;"><?php echo isset($form['min'])?$form['min']:"<i>未定義</i>" ?></td>
                                </tr>
                                <tr>
                                    <td style="border:0px; font-size:100%;">正規表現</td>
                                    <td style="border:0px; font-size:100%;"><?php echo isset($form['regexp'])?$form['regexp']:"<i>未定義</i>" ?></td>
                                </tr>
                                <tr>
                                    <td style="border:0px; font-size:100%;">チェックメソッド</td>
                                    <td style="border:0px; font-size:100%;"><?php echo isset($form['custom'])?$form['custom']:"<i>未定義</i>" ?></td>
                                </tr>
                                <tr>
                                    <td style="border:0px; font-size:100%;">変換フィルタ</td>
                                    <td style="border:0px; font-size:100%;"><?php echo isset($form['filter'])?$form['filter']:"<i>未定義</i>"|nl2br ?></td>
                                </tr>
                                <tr>
                                    <td style="border:0px; font-size:100%;">フォームタイプ</td>
                                    <td style="border:0px; font-size:100%;"><?php echo isset($form['form_type'])?$form['form_type']:"<i>未定義</i>" ?></td>
                                </tr>
                                <tr>
                                    <td style="border:0px; font-size:100%;">タイプ</td>
                                    <td style="border:0px; font-size:100%;"><?php echo isset($form['type'])?$form['type']:"<i>未定義</i>" ?></td>
                                </tr>
                            </table>
                        </td>
                        <?php if ($cnt != $loopcnt): ?></tr><?php endif; ?>
                    <?php endforeach; else: ?>
                        <td class="v" colspan="2"></td>
                    <?php endif; ?>
                </tr>
                <tr>
                    <?php if (count($action['action_class_info']['return']['prepare']) == 0): ?>
                    <td class="e">遷移先(prepare)</td>
                    <?php else: ?>
                    <td class="e" rowspan="<?php echo count($action['action_class_info']['return']['prepare']) ?>">遷移先(prepare)</td>
                    <?php endif ?>
                    <?php $cnt=0; $loopcnt=count($action['action_class_info']['return']['prepare']); if ($loopcnt): foreach ($action['action_class_info']['return']['prepare'] as $forward): $cnt++ ?>
                        <?php if ($cnt!=1): ?><tr><?php endif; ?>
                        <td class="v" colspan="2"><?php echo $forward ?></td>
                        <?php if ($cnt!=$loopcnt): ?></tr><?php endif; ?>
                    <?php endforeach; else: ?>

                        <td class="v" colspan="2"></td>

                    <?php endif; ?>

                </tr>
                <tr>
                    <?php if (count($action['action_class_info']['return']['perform']) == 0): ?>
                    <td class="e" >遷移先(perform)</td>
                    <?php else: ?>
                    <td class="e" rowspan="<?php echo count($action['action_class_info']['return']['perform']) ?>">遷移先(perform)</td>
                    <?php endif ?>
                    <?php $cnt=0; $loopcnt=count($action['action_class_info']['return']['perform']); if ($loopcnt): foreach ($action['action_class_info']['return']['perform'] as $forward): $cnt++ ?>
                        <?php if ($cnt!=1): ?><tr><?php endif; ?>
                        <td class="v" colspan="2"><?php echo $forward ?></td>
                        <?php if ($cnt!=$loopcnt): ?></tr><?php endif; ?>
                    <?php endforeach; else: ?>
                        <td class="v" colspan="2"></td>
                    <?php endif; ?>
                </tr>
                <?php endforeach; ?>
            </table>
            <br />

            <a name="forwards"></a>
            <h1>Forwards</h1>

            <table border="0" cellpadding="3" width="600">
                <?php foreach ($app['forward_list'] as $forward_name=>$forward): ?>
                <tr class="h">
                    <th colspan="2">
                        <a name="forward_<?php echo $forward_name ?>"></a>
                        <?php echo $forward_name ?>
                    </th>
                </tr>
                <tr>
                    <td class="e">ビュークラス</td>
                    <td class="v"><?php echo isset($forward['view_class'])?$forward['view_class']:"<i>未定義</i>" ?></td>
                </tr>
                <tr>
                    <td class="e">テンプレートファイル</td>
                    <td class="v"><?php echo $forward['template_file'] ?></td>
                </tr>
                <?php endforeach; ?>
            </table>
            <br />

            <a name="configuration"></a>
            <h1>Configuration</h1>

            <table border="0" cellpadding="3" width="600">
            <?php foreach ($app['configuration'] as $section_name=>$section): ?>
                <?php if ($section_name): ?>
                    <tr class="h">
                        <th class="h" colspan="2"><?php echo $section_name ?></th>
                    </tr>
                <?php endif ?>
                <?php if (count($section) == 0): ?>
                    <tr>
                        <td class="v" colspan="2">N/A</td>
                    </tr>
                <?php else: ?>
                    <?php foreach ($section as $entry_name=>$entry): ?>
                    <tr>
                        <td class="e"><?php echo $entry_name ?></td>
                        <td class="v"><?php echo nl2br($entry) ?></td>
                    </tr>
                    <?php endforeach; ?>
                <?php endif; ?>
            <?php endforeach; ?>
            </table>

            <br />

            <a name="plugins"></a>
            <h1>Plugins</h1>

            <table border="0" cellpadding="3" width="600">
                <?php foreach ($app['plugin_list'] as $plugin_type=>$plugin): ?>
                <tr class="h">
                    <th colspan="2">
                        <a name="plugin_<?php echo $plugin_type ?>"></a>
                        <?php echo $plugin_type ?>
                    </th>
                </tr>
                    <?php foreach ($plugin as $plugin_name=>$plugin_class): ?>
                    <tr>
                        <td class="e"><?php echo $plugin_name ?></td>
                        <td class="v"><?php echo $plugin_class ?></td>
                    </tr>
                    <?php endforeach; ?>
                <?php endforeach; ?>
            </table>
            <br />

            <hr />
            powered by <a href="http://ethna.jp/">Ethna <?php echo $app['ethna_version'] ?></a> (experimental)
            <hr />
        </div>
    </body>
</html>

追記(2008-04-28)

すでに、作られている方がいました。

Smartyのform_imput を使うには
input("email") ?>
みたいな感じ。超やっつけ仕事だけど。

Smartyのフォーム関連の拡張も使えるから、こっちの方がいいかも。