唐突に、PHP用のテンプレートエンジンを4+1行で実装してみる。
方針:4+1行で作るPHP用テンプレートエンジン - 絶品ゆどうふのタレ
- ふつうのPHPファイルをテンプレートとして使う。
- <?php echo $var; ?> は面倒なので <?=$var ?> と書けるようにする。
- <?php echo htmlspecialchars($var); ?> はもっと面倒なので <?=h($var)?> と書けるようにする。
- ついでにXML宣言も <?xml?>.... とかけるようにする。
を参考に、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でテンプレート変数空間を設定できるようにする。
- 思いのほか好評だったのでphpize済みアーカイブとして配布。
- h() を htmlspecialchars() のエイリアスから組み込み関数に変更。引数は一つだけでオプションはとらない。
- h() した結果を出力する関数 p() を追加。
- 定数 xml を廃止し、XML宣言を出力する関数 xmldec() を追加。
<?xml version="1.0"?> <extension name="speedy" version="1.0.0"> <summary>Speedy Template Engine</summary> <constants><constant type="string" name="xml" value="<?xml"/></constants> <code position="top">#include <ext/standard/php_string.h></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&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><A&amp;B></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が書けます)