簡易テンプレートエンジンを作る(2)

#実用的なテンプレートエンジンを目指して･･･#

[前回の記事](http://qiita.com/tak-solder/items/1718cc91daefad41efed)ではテンプレートエンジンのさわりの部分だけに触れましたが、もう少し掘り下げて、「ちょっとした開発なら使っても良いかな？」レベルのものを作ってみたいと思います。

##今回やること##

* クラスにまとめる
* テンプレート側でちょっとした処理を使えるようにする
* テンプレートを編集した後はキャッシュを作り直す

###クラスにまとめる###

やることが増え、コード量も増えるので一つのクラスにまとめて管理するようにします。
テンプレートに送る変数はマジックメソッドを使って配列で格納し、テンプレートを読み込む際に展開します。

###テンプレート側でちょっとした処理を使えるようにする###

テンプレートを分ける際に、「エスケープ」や「前後の空白のトリミング」をしたいときがあります。
エスケープしたものやトリミングしたものをテンプレートに送ってもかまわないのですが、こういった簡単な処理はテンプレート側でできると便利ですね。

今回は引数が1個の関数をテンプレート側から簡単に使えるようにしたいと思います。
引数が1個であれば、PHP標準関数でもユーザー定義関数でも大丈夫です。

###テンプレートを編集した後はキャッシュを作り直す###

前回の最後でテンプレートのキャッシュを生成しましたが、あの状態だとテンプレートファイルを再編集した場合にいちいちキャッシュを消す必要があります。
それでは面倒だったり、忘れてしまうことがあるので、テンプレートを編集したときに自動でキャッシュを切り替えるような仕組みを入れます。

今回はsha1_file関数を使って、ファイルのハッシュ値をキャッシュファイル名にして、ファイルの変更にも対応できるようにします。

##実際のコード##
```php:View.php
<?

class View
{
    private $vars = [];
    private $template = '';
    private $cache = '';

    public function __construct()
    {
        if (!defined('VIEW_CACHE')) {
            throw new \Exception('undefined VIEW_CACHE');
        }
    }

    public function setTemplate($template)
    {
        $template = realpath($template);

        if (!is_file($template)) {
            throw new \Exception('can\'t read file: ' . $template);
        }

        $this->template = $template;
    }

    public function __set($key, $value)
    {
        $this->vars[$key] = $value;
    }


    public function show()
    {
        $this->getCache();

        extract($this->vars);

        require $this->cache;
    }

    //キャッシュのパスを返す
    private function getCache()
    {
        //テンプレートを編集した後はキャッシュを作り直す
        $this->cache = VIEW_CACHE . '/' . sha1_file($this->template);


        if (is_file($this->cache)) {
            return;
        }

        //キャッシュがない場合は新規作成
        $source = file_get_contents($this->template);

        $source = preg_replace_callback('/\%(\w+) ?([\w -]*)%/', function ($m) {
            $str = '$' . $m[1];

            //テンプレート側でちょっとした処理を使えるようにする
            if (isset($m[2]) && preg_match_all('/-(\w+)/', $m[2], $funcs)) {
                foreach ($funcs[1] as $func) {
                    $str = $func . '(' . $str . ')';
                }
            }
            return '<?=' . $str . '?>';
        }, $source);

        file_put_contents($this->cache, $source, LOCK_EX);
    }
}
```

###使用方法###

1. インスタンスを作成する
2. `setTemplate`メソッドで使用するテンプレートを作成
3. テンプレートに渡す変数を格納する
4. `show`メソッドでキャッシュの生成・表示を行う

##使用例##
###テンプレート###
```php:template.php
<h2>プロフィール</h2>
<table>
    <tr>
        <th>名前</th>
        <td>%name -e%</td>
    </tr>
    <tr>
        <th>年齢</th>
        <td>%age -intval%</td>
    </tr>
    <tr>
        <th>職業</th>
        <td>%job -e -trim%</td>
    </tr>
</table>
```

前回同様`%name%`がキャッシュ作成時に`<?=$name?>`と代わります。
ただし、今回は`-e`というオプションをつけることによって、`<?=e($name)?>`と実際に表示されるのは`$name`を引数とする関数`e`の返り値となります。
※今回`e`関数はhtmlspecialcharsの省略形として使っています。

同様に、`%age -intval%`⇒`<?=intval($age)?>`や`%job -e -trim%`⇒`<?=trim(e($age))?>`といったようにPHPの標準関数や関数の複数掛けにも対応しています。

###プログラム###
```php
<?

require_once('src/Template/View.php');

//キャッシュディレクトリを定義
define('VIEW_CACHE', __DIR__ . '/cache');

//インスタンスの生成
$view = new View();

//テンプレートファイルの指定
$view->setTemplate(__DIR__ . '/template.php');

//テンプレートに送るパラメータの指定
$view->name = '<tak>';
$view->age = 0x17;
$view->job = ' "PHP"プログラマー';

//テンプレートの表示
$view->show();

//htmlspecialcharsの短縮形
function e($str)
{
    return htmlspecialchars($str, ENT_QUOTES, 'SJIS', false);
}
```


###生成されるキャッシュファイル###

```php:cache/[ハッシュ値]
<h2>プロフィール</h2>
<table>
    <tr>
        <th>名前</th>
        <td><?=e($name)?></td>
    </tr>
    <tr>
        <th>年齢</th>
        <td><?=intval($age)?></td>
    </tr>
    <tr>
        <th>職業</th>
        <td><?=trim(e($job))?></td>
    </tr>
</table>
```

変数が正しくPHPの構文に置き換えられ、オプションで指定した関数も使われてます。
ここまでくれば、出力例はいらないかもしれませんが、一応･･･

###出力例###

```html
<h2>プロフィール</h2>
<table>
    <tr>
        <th>名前</th>
        <td>&lt;tak&gt;</td>
    </tr>
    <tr>
        <th>年齢</th>
        <td>23</td>
    </tr>
    <tr>
        <th>職業</th>
        <td>&quot;PHP&quot;プログラマー</td>
    </tr>
</table>
```

良い感じにエスケープされたり、不要な余白が削除され、
~~若く見せようとしていたのがバレたりしてしまいました~~