speedy: 37 lines of the fastest PHP template engine (joke)

唐突に、PHP用のテンプレートエンジンを4+1行で実装してみる。
方針:

  • ふつうのPHPファイルをテンプレートとして使う。
  • <?php echo $var; ?> は面倒なので <?=$var ?> と書けるようにする。
  • <?php echo htmlspecialchars($var); ?> はもっと面倒なので <?=h($var)?> と書けるようにする。
  • ついでにXML宣言も <?xml?>.... とかけるようにする。
4+1行で作るPHP用テンプレートエンジン - 絶品ゆどうふのタレ


を参考に、PHP用のテンプレートエンジンをCで実装してみる。
まかり間違って海外から人がやってきたときのためにタイトルに (joke) と入れているあたりチキンである。


方針:

  • 打倒、Blitz。(ネタなのでベンチマークはしない。たぶん負ける)
  • ふつうのPHPファイルをテンプレートとして使う。
  • <?php echo $var; ?> は面倒なので <?=$var ?> と書けるようにする。
  • <?php echo htmlspecialchars($var); ?> はもっと面倒なので <?=h($var)?> と書けるようにする。
    • h() を htmlspecialchars() のエイリアスにして、ユーザ定義関数呼び出しのオーバーヘッドを回避する。
  • ついでにXML宣言も <?=xml?>.... とかけるようにする。(Yudoufuさんのコードの <?xml?> はたまたまうまく動いているように見えますが、誤りです僕の勘違いで、<?xmlを予め出力しておいて、テンプレートの<?xmlは定数xmlを評価するだけという意図あってのものでした。失礼しました。>Yudoufuさん)
  • ついでに配列かstdClassでテンプレート変数空間を設定できるようにする。
php_speedy-071118.tgz
  • 思いのほか好評だったのでphpize済みアーカイブとして配布。
  • h() を htmlspecialchars() のエイリアスから組み込み関数に変更。引数は一つだけでオプションはとらない。
  • h() した結果を出力する関数 p() を追加。
  • 定数 xml を廃止し、XML宣言を出力する関数 xmldec() を追加。


CodeGen_PECL用specファイル:

<?xml version="1.0"?>
<extension name="speedy" version="1.0.0">
    <summary>Speedy Template Engine</summary>
    <constants><constant type="string" name="xml" value="&lt;?xml"/></constants>
    <code position="top">#include &lt;ext/standard/php_string.h&gt;</code>
    <function role="internal" name="RINIT"><code><![CDATA[
zend_function *func;
if (zend_hash_find(EG(function_table), "htmlspecialchars", sizeof("htmlspecialchars"), (void **)&func) == FAILURE) {
    return FAILURE;
}
return zend_hash_add(EG(function_table), "h", sizeof("h"), func, sizeof(zend_function), NULL);
    ]]></code></function>
    <function role="internal" name="RSHUTDOWN"><code><![CDATA[
return zend_hash_del(EG(function_table), "h", sizeof("h"));
    ]]></code></function>
    <function role="public" name="speedy">
        <proto>mixed speedy(string template[, mixed context])</proto>
        <code><![CDATA[
HashTable *orig_symbol_table = EG(active_symbol_table);
char *code;
int length;
code = php_addslashes((char *)template, template_len, &length, 0 TSRMLS_CC);
code = (char *)erealloc(code, length + sizeof("include \"\";\0"));
memmove(code + sizeof("include \"") - 1, code, length);
memcpy(code, "include \"", sizeof("include \"") - 1);
memcpy(code + sizeof("include \"") - 1 + length, "\";", sizeof("\";"));
if (context != NULL && (Z_TYPE_P(context) == IS_ARRAY ||
    (Z_TYPE_P(context) == IS_OBJECT && !strcmp(Z_OBJCE_P(context)->name, "stdClass"))))
{
    EG(active_symbol_table) = HASH_OF(context);
}
zend_eval_string(code, return_value, "function speedy()");
EG(active_symbol_table) = orig_symbol_table;
efree(code);
    ]]></code>
    </function>
</extension>


インストール:

pecl-gen speedy.xml
cd speedy
phpize
./configure
make
sudo make install


サンプルPHPコード:

<?php
dl('speedy.so');
$title = 'Example';
$list = array(10, '<A&amp;B>', null);
speedy('sample.html');


サンプルテンプレート:

<?=xml?> version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <title><?=$title?></title>
  </head>
  <body>
    <h1><?=$title?></h1>
    <table>
<? foreach ($list as $i=>$item): ?>
      <tr bgcolor="<?= $i % 2 ? '#FFCCCC' : '#CCCCFF'?>">
        <td><?=$i?></td>
        <td><?=h($item)?></td>
      </tr>
<? endforeach ?>
    </table>
  </body>
</html>


出力例 (php -d short_open_tag=1 sample.php):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <title>Example</title>
  </head>
  <body>
    <h1>Example</h1>
    <table>
      <tr bgcolor="#CCCCFF">
        <td>0</td>
        <td>10</td>
      </tr>
      <tr bgcolor="#FFCCCC">
        <td>1</td>
        <td>&lt;A&amp;amp;B&gt;</td>
      </tr>
      <tr bgcolor="#CCCCFF">
        <td>2</td>
        <td></td>
      </tr>
    </table>
  </body>
</html>


100%ネタ。
本気でテンプレートエンジンを書くならMayaaやKwartzを参考にPlain Old HTML Pagesベースにする。
POHPではないけれどノードツリーに独自の制御構文が入らない (入りにくい) Flexyでそれなりに満足しているので書かないけど。(Flexyだとflexy名前空間の属性でifやforeachが書けます)