開発畑トップ   »  PHP   »  今日からプログラマー!PHP入門【テンプレートエンジン作成編】   »  今日からプログラマー!PHP入門(第4回)

今日からプログラマー!PHP入門(第4回)

一週間のご無沙汰です。さぁ始めましょう。 今日からプログラマー!PHP入門、略して今プロPHP第4回です。

4回目まで続いたのでもう一度このブログ連載の方針をご説明しますね。

まず私個人の考えですが、致せり尽くせりな説明だと勉強する側の為にならないと思っています。
なので、かな~り端折って説明しております。
必要な部分や解らないだろうなと思う部分は極力URLリンク(PHPの公式サイトへのリンク)を張っていますので、そちらを見て勉強してもらうという方針です。
人間悩まないと覚えないと思うのです(極論w
なので、頑張って悩んで覚えてくださいませ。

では、そんな感じで第4回始めましょう~


Includeとeval

PHPでプログラムを作る場合に、たとえば以下のような$calc変数の中身を実行したい場合があります。

$calc = '$c = $a + $b;';

echoを使って$calcを出力した場合、「$c = $a + $b;」という文字列が表示されるだけです。
また、以下のようにシングルクォートをダブルクォートに変えても、計算結果にはなりません。

$a = 10;
$b = 20;
$calc = "$c = $a + $b;";

この場合echoで表示される文字は「= 10 + 20;」となります。
ダブルクォート(二重引用符)内の変数についてはこのリンクを参照してください。
また、上記の場合は$c変数が定義されていないのでNoticeエラーが発生します(PHPの設定によってはエラーが出ない場合もあります)

文字列の計算は色々方法がありますが、簡単にできる方法としてinclude(やrequire)を使う方法とevalを使う方法があります。

includeは外部ファイルを読み込む制御構造です。
また、読み込んだ時に実行までします。

では、上記の処理をincludeで処理する場合を見てみましょう

includeを使った場合の処理

<?php
$a = 10;
$b = 20;
$calc = '$c = $a + $b;';
$file = 'calc.php';
$content = "<?php\n" . $calc;
file_put_contents($file, $content);
include $file;
echo $c;

ここでは、一旦$calcの計算式をファイルとして保存して、実行しています。
では、順を追ってみてみましょう

  • 1行目~4行目までは、最初に描いたコードと同じなので省略
  • 5行目で一時ファイルを作るために、ファイル名を$file変数に代入します
  • 6行目では、includeでphpファイルを読み込む際に、対象となるファイルは行頭に<?phpの記述が必要なので、$calc変数に文字列を接続しています。
    ※後ろの\nはラインフィード(改行コード)です。
  • 7行目で使用しているfile_put_contents()関数は文字列をファイルに書き込みをする関数です。
    ここでは、$fileの値(calc.php)というファイルに$contentの値を書き込みします
  • 8行目でincludeを使って、7行目で作成したcalc.phpのファイルを読み込みます。
    また読み込んだ時点で実行(この場合は「$c = $a + $b」が実行)されます。
  • 9行目で実行結果の$cをechoを使って表示します。

evalを使った場合の処理

次にevalを使った方法です。
ただし、evalを使うときは気を付けないといけません。
evalは任意のPHPコードを実行出来てしまうので、ユーザーから受け取った値などを処理する場合、想定外の動きをする可能性があります。

まぁ、使う使わないは別として、知識として覚えておくにこしたことはないので今回はevalのやり方も覚えてみましょう。

<?php
$a = 10;
$b = 20;
$calc = '$c = $a + $b;';
eval($calc);
echo $c;

使い勝手だけでいえば、evalの方が簡単です。

  • 1行目~4行目は同様です。
  • 5行目でevalを使って$calc変数の値を実行します。
  • evalで処理された$cをechoを使って表示します

では、この2つの方法を使って前回のテンプレートエンジンを改良していきましょう。

ループ処理と分岐処理を作る

PHP関数のループ処理と分岐処理は前回・前々回に学びましたので、それを使って、テンプレートエンジンの処理を作っていきましょう。

たとえば、以下のようなHTMLがあった場合

<ul>
     <li>home</li>
     <li>blog</li>
     <li>contact us</li>
     <li>about us</li>
</ul>

テンプレートエンジンに以下のようなループ処理をさせれば、項目が増えてもHTMLは編集しなくて済みます。

<ul>
     {foreach $liList as $key => $val}
          <li>{$val}</li>
     {/foreach}
</ul>

※「{foreach $liList as $key => $val}~{/foreach}」の文はPHPのforeach()に似せて作ったテンプレートエンジン用のオリジナル関数です。

$liListには配列でhome、blog、contact us、about usの4つの項目が入ります。
また、liタグが1件もない場合の処理も追加してみましょう(分岐処理)

{if isset($liList)}
     <ul>
          {foreach $liList as $key => $val}
               <li>{$val}</li>
          {/foreach}
     </ul>
{/if}

これで項目が無い場合に表示しないようになります。

※「{if isset($liList)}~{/if}」はPHPのif()に似せて作ったテンプレートエンジン用のオリジナル関数です。
またif内のisset()関数はPHPの関数をそのまま使用します。

上記のようにテンプレートファイルを記述して、以下のようにPHP側のテンプレートエンジンで変換するプログラムを作っていきます。

<?php if (isset($liList)) { ?>
     <ul>
          <?php foreach ($liList as $key => $val) { ?>
               <?php echo "<li>$val</li>"; ?>
          <?php } ?>
     </ul>
<?php } ?>

PHPファイルは、上記のようにHTML文の中にPHPの開始タグ・終了タグを入れることでHTMLとPHPコードを混在させることができます
詳しくはリンク先をご参照ください。

では、PHP側の処理を作っていきましょう。

PHPファイル、Classを使ったテンプレートエンジン

<?php
class template {
    private $template;
    private $resource;
    private $assignVal;
    public function __constract() {
        $this->template = '';
        $this->resource = '';
        $this->assign = array();
    }
    public function tplSetup($file) {
        if (file_exists($file) == false) return false;
        $this->template = $file;
        $this->resource = file_get_contents($file);
        return true;
    }
    public function tplAssign($key, $val) {
        $this->assignVal[$key] = $val;
    }
    public function tplDisplay() {
        $dat = preg_replace("/\{if[ ]+?(.+?)\}/", "<?php if (\\1) { ?>", $this->resource);
        $dat = preg_replace("/\{elseif[ ]+?(.+?)\}/", "<?php }elseif (\\1) { ?>", $dat);
        $dat = preg_replace("/\{else\}/", "<?php }else{ ?>", $dat);
        $dat = preg_replace("/\{\/if\}/", "<?php } ?>", $dat);
        $dat = preg_replace("/\{foreach[ ]+?(.+?)[ ]+?as (.+?)=>(.+?)\}/"   , "<?php foreach (\\1 as \\2 => \\3) { ?>", $dat);
        $dat = preg_replace("/\{foreach[ ]+?(.+?)[ ]+?as (.+?)\}/"          , "<?php foreach (\\1 as \\2) { ?>", $dat);
        $dat = preg_replace("/\{\/foreach\}/", "<?php } ?>", $dat);
        $varParam = '[\$][a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff\[\]\'\"\$]*?';
        $dat = preg_replace('/\{('.$varParam.')\}/', "<?php echo \\1; ?>", $dat);
        $this->IncludeText($dat);
    }
    private function IncludeText($text, $evalMode = false){
        foreach ($this->assignVal as $key=>$val) {
            $$key = $val;
        }
        if ($evalMode) {
            eval('?>'.$text);
        }else{
            $filename = tempnam('/','tmp');
            file_put_contents($filename,$text);
            include $filename;
            unlink($filename);
        }
    }
}
//------------------------------------------------------------
$file = "template.html";
$objTemplate = new template();
$result = $objTemplate->tplSetup($file);
if (!$result) {
    exit("file '{$file}' is not found.");
}
$liList = array("home",
                "blog",
                "contact us",
                "about us");
$objTemplate->tplAssign('liList', $liList);
$objTemplate->tplDisplay();
exit;
?>

前回から結構修正が入りました。
では、順を追ってみていきましょう。

Classの最初の方は同じです。
tplDisplayの中身が大幅に増えましたので、そのあたりから解説していきます

  • まず、21行目~24行目
    ここでは、テンプレートファイルの中に{if }~{/if}の処理がある場合に、PHP関数のif文に置き換える処理をしています。
    if文については、リンクを参照してください
    処理自体はpreg_replace()関数を使っています。※第2回参照
  • 次に25行目~27行目
    ここでは、テンプレートファイルの中に{foreach }~{/foreach}の処理がある場合に、PHP関数のforeach文に置き換える処理をしています。
    foreach文については、リンクを参照してください
    処理自体はpreg_replace()関数を使っています。※第2回参照
  • 最後に28行目~29行目
    ここでは、前回(第3回)の文字列置換を少し複雑にした内容です。
    $varParamに代入した文字列は変数名の命名規則を正規表現で書いています。
    これは、リンク先に正規表現が掲載されているので、そのまま流用しています
    ※最初のほうの[\$]は変数の$マークです。
  • 30行目
    IncludeTextメソッドを呼び出しています。
  • 32行目
    では、IncludeTextメソッドの処理を見てみましょう
    IncludeTextメソッドはprivateプロパティメソッドです。privateプロパティメソッドは外部からは呼び出しできません。同じClass内からのみ呼び出しのみ可能です。
    IncludeTextメソッドの第2引数を見てください。
    「$eval = false」と書かれています。
    これは、呼び出し元の第2引数が無い場合は、$evalの値はFalseを代入するという意味です。
    今回は第2引数は指定していないので$evalはFalseになります。
  • 33行目~35行目
    ここでは、foreach文でtplAssignで登録した文字列を順に呼び出しています。
    $$key=$val;

    この記述で、変数の値を変数名にすることができます。これを可変変数と言います。
    $this->assignValの中身はtplAssignメソッドで登録された$liList変数なのでこの処理で以下のような変数が作成されます。

    $liList= array("home", "blog", "contact us", "about us");
  • 36行目
    ここで、2種類のモード(includeを使った方法とevalを使った方法)を切り替えるようにしています。
    まずは、$evalがtrueの方をみましょう。
  • 37行目
    eval('?>'.$text);と書かれています。
    これは、eval内の文字列は<?phpから開始してはいけないため、終了タグを先に入れることで対処しています。
    evalの場合は、これだけで処理は終わりです。
    evalが実行された時点でデータが置換されたテンプレートの中身が表示されます。
  • 38行目~41行目
    次に$evalがfalseの場合の処理です。
    最初に説明した内容の通りの処理ですが、1点異なっています。
    $filename = tempnam('/','tmp');

    tempnam()関数はー意なテンポラリファイルを作成します。

    そしてinclude処理が実行された時点で置換されたテンプレートの中身が表示されます。

  • 42行目
    unlink()関数を使って、作成したテンプレートファイルを削除しています。
    処理としては、このファイルを残しておいて、次に同じ処理が来た場合流用する方法もあります。(俗に言うキャッシュファイルです)

最後に

今回はさらに難しかったと思います。
しかし、一つ一つ理解していけば大丈夫なはずです!(たぶん
がんばって悩んで覚えていきましょう!!

今回のプログラムは、セキュリティ的には問題が結構ありますので、そのままの使用はお勧めできません。
というわけで、次回はそのあたりの対策をやっていこうと思います。
では、また来週~

コメントをどうぞ!