HTMLoader作ってみた - HTMLとPHPを分離したい一心で -

#HTMLoader
HTMLとPHPが混在しているコードって見づらくないですか？
私ダメなんですよあれ。

```php:hoge.php
<div><?php echo $menu["name"]; ?></div>
```

これをなんとか解決してみようと思います。
多分世の中にはすでにあるんでしょうけど、PHPへの理解を深める勉強と思いHTMLoaderを作ってみました。

--追記 2017.12.28
これってテンプレートエンジンって言われてるものなんですね。知らなかったです。
Twigっていう有名なすごいものがすでにあってビックリしました。
しかもすごく文法（　{{data.abc}}とか ）似てて、二番煎じ感が恥ずかしいです。
AnglarJSの書き方をイメージしたんですけどね。

#Git
サンプル含めファイルをGitHubに上げました。
[HTMLoader](https://github.com/AdjusTech/HTMLoader)

#基本的な処理
いろんなやり方があると思いますが、私が思いついたのはこれだけでした。

1. phpファイルとhtmlファイルを用意する。
2. phpファイルでhtmlを読み込む。
3. html内のとある決められた記号を置き換えていく。（表示内容の流し込み）

#使用方法
##ファイル構造

* www/
	* HTMLoader.php
	* index.php
	* _tmpl.html
  * config.json
	* parts/
		* head.html
		* header.html
		* footer.html
	* info/
		* index.php
		* _tmpl.html
		* config.json
  * jquery.js

##各ファイル・ディレクトリ説明
<dl>
	<dt>www/</dt>
  <dd>ドキュメントルート。</dd>
  <dt>HTMLoader.php</dt>
  <dd>HTMLoaderファイル。</dd>
  <dt>index.php</dt>
  <dd>HTMLoaderのインスタンスを作成し、表示内容を流し込む。</dd>
  <dt>_tmpl.html</dt>
  <dd>通称テンプレートファイル。HTMLoaderに読み込まれる。</dd>
  <dt>config.json</dt>
  <dd>通称設定ファイル。現在の階層や、パーツディレクトリのある階層。サイトタイトルなどの設定を書き込む。</dd>
  <dt>parts/</dt>
  <dd>通称パーツディレクトリ。</dd>
  <dt>head.html</dt>
  <dd>通称ヘッドパーツ。全ページで共通してheadタグ内に書かれるもの。</dd>
  <dt>header.html</dt>
  <dd>通称ヘッダーパーツ。全ページ共通のヘッダーが書かれたHTML。</dd>
  <dt>footer.html</dt>
  <dd>通称フッターパーツ。全ページ共通のフッターが書かれたHTML。</dd>
  <dt>info/</dt>
  <dd>infoページディレクトリ。</dd>
  <dt>jquery.js/</dt>
  <dd>例としてJQueryのjsファイルを置いてみる。</dd>
</dl>

##導入の一例
<details>
	<summary>parts/内のファイル</summary>
	<div>
###テンプレートファイル_tmpl.html

```html:_tmpl.html
<h2>{{data.page_title}}</h2>

<section id="message" class="content">
	<h1>{{data.msg.title}}</h1>
	<p>{{data.msg.text}}</p>
</section>

<section id="notice" class="content">
	<!-- each data.notices start -->
		<section>
			<h1>[[data.notices.title]]</h1>
			<p>[[data.notices.text]]</p>
		</section>
	<!-- each data.notices end -->
	<!-- if {{ %COUNT data.notices }} == 0 -->
		<p>現在、お知らせはありません。</p>
	<!-- endif -->
</section>

<!-- if isAprilFools -->
<section id="april_fools" class="content">
	<p>本日はエイプリルフールですね。</p>
</section>
<!-- endif isAprilFools -->

<script src="{{$ROOT}}/jquery.js"></script>
```

###index.php

```php:index.php
<?php 
include_once($_SERVER["DOCUMENT_ROOT"]."/HTMLoader.php");

// テンプレートファイルのファイル名を指定
// 指定がなければ"_tmpl.html"が読み込まれる。今回はわかりやすいようにあえて記述した。
$loader = new HTMLoader("_tmpl.html");

$msg = [
	"title" => "メッセージタイトル",
	"text"	=> "メッセージ内容"
];

$notices = [
	[
		"title" =>	"お知らせ1",
		"text"	=>	"これはお知らせ1の内容です。"
	],
	[
		"title" =>	"お知らせ2",
		"text"	=>	"これはお知らせ2の内容です。"
	],
	[
		"title" =>	"お知らせ3",
		"text"	=>	"これはお知らせ3の内容です。"
	],
];


$loader->setData("site_title",$loader->getTitle());
$loader->setData("page_title","ページタイトル");
$loader->setData("msg",$msg);

$loader->setData("notices",$notices);

$dt = new DateTime();
$loader->setIf("isAprilFools","04/01"===$dt->format("m/d"));

echo $loader->output();
?>
```

###設定ファイル config.json

```json:config.json
{
	"layer": 0,
	"parts_layer": 0,
	"title": "HTMLoaderの使い方"
}
```

<dl>
	<dt>layer</dt>
  <dd>必須。現在の階層。これが0のところが起点となり、そこから下の階層は1ずつ増やしていく。</dd>
  <dt>parts_layer</dt>
  <dd>必須。パーツディレクトリのある階層を入力。</dd>
  <dt>title</dt>
  <dd>サイトタイトル。HTMLoaderのsetTitle($title)で設定できる。</dd>
  <dt>parts_dir</dt>
  <dd>パーツディレクトリ名。指定がなければ"parts"。HTMLoaderのsetPartsDir($name)で設定できる。</dd>
  <dt>parts_head_name</dt>
  <dd>パーツディレクトリ内のヘッドパーツのファイル名。指定がなければ"head.html"。HTMLoaderのsetPartsHeadName($name)で設定できる。</dd>
  <dt>parts_header_name</dt>
  <dd>パーツディレクトリ内のヘッダーパーツのファイル名。指定がなければ"header.html"。HTMLoaderのsetPartsHeaderName($name)で設定できる。</dd>
  <dt>parts_footer_name</dt>
  <dd>パーツディレクトリ内のフッターパーツのファイル名。指定がなければ"footer.html"。HTMLoaderのsetPartsFooterName($name)で設定できる。</dd>
</dl>
</div>

<details>
	<summary>parts/内のファイル</summary>
	<div>
		
###ヘッドパーツ parts/head.html

```html:parts/head.html
<meta charset='utf8'>
<meta name="viewport" content='width=device-width,initial-scale=1'>
```

###ヘッダーパーツ parts/header.html

```html:parts/header.html
<header>
	<h1>{{data.site_title}}</h1>
</header>
<nav>
	<a href="{{$ROOT}}">Top</a>
	<a href="{{$ROOT}}info/">Information</a>
</nav>
```

###フッターパーツ parts/footer.html

```html:parts/footer.html
<footer>
	Copyright (c) 2017 KotaTakeuchi
</footer>
```
</div>
</details>

<details>
	<summary>info/内のファイル</summary>
	<div>
		
###テンプレートファイル_tmpl.html
```html:info/_tmpl.html
<h2>{{data.page_title}}</h2>

<section id="message" class="content">
	<h1>{{data.msg.title}}</h1>
	<p>{{data.msg.text}}</p>
</section>

<section id="functions" class="content">
	<table>
		<!-- each data.functions start -->
		<tr>
			<th>[[data.functions.name]]</th>
			<td>[[data.functions.description]]</td>
		</tr>
		<!-- each data.functions end -->
	</table>
</section>

<script src="{{$ROOT}}/jquery.js"></script>
```

###index.php

```php:info/index.php
<?php 
include_once($_SERVER["DOCUMENT_ROOT"]."/HTMLoader.php");

// テンプレートファイルのファイル名を指定
// 指定がなければ"_tmpl.html"が読み込まれる。今回はわかりやすいようにあえて記述した。
$loader = new HTMLoader("_tmpl.html");

$msg = [
	"title" => "使用している関数",
	"text"	=> "このページ内で使用している関数のご紹介"
];


$functions = [
	[
		"name"				=> 	'setData($Symbol,$Data)',
		"description"	=>	HTMLoader::h(' "{{ data.$Symbol }}" -> $Data. 
												If $Data is hash,  "{{ data.\$Symbol.key }}"" -> $Data["key"]
												If $Data is array, "<!-- each data.$Symbol start --> [[data.$Symbol.key]] <!-- each data.$Symbol end -->" . Repeat count($Data) times. [[data.$Symbol.key]] -> ($Data[0][key],$Data[1][key]...).
												'),
	],[
		"name"				=> 	'setIf($Symbol,$Boolean)',
		"description"	=>	HTMLoader::h('"<!-- if $Symbol --> TRUE! <!-- endif $Symbol -->" If $Boolean is true, Display "TRUE!". '),
	],[
		"name"				=> 	"output()",
		"description"	=>	'Output html string.',
	],[
		"name"				=> 	"getTitle()",
		"description"	=>	'Get title is Written in config.json ',
	],
];


$loader->setData("site_title",$loader->getTitle());
$loader->setData("page_title","Information");
$loader->setData("msg",$msg);

$loader->setData("functions",$functions);

$dt = new DateTime();
$loader->setIf("isAprilFools","04/01"===$dt->format("m/d"));

echo $loader->output();
?>
```

###設定ファイル config.json

```json:info/config.json
{
	"layer": 1,
	"parts_layer": 0,
	"title": "Information | HTMLoaderの使い方"
}
```
</div>
</details>

#何をしているのか
処理の順序をひたすらに列挙してみます。
1. テンプレートファイル(\_tmpl.html)の読み込み。
1. 設定ファイル(config.json)の読み込み。
1. setDataにより記号文字列と値のセットを設定（値として設定できるのは、プリミティブ型・オブジェクト）
1. setIfにより記号文字列と真偽値のセットを設定
1. ヘッダー前・テンプレート前・テンプレート後・フッター後にそれぞれHTMLを差し込みたい場合、addHtmlBeforeTemplate($html)などのメソッドを使用して設定。（同じパーツを使用してもindex.phpによって自由にmetaタグを追加したりできる）
1. パーツディレクトリ内の各種パーツを取得。
1. 各種パーツを以下の順につなげて出力HTMLを作る。
	1. ヘッドパーツ
	1. ヘッダー前HTML
	1. ヘッダーパーツ
	1. テンプレート前HTML
	1. テンプレート
	1. テンプレート後HTML
	1. フッターパーツ
	1. フッター後
1. `<!-- load tmpl 〜〜〜.html -->`という記述があればそこに〜〜〜.htmlの内容を差し込む。（テンプレートファイルの分割が可能になる。）
1. `{{$ROOT}}`記号を自身の階層nから階層0までの相対パスに置換する。（nが1なら../。nが3なら../../../）
1. `<!-- if isOk -->hogehoge<!-- endif isOk -->`文字列がHTML内にあれば、setIfで設定された真偽値によって表示あるいは非表示する。
1. `{{data.☆☆☆}}`、`{{data.◯◯◯.△△△}}`、`<!-- each data.□□□ start--> [[data.□□□.◇◇◇]] <!-- each data.□□□ end-->`などの文字列をそれぞれ置換する。
1. `{{ %〜〜 }}`は関数となっていて、配列の要素数(%COUNT)やJSONエンコード(%JSON_ENCODE)などができる。
1. `<!-- if ◯◯ == △△ --> TRUE <!-- else --> FALSE <!-- endif -->`文字列がHTML内にあれば、真偽値次第で表示を変える。
1. `<!-- if ◯◯ == △△ --> TRUE <!-- endif -->`文字列がHTML内にあれば、真偽値次第で表示あるいは非表示する。
1. `{{ if ◯◯ == △△ ? A : B }}`文字列がHTML内にあれば、三項演算を行い表示を変える。
1. HTMLoaderで特殊文字(値がNullの場合は$NULLという特殊文字に置換される)に置換されたものがあればそれを削除する。
1. 出来上がった文字列を画面にechoする。

#作って良かった点
* <?php echo "〜〜〜"; ?>まみれだったHTMLがすっきり！
* DBから取得したデータを簡単に表示できるようになりました。
* HTMLとPHPが分離したことでデザイナーとプログラマーの分業が進んだ。
* 自由度重視で作ったのでどんなサイトにも意外と使えた。

#悩み
`<!-- if isOk -->hogehoge<!-- endif isOk -->`これはネストできる。
`<!-- if ◯◯ == △△ --> TRUE <!-- else --> FALSE <!-- endif -->`これはネストできない。
いつか直したい。

[追記 2018-01-20]
IF文ネストできるようになりました。
setDataに渡せる値はArray型のみでしたが、オブジェクトにも多分対応しました。


#ライセンス
This software is released under the MIT License.