ほぼ使われていないと思いますが、もし本当に不幸で10年前以上の技術を使われたプロジェクトの保守に任せられたら、ご参考になっていただければ幸いです。
本記事の想定対象
本記事は以下の対象を読者として想定しています。- PHPの初心者
- 不幸にもPHPの古い技術TemplatePowerを使えざるを得ない担当者
- HTML、CSS、Javascript初級〜中級の方
- PHPのtemplate engineに変な見たことのない記述に遭遇する時
- TemplatePowerを知りたい方
- template engineのHTML、CSS、Javascript担当者がPHP担当者の業務を知りたい方
参考資料があまり見つからなかったため、錯誤、間違いがあれば、お気軽にコメントしていただければ幸いです。
何度でも修正しますので、多分これが数少ない上古時代技術の参考文献になると思います。
Template Engineとは?
Template Engine(テンプレートエンジン)とはHTMLの重複した区域をテンプレートにして、バックエンドの言語でデータモデルをそのテンプレートに投げて、それでHTMLを生成する、便利なものです。 (文系見解)グーグルで検索したら色々出てくると思いますが、現代風に言えば、Reactのcomponents(コンポーネント)のようなものですが、Reactは全部jsで書くに対して、テンプレートエンジンはHTMLの中にバックエンド言語を記述します。
例えば、一つ一つの商品カードにデータを渡して、描画しようとした時。
// Card.js
import React from "react";
function Card(props){
return(
<div>
<h2>{props.title}</h2>
<img src={props.imageUrl} />
<p>{props.describe}</p>
</div>
);
}
export default App;
// App.js
import React from "react";
import Card from "components/Card";
import CardData from "datas";//普通は他のところが投げてきたjsonデータです
function App(){
return(
{
CardData.map(
(value, key) => {
return(
<Card key={key}
title={value.title}
imageUrl={value.imageUrl}
describe={value.describe}
>
);
}
)
}
);
}
export default App;
初心者のためにちょっと改行を多くしましたが、凝縮すると、map関数でloop処理を行なって、沢山の<div>
を生成します。
Reactはこういう感じで描画しますが、テンプレートエンジン搭載されたHTMLファイルはこうなります。
(Smartyを例とします。弊社のサイトは開発会社が当時、<{}>
をSmartyのタグとして定義しました)
<body>
<div>
<{section name=cardArrayIndex loop=$cardArray}>
<div>
<h2><{$cardArray[cardArrayIndex].title}></h2>
<img src='<{$cardArray[cardArrayIndex].imageUrl}>' />
<p><{$cardArray[cardArrayIndex].describe}></p>
</div>
<{/section}>
</div>
</body>
ちなみに、テンプレートエンジンを使わない場合は、PHPファイルで描画することになります。
コードは下のようになるでしょう。
<?php
/*データの処理*/
$array = array(
array('title'=>'apple', 'imageUrl'=>'imgs/apple.png', 'describe'=>'From Aomori'),
array('title'=>'banana', 'imageUrl'=>'imgs/banana.png', 'describe'=>'From America'),
array('title'=>'kiwi', 'imageUrl'=>'imgs/kiwi.png', 'describe'=>'From Australlia')
);
?>
<html>
<body>
<div>
<?php
for($i=0; $i<$count($array); $i++){
echo "<div>".
"<h2>".$array[$i]['title']."</h2>".
"<img src='".$array[$i]['imageUrl']."' />".
"<p>".$array[$i]['describe']."</p>".
"</div>";
}
?>
</div>
</body>
</html>
という感じで、Stringをめちゃ長く繋いで、HTMLにタグを含むStringを描画してもらうようになります。
バックエンド言語が沢山あるように、色々なテンプレートエンジンがあります。
PHPでは、歴史悠久のSmartyはいまだに健在なテンプレートエンジンが有名でしょう。
(ここは同時代、開発元がもういないエンジンのTemplatePowerを主に紹介します。)
TemplatePowerとは?
ウィキペディアによりますと、PHP5がベースのウェブテンプレートエンジンです。公式サイトは完全に接続できなくなりました。
http://templatepower.codocad.com/
2010年に二代目を発表した後、更新が途絶えました。
特徴はSmartyに影響され、記述は似ていて、ページ分けなど豊富の機能が備わっています。
あと、拡充性、クロスサイトスクリプトに対抗できる、XMLを操作可能など...
(Smartyを使った方が良さそうですけど...)
導入
公式サイトが消えた今は導入不可能ですが、弊社の公式サイトにはこういうファイルがあります。
こちらは私が弊社サイトのテンプレートエンジンのフォルダー内に見つけた、TemplatePowerの公式サイトのファイルらしきものを集めて、Notionに整理したものです。
TemplatePowerの公式サイトがなくなった今、貴重なドキュメントになったと思います。
ネットで真面目に検索したら、もっと出てくるかもしれません。
ライセンスも添付していますので、ご参考頂ければ幸いです。
※私は開発者ではないので、保守責任は致しかねます。
※こうしてシェアするのも著作権違反になりかねないので、もし原作者が要求したら、上記データを告知なしに撤去しますので、ご了承ください
※Notionのサービスを使っていますので、Notion社の都合で上記ページが見られなくなる場合があります。
プロジェクトに入れて、PHPのファイルにinclude
記述すれば導入が完了すると思います。
<?php
include('../commonfile/TemplatePower/class.TemplatePower.inc.php');
?>
PHPでHTML生成の準備
classをnewする
導入したら、classを一つnewして、引数は対象となるHTMLファイルの位置と名前です。オブジェクト指向の感じですね。
<?php
//上略
$tpl = new TemplatePower('./order_list_mang.htm');
?>
弊社公式サイトのコンテンツ管理システムは結構昔に作られたものなので、拡張子は.htm
です。
拡張子を見るだけで、古いサイトと分かるんですね。
assignInclude()で他のPHPを追加する
もし他のPHPファイルを追加したい場合、下記のように記述します。
<?php
//上略
$tpl -> assignIclude('mainMenu', './00mainMenu.php');
?>
一つ目の引数はHTMLファイルに嵌める時、どんな名前で嵌めるのか、二つ目はどのファイルを追加するかのパスとファイル名です。
こちらは変数命名の概念と考えて良いでしょう。const mainMenu = './00mainMenu.php';
的な感じです。
こちらの追加はincludeと違って、画面に表示するという追加です。
テンプレートエンジンでPHPとHTMLを分離したから、PHPでincludeしても、HTMLに描画しません。
prepare()で準備して、これからループ処理して描画する
includeが完了したら、prepare()
しましょう。
<?php
//上略
$tpl -> prepare();
?>
ここまでが前菜、これからは複雑なデータをHTMLに投げる処理です。
gotoBlock()で_ROOT区域に移動する
この_ROOT
の意味は、HTMLのbody内の一番広域のことを指します。
<?php
//上略
$tpl -> gotoBlock('_ROOT');
?>
これでここからの処理はHTMLの一番外側の要素になるということです。
公式のドキュメントでは、上の一層に移動すると説明しているんですが、基本的に最上層しか行かないと思います。
(弊社のサイト全部gotoBlock('_ROOT')
になっているし...)
newBlock()でループ処理の区域を作る
これは、HTML上ある区域、ループ処理のデータをパパパパパッと表示したい、その区域の名前を宣言するという意味です。<?php
//上略
for($i=0; $i<count(xxx); $i++){
$tpl -> newBlock('orderData');
/* その他処理 */
}
$tpl -> gotoBlock('_ROOT');
?>
公式のドキュメントではこのように、ループ内に下の処理は全部その区域に入れる、というふうに解釈しています。
そして、ループ終了後一番上の層に戻るという書き方が基本だそうです。
assign()でHTMLに投げるデータを命名する
これはループ処理の中に書かなくても大丈夫です。
newBlock()
でどの区域を指定したら、どこでもアサインできます。一番上の_ROOT
でもアサインできます。
assign()
はSmartyのassign()
と同じ用途だと考えて良いでしょう。
<?php
//上略
$tpl -> gotoBlock('_ROOT');
$count = 0;
while($count < 10){
$tpl -> newBlock('orderData');
$tpl -> assign('name', $customerName);
$tpl -> assign('orderId', $orderId);
}
$tpl -> gotoBlock('_ROOT');
$tpl -> assign('allOrderPrice', $sum);
?>
という感じで、HTMLでorderDataブロックの{name}
はPHPで取得した$customerName
になります。
そして、処理完了したら、_ROOT
に戻って、また別の処理を始めるときnewBlock()
して、また_ROOT
戻って、またnewBlock()
して、こういうのを一つのページが表示したいものを全処理完了するまで繰り返します。
最後にprintToScreen()で指定したHTMLに描画する
最初 new TemplatePower()をしたんじゃないですか?
その引数のパスのHTMLで描画します。
<?php
//上略、ほぼ最後の一行が下
$tpl -> printToScreen();
?>
PHP全体はこういう感じ
<?php
include('../commonfile/TemplatePower/class.TemplatePower.inc.php');
$tpl = new TemplatePower('./order_list_mang.htm');
$tpl -> assignIclude('mainMenu', './00mainMenu.php');
$tpl -> prepare();
$tpl -> gotoBlock('_ROOT');
$count = 0;
while($count < 10){
$tpl -> newBlock('orderData');
$tpl -> assign('name', $customerName);
$tpl -> assign('orderId', $orderId);
}
$tpl -> gotoBlock('_ROOT');
$tpl -> assign('allOrderPrice', $sum);
$tpl -> printToScreen();
?>
HTMLの表示したいところに放り込む
基本はHTMLのコメントアウト(<!-- -->
)のような記述をしています。
<!-- XXXX : OOO -->
という形になっています。
XXXXはTemplatePowerの指令、OOOは先ほどPHPで書いたassignInclude()
、newBlock()
などの処理で命名した名前です。
INCLUDESCRIPT BLOCKはassignInclude()に対応する
<html>
<head>
</head>
<body>
<!-- INCLUDESCRIPT BLOCK : mainMenu -->
</body>
</html>
公式のドキュメントは <!-- INCLUDE BLOCK -->
は.tpl
ファイルしか引用できなくて、<!-- INCLUDESCRIPT BLOCK -->
は2.0版で、.php
ファイルを引用できるようになったと説明しています。
START BLOCKとEND BLOCKはnewBlock()に対応する
<html>
<head>
</head>
<body>
<table>
<!-- START BLOCK : orderData -->
<tr>
<td>{orderId}</td>
<td>{orderName}</td>
<td>{orderAddress}</td>
<td>{orderPrice}</td>
<td>{addDate}</td>
<td>{orderStatus}</td>
<td>{updateDate}</td>
</tr>
<!-- END BLOCK : orderData -->
</body>
</html>
書き方は結構厳しくて、<!-- START BLOCK : XXXX -->
は単語と単語の間、ちゃんとスペースキーを入れないといけないそうです。
XXXXの部分は先ほどPHPでnewBlock()
を使った時の引数です。
ここは多分最も重要な重複の要素を生成する記述です。これで一列一列全部の要素を書かなくて済みます。
PHPでループ処理でassign()
したデータを全部パパパッと描画します。
Smartyと違って、ループ処理の中に、条件を付けられないそうです。
具体例を挙げますと、Smartyはsectionの中で、if/elseの条件処理は可能です。(弊社公式サイトのバナー区域から引用)
<{section name=adv01_sec1 loop=$a01_link01_1}>
<div class="card other">
<{if $a01_link01_1[adv01_sec1].weblink == ""}>
<a href="javascript:void(0);" title="<{$a01_link01_1[adv01_sec1].title01}>">
<img src="<{$a01_link01path}><{$a01_link01_1[adv01_sec1].dirname}>Crop_<{$a01_link01_1[adv01_sec1].upfile1}>" width="687" height="239" />
</a>
<{else}>
<a href="<{$a01_link01_1[adv01_sec1].weblink}>" target="<{$a01_link01_1[adv01_sec1].weblink_target}>" title="<{$a01_link01_1[adv01_sec1].title01}>">
<img src="<{$a01_link01path}><{$a01_link01_1[adv01_sec1].dirname}>Crop_<{$a01_link01_1[adv01_sec1].upfile1}>" width="687" height="239" />
</a>
<{/if}>
</div>
<{/section}>
上記コードは、もしコンテンツマネジメントシステムにバナー画像の移動先URLを設定すれば、aタグにhref
属性を付けて、もしないならjavascript:void(0);
を付けるという。
しかし、TemplatePowerはできないんです。つまり、PHPでif/elseの条件処理を完了したデータをHTMLに投げる、ということになります。
上記を例とすれば、PHPでaタグのhref
を決めて、全部文字列にして、HTMLにてSmartyで描画する感じになります。
ちなみに、TemplatePowerの公式説明ドキュメントでは、二重ループの記述を披露しています。
私も自力で公式ドキュメントを見ずになんとか辿り着けました。下記のようになっています。
<?php
include_once('./class.TemplatePower.inc.php');
$tpl_name = 'survey_response';
$tpl = new TemplatePower('./'.$tpl_name.'_edit.htm');
$tpl->prepare();
/*中略*/
/**************** 用迴圈繪製DIV到HTML ****************/
//把問題json解析成各大題
$array_question_content = json_decode($survey_question_content);//變成各個array
//但json裡面有object檔案(有指定array編號項目會變成object)要變成array才能用
function stdObjectToArray($object){
$array = json_decode(json_encode($object), true);
return $array;
}
for($i=0; $i< count($array_question_content); $i++){
$tpl->newBlock("QuestionGroup");//問題區塊起始
$question_group_title = $array_question_content[$i][0];//各群組大標題
$tpl->assign("question_group_title", $question_group_title);
$question_group = $array_question_content[$i];//每個陣列存成一個變數
for($j=1; $j< count($question_group); $j++){
$question_question = $question_group[$j];//每個物件存成一個變數
$array_question = stdObjectToArray($question_question);//每個物件轉成陣列,每一題的內容
$tpl->newBlock("QuestionContent");
$str_question_id = $array_question[0];
$tpl->assign("question_id",$str_question_id);//題目編號
$str_question_title = $array_question["title"];
$tpl->assign("question_content", $str_question_title);//題目內容
$str_question_name = $array_question["name"];
$tpl->assign("question_name", $str_question_name);//題目名字
$str_question_type = $array_question["type"];
$str_question = "";
for($m = 0; $m< count($array_question["options"]); $m++){
if($str_question_type === "radio"){
$str_question .= " <input type='radio' name='".$str_question_id."' value='".$array_question["values"][$m]."'>".$array_question["options"][$m]."";
}else if($str_question_type === "checkbox"){
$str_question .= " <input type='checkbox' name='".$str_question_id."' value='".$array_question["values"][$m]."'>".$array_question["options"][$m]."<br>";
}else if($str_question_type === "select"){
$str_question0 .= "<option value='".$array_question["values"][$m]."'>".$array_question["options"][$m]."</option>";
$str_question = " <select name='".$str_question_id."'>.$str_question0.</select>";
}else if($str_question_type === "text"){
$str_question .= " <input type='text' name='".$str_question_id."' style='width:60%;'>";
}else{
$str_question .= " <textarea name='".$str_question_id."' ".$array_question["options"]."></textarea>";
}
}
$tpl->assign("question",$str_question);
$tpl->gotoBlock("_ROOT");
}
$tpl->gotoBlock("_ROOT");
}
/*下略*/
?>
一部私が書いたコードをそのままここにコピペしてきたので、コメントが中国語になって申し訳ありません。
アンケート機能の、問題を描画する部分です。
jsonデータを分析して、二重ループで、外側は問題グループ分けを陳列、内側は問題のタイトルと内容など、さらに一番内側のループは選択肢を陳列します。
PHPは二つのnewBlock()
が必要です。
一つは問題(タイトルと選択肢)を一つのユニット(newBlock("QuestionContent")
)、もう一つは問題グループ分けのグループ(newBlock("QuestionGroup")
)、問題の種類で全部何種類ありますから。
HTMLファイルはこういう感じになります。
<!-- START BLOCK : QuestionGroup -->
<div class="field">
<label style="font-size: 1.3rem;">{question_group_title}</label>
<table style="width:100%;">
<!-- START BLOCK : QuestionContent -->
<tr><td>{question_content}</td></tr>
<tr><td>{question}</td></tr>
<!-- END BLOCK : QuestionContent -->
</table>
</div>
<!-- END BLOCK : QuestionGroup -->
感想を簡単に述べますと、HTMLは簡単に表示しているんですが、PHPはかなり複雑に処理しなければならないです。
でも、逆に言えば、if/else処理はPHPで統一処理したほうが良いとも言えるでしょう。
以上が古きPHPのテンプレートエンジンの一つ、TemplatePowerの紹介でした。
(公式のドキュメントもあまり長くなかったです)
後書き
(ただの愚痴になるかもしれません)
弊社の公式サイト、元々PHP5で開発されていたらしいです。
三人のエンジニアとPMと私の上司(デザイン担当)、五人〜六人くらいで作り上げたそうです。
どうしてこの技術を2013年にウェブ制作の時に選んだのか、知る由もなかったです。
多分当時、ウェブ制作会社に頼んだ時は経費を色々削って、こうなったのだろうと思います。
それと、当時の情シスはインフラ専門で、ウェブ系についてあまり詳しくなかったそうです。
(情シスの業務はインフラがメインなのは普通ですけど。)
表向きのパソコン版、その内容を操作する裏のコンテンツマネジメントシステム(CMS)、2017年に新しく作ったモバイル向けのサイト、三つで構成されています。
全部バニラのPHPですが、ADOdbライブラリを使ってMySQLと接続、テンプレートエンジンはパソコン版、モバイル版がSmarty、CMSがTemplatePower。
(どうして統一しないでしょうか?せめて統一しろ!if/esleの処理をHTMLで行うかPHPで行うか統一してくれ!)
これからはちょくちょくと上記の古い技術を整理して、記事にしたいと思います。自分で一つのケジメをつけたいです。
(現在は新規サイトの企画で、PythonのFastAPI、React、MySQLを使う予定で、そちらを勉強中です。)
参考資料
- https://pear.php.net/manual/en/package.html.html-template-it.intro.php
- https://www.dunebook.com/best-php-template-engine/
- https://tleapps.com/best-php-template-engines/#Smarty
- https://www.smarty.net/
- https://www.fenet.jp/dotnet/column/language/7696/
- http://www.forpower.com/blog/2013/09/15/smarty%E5%B8%B8%E7%94%A8/
- https://www.slideshare.net/zyxist/open-power-template-2-presentation
- http://www.phpprogram.net/php-template-engines/open-power-php-template-engine/
- http://www.phpprogram.net/php-template-engines/smarty-php-template-engine/
- https://hotexamples.com/examples/-/TemplatePower/newblock/php-templatepower-newblock-method-examples.html
- https://en.wikipedia.org/wiki/Open_Power_Template
- https://github.com/OPL/Open-Power-Template
- https://zenn.dev/ynakamura/articles/e562376735d398
- https://fannys23.pixnet.net/blog/post/44903881-php7-templatepower
- https://github.com/php/presentations/blob/master/slides/intro/templatepower.php
- http://www.terramar-info.com.uy/landing/landing_php/includes/TemplatePower/TPexamples/manual_es.html
- https://fossies.org/dox/pbcs-0.7.2/class_8TemplatePower_8inc_8php_source.html
- http://templatepower.codocad.com/
- https://programmierfrage.com/items/templatepower-block-inside-block
- https://www.nc.com.tw/modules/answer/question/5