ユーティリティ(三日完)

はじめに

PHPでWEBプログラミングが楽々出来るユーティリティ(テンプレートライブラリ)を開発したので公開します。

特徴

三日完には以下の特徴があります。

三日完を使うとWEBプログラムが簡単に作成できます(「三日間で完成」が名前の由来です)。金曜の夜に「月曜の朝までに何とか…」と言われても間に合ってしまうところがミソ。

まずはHelloWorldを出力する

プログラミングの話では良く出てくるHelloWorld を表示するプログラム。PHPはHTMLの中にPHPの構文を埋め込むことが出来るので,普通は以下のようなプログラムが紹介されています。

<html>
<?
  print "Hello World."
?>
</html>

これだけでは芸がないので,PHPのサンプルプログラムを載せる場合以下のようにフォントサイズを変更してHTMLの中にプログラムコードを書き込めることを強調しています。

<html>
<?
for( $isize=1; $isize< 8; $isize++) {
  print "<font size=\"$isize\">Hello World.</font><br>";
}
?>
</html>

このプログラムの出力結果は以下のようになります。

Hello World.
Hello World.
Hello World.
Hello World.
Hello World.
Hello World.
Hello World.

これを見てどう感じますか?

おー,すばらしい。

と思いますか?私はそうは思いませんでした。

このPHPプログラムは,プログラムの中にHTMLコードが直接書かれているため以下の欠点があります。

三日完を使うと以下のように記述することで上記出力を得られます。

以下のプログラムにはHTMLのTAGが見あたりません。

<?
require('mthtml.php');
$z = new Html('test-mthtml-0-3.html');
$z->pr('file');
for( $isize=1; $isize< 8; $isize++) {
  $z->pr('font');
}
$z->end();
?>

一見プログラムが長くなってメリットを感じられないと感じるかも知れませんが,このような単純な例でなく実際の開発で使うと三日完のありがたみがよく判ります。

三日完ではPHPの特徴である,HTMLにPHPコードを埋め込むことが出来るという特徴を使用しません。

そして,デザインファイルとプログラムファイルを分離します。

簡単な例(変数の展開)

まずは簡単なサンプルを紹介します。

プログラム test-mthtml-1.php の中身は以下の通りです。

<?
require('mthtml.php');
$z = new Html('test-mthtml-1.html');
$weight = 80;
$z->pr('file');
$z->end();
?>

このプログラムを実行すると以下の出力を得ます。end()をコールしないと何も出力されません。内部で出力をキャッシュしているためです。pr()をコールするとすぐに出力するにはキャッシュを解除するように指定します(後ろの方に指定方法が記述してあります)。

私の体重は80 Kgです。
私の体重はです。
私の体重は"80"Kgです。
私の体重は80Kgです。
私の体重は{80}Kgです。

デザインファイル test-mthtml-1.html は以下の通りです。

<!--KEY_start_file -->
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS">
</head>
<body>
私の体重は$weight Kgです。<br>
私の体重は$weightKgです。<br>
私の体重は"$weight"Kgです。<br>
私の体重は${weight}Kgです。<br>
私の体重は{$weight}Kgです。<br>
</body>
</html>
<!--KEY_end_file -->

特徴はファイルの先頭にある <!--KEY_start_file --> と,ファイルの終わりにある <!--KEY_end_file --> です。これらは囲まれた部分がブロックであることを示し,出力の指示があると,ブロックの開始から終了までを出力します

結果に<!--KEY_start_file -->, <!--KEY_end_file --> は含まれません。

$z->pr('file');

上記コードを実行すると,<!--KEY_start_file --> と <!--KEY_end_file --> で囲まれた領域の文字列を出力します。このときブロックの中に変数があると,それを展開して出力します

2行目の出力が「私の体重はです。」になっているのは,$weiteKg を変数と見なして,この変数の値を埋め込もうとするからです。

変数は $foo, ${foo} の2通りの形式で埋め込むことが出来ますが,PHPが文字列中で変数を展開できる {$foo} という形式は三日完では展開できません。

配列の展開も出来ません

この例ではブロック名に'file'という文字列を利用しましたが,他の文字列を使っても良いです。_start_,_end_は固定でこれらはブロックの開始と終了を識別するために利用しています。ファイル全体を示すブロックの場合_end_を省略することが出来ます。

三日完を使ってHelloWorldを表示する

先ほど三日完を使ってHelloWorldを表示するプログラムは以下の通りになることを書きました。

<?
require('mthtml.php');
$z = new Html('test-mthtml-0-3.html');
$z->pr('file');
for( $isize=1; $isize< 8; $isize++) {
  $z->pr('font');
}
$z->end();
?>

クラスHtmlが読み込むHTMLファイルは以下の通りです。

<!--KEY_start_file -->
<html>
<!--KEY_start_font -->
<font size="$isize">Hello World.</font><br>
<!--KEY_end_font -->
</html>

三日完の利点は,プログラムのことを判らないデザイナの人がこのファイルを見て,このHTML自体は理解できるということです。

このファイルをデザイン担当者に提示し,デザインを変更して貰うことが簡単に出来ます。例えば以下のような要求をされたとします。

これらの要求は全てHTMLファイルの変更だけで済み,プログラムを修正する必要がありません。

Hello World.
Hello World.
Hello World.
Hello World.
Hello World.
Hello World.
Hello World.
Jump to MediaTips top page.

修正したHTMLファイルは以下の通りです。

<!--KEY_start_file -->
<html>
<body background="img/back-rain.gif">
<!--KEY_start_font -->
<font size="$isize"><font color="#FF0000"><b>H</b></font>ello <font color="#FF0000"><b>W</b></font>orld.</font><br>
<!--KEY_end_font -->
Jump to <a href="http://www.mediatips.co.jp">MediaTips</a> top page.<br>
</body>
</html>

プログラムには一切手を入れることなくこれらの修正が出来ることがありがたい。

CSVファイルを表示する

CSVファイルを読み込み,結果をテーブルで表示します。

CSVファイルに限らずデータベースの検索結果などをテーブルで表示する時にも応用できます。

例えば名前と住所が対になった以下のようなCSVファイルがあるとします。

山田一郎,北海道
山田二郎,沖縄県
山田三郎,神奈川県
山田四郎,東京都
鈴木一郎,京都府
鈴木花子,大阪府

これを以下のように出力することを考えます。

名前住所
山田一郎北海道
山田二郎沖縄県
山田三郎神奈川県
山田四郎東京都
鈴木一郎京都府
鈴木花子大阪府

この出力はHTMLファイルにCSVファイルの項目を埋め込んで出力すると表示されます。

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS">
</head>
<body>
<table width="600" border="1" cellpadding="0" cellspacing="0">
<tr><td>名前</td><td>住所</td></tr>
<tr><td>山田一郎</td><td>北海道</td></tr>
<tr><td>山田二郎</td><td>沖縄県</td></tr>
<tr><td>山田三郎</td><td>神奈川県</td></tr>
<tr><td>山田四郎</td><td>東京都</td></tr>
<tr><td>鈴木一郎</td><td>京都府</td></tr>
<tr><td>鈴木花子</td><td>大阪府</td></tr>
</table>
</body>
</html>

まずはデザイン担当者にサンプルデータを埋め込んだ上記HTMLファイルを作ってもらい,これをプログラムからコントロールできるように変換します。

<!--KEY_start_file -->
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS">
</head>
<body>
<table width="600" border="1" cellpadding="0" cellspacing="0">
<tr><td>名前</td><td>住所</td></tr>
<tr><td>$name</td><td>$addr</td></tr>
</table>
</body>
</html>

これで $z->pr('file') とすれば出力できます。しかし,これだと1件しか出力されません。名前と住所は繰り返しになるので,その部分を繰り返しブロックとして定義します。

<!--KEY_start_file -->
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS">
</head>
<body>
<table width="600" border="1" cellpadding="0" cellspacing="0">
<tr><td>名前</td><td>住所</td></tr>
<!--KEY_start_data -->
<tr><td>$name</td><td>$addr</td></tr>
<!--KEY_end_data -->
</table>
</body>
</html>

繰り返しブロックといっても繰り返しを意識するのはプログラマです。HTMLファイルでは単なる1行です。

このファイルをブラウザで見ると以下のように見えます。

名前 住所
$name $addr

CSVファイルを1行読む毎に $name, $addr に名前と住所を設定し,$z->pr('data') を呼び出します。プログラムは以下のようになります。

<?
require('mthtml.php');
$z = new Html('test-mthtml-2.html');
$z->pr('file');
$fp = fopen( 'test-mthtml-2.csv', 'r');
while( (list($name,$addr)=fgetcsv($fp,2048)) !== false) {
  $z->pr('data');
}
fclose($fp);
$z->end();
?>

「データがありません」を表示する。

プログラムでデータを読み込み表示する場合,データが無かったり,ファイルが無かった場合「データがありません」や「データが見つかりません」と表示します。

名前住所
データがありません

こう表示するためには事前に「データがありません」という文言をHTMLファイルの中に埋め込んでおきデータが無い場合そのブロックを $z->pr('nodata') として表示します。HTMLファイルは以下のようにします。

<!--KEY_start_file -->
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS">
</head>
<body>
<table width="600" border="1" cellpadding="0" cellspacing="0">
<tr><td>名前</td><td>住所</td></tr>
<!--KEY_start_data -->
<tr><td>$name</td><td>$addr</td></tr>
<!--KEY_split_nodata -->
<tr><td align="center" colspan="2">データがありません</td></tr>
<!--KEY_end_data -->
</table>
</body>
</html>

このファイルをブラウザで見ると以下のように見えます。

名前 住所
$name $addr
データがありません

プログラムは以下のように書きます。

<?
require('mthtml.php');
$z = new Html('test-mthtml-5.html');
$z->pr('file');
$fp = @fopen( 'test-mthtml-5.csv', 'r');
if( $fp !== false) {
  while( (list($name,$addr)=fgetcsv($fp,2048)) !== false) {
    $cnt++;
    $z->pr('data');
  }
  fclose($fp);
  if( !$cnt) $z->pr('nodata');  // 表示するデータが1件もなかった
}
else {
  $z->pr('nodata'); // ファイルのオープンに失敗したときデータは無いと見なす
}
$z->end();
?>

1行毎に行の背景色を変更し見やすくする

テーブルを表示するときタイトル行の色を変えるだけでなく,1行毎に色を変えると横幅が長い場合見やすく感じるようになります。

名前 住所
山田一郎 北海道
山田二郎 沖縄県
山田三郎 神奈川県
山田四郎 東京都
鈴木一郎 京都府
鈴木花子 大阪府

このような出力を得るための方法には2通りあります。

方法1

行の色を指定する部分を変数にして,プログラムから色を制御する。

HTMLファイルは以下のようにします。

<!--KEY_start_file -->
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS">
</head>
<body>
<table width="600" border="1" cellpadding="0" cellspacing="0">
<tr bgcolor="#CCCCCC"><td>名前</td><td>住所</td></tr>
<!--KEY_start_data -->
<tr bgcolor="$color"><td>$name</td><td>$addr</td></tr>
<!--KEY_split_nodata -->
<tr><td align="center" colspan="2">データがありません</td></tr>
<!--KEY_end_data -->
</table>
</body>
</html>

プログラムでは$color に色を設定します。プログラムは以下の通りです。

<?
require('mthtml.php');
$z = new Html('test-mthtml-5-a.html');
$z->pr('file');
$fp = @fopen( 'test-mthtml-2.csv', 'r');
$acolor = array('#FFFFFF','#E7E6D8');
if( $fp !== false) {
  while( (list($name,$addr)=fgetcsv($fp,2048)) !== false) {
    $cnt++;
    $color = $acolor[$cnt%2];
    $z->pr('data');
  }
  fclose($fp);
  if( !$cnt) $z->pr('nodata');  // 表示するデータが1件もなかった
}
else {
  $z->pr('nodata'); // ファイルのオープンに失敗したときデータは無いと見なす
}
$z->end();
?>

方法2

事前に偶数行,奇数行の色を分けてHTMLファイルに記述しておきます。

この場合HTMLファイルは以下のようになります。

<!--KEY_start_file -->
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS">
</head>
<body>
<table width="600" border="1" cellpadding="0" cellspacing="0">
<tr bgcolor="#CCCCCC"><td>名前</td><td>住所</td></tr>
<!--KEY_start_data0 -->
<tr bgcolor="#FFFFFF"><td>$name</td><td>$addr</td></tr>
<!--KEY_split_data1 -->
<tr bgcolor="#E7E6D8"><td>$name</td><td>$addr</td></tr>
<!--KEY_split_nodata -->
<tr><td align="center" colspan="2">データがありません</td></tr>
<!--KEY_end_data0 -->
</table>
</body>
</html>

偶数行,奇数行で呼び出すKEYの名前を簡単に求めることが出来るようにKEYの名前をdata0, data1 に変更しプログラムは以下のようにします。

<?
require('mthtml.php');
$z = new Html('test-mthtml-5-b.html');
$z->pr('file');
$fp = @fopen( 'test-mthtml-2.csv', 'r');
$acolor = array('#FFFFFF','#EEEEEE');
( $fp !== false) {
  while( (list($name,$addr)=fgetcsv($fp,2048)) !== false) {
    $cnt++;
    $oe = $cnt % 2;
    $z->pr("data$oe");
  }
  fclose($fp);
  if( !$cnt) $z->pr('nodata');  // 表示するデータが1件もなかった
}
else {
  $z->pr('nodata'); // ファイルのオープンに失敗したときデータは無いと見なす
}
$z->end();
?>

どちらの方法でも同じ出力を得ることが出来ます。どちらの方法を選択するのかは好みの問題になりますが,方法2はHTMLファイルを直接ブラウザで見たとき色を反映しているので方法2の方が判りやすいと言えます。

方法1の場合のHTMLファイルはブラウザで見ると以下のように見えます(実際は$nameを書いてある行は黒くなって$name,$addrという文字が見えないので色を薄くして表示しています)。

名前 住所
$name $addr
データがありません

方法2の場合のHTMLファイルはブラウザで見ると以下のように見えます。

名前 住所
$name $addr
$name $addr
データがありません

イメージを表示する。

CSVファイルに男女を示す項目を1:女,2:男として追加します。

山田一郎,北海道,2
山田二郎,沖縄県,2
山田三郎,神奈川県,2
山田四郎,東京都,2
鈴木一郎,京都府,2
鈴木花子,大阪府,1

これを以下のように表示することが出来ます。

名前住所性別
山田一郎北海道
山田二郎沖縄県
山田三郎神奈川県
山田四郎東京都
鈴木一郎京都府
鈴木花子大阪府

性別の部分はイメージファイルを表示しています。女性用にsex1.gif, 男性用にsex2.gif というファイルを作成しておき,HTMLファイルの行は以下のように記述します。

<tr><td>$name</td><td>$addr</td><td><img src="img/sex$sex.gif"></td></tr>

こうすると $sex が1か2で展開されるので,プログラムの実行結果は以下のようになり,性別部分に男女のGIFファイルが表示されることになります。

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS">
</head>
<body>
<table width="600" border="1" cellpadding="0" cellspacing="0">
<tr><td>名前</td><td>住所</td><td>性別</td></tr>
<tr><td>山田一郎</td><td>北海道</td><td><img src="img/sex2.gif"></td></tr>
<tr><td>山田二郎</td><td>沖縄県</td><td><img src="img/sex2.gif"></td></tr>
<tr><td>山田三郎</td><td>神奈川県</td><td><img src="img/sex2.gif"></td></tr>
<tr><td>山田四郎</td><td>東京都</td><td><img src="img/sex2.gif"></td></tr>
<tr><td>鈴木一郎</td><td>京都府</td><td><img src="img/sex2.gif"></td></tr>
<tr><td>鈴木花子</td><td>大阪府</td><td><img src="img/sex1.gif"></td></tr>
</table>
</body>
</html>

実際のプログラムは test-mthtml-5.php に性別の入力を追加しただけです。

<?
require('mthtml.php');
$z = new Html('test-mthtml-6.html');
$z->pr('file');
$fp = @fopen( 'test-mthtml-6.csv', 'r');
if( $fp != false) {
  while( (list($name,$addr,$sex)=fgetcsv($fp,2048)) !== false) {
    $cnt++;
    $z->pr('data');
  }
  fclose($fp);
  if( !$cnt) $z->pr('nodata');
}
else {
  $z->pr('nodata');
}
$z->end();
?>

入力したデータを検索して表示する。

CSVファイルの場合,件数が多いとテーブル表示しても見にくいので,絞り込み機能を付けるとかいう話になる。

名前で絞り込み(検索)するようにプログラムを修正してみます。

まずはHTMLファイルに検索語を入力する欄を設ける必要があります。HTMLファイルを以下のように修正します。

<!--KEY_start_file -->
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS">
</head>
<body>
<form action="test-mthtml-7.php" method="post">
<input type="text" name="word" value="$word"><input type="submit" name="btnGO" value="検索">
<table width="600" border="1" cellpadding="0" cellspacing="0">
<tr><td>名前</td><td>住所</td><td>性別</td></tr>
<!--KEY_start_data -->
<tr><td>$name</td><td>$addr</td><td><img src="img/sex$sex.gif"></td></tr>
<!--KEY_split_nodata -->
<tr><td align="center" colspan="3">データがありません</td></tr>
<!--KEY_end_data -->
</table>
</form>
</body>
</html>

名前を検索する部分の見栄は以下のようになります。

プログラムはユーザの入力を受け取り,名前と比較して行を出力するかどうか判断します。

<?
require('mthtml.php');
expand_var('word');
$z = new Html('test-mthtml-7.html');
$z->pr('file');
$fp = @fopen( 'test-mthtml-7.csv', 'r');
if( $fp != false) {
  while( (list($name,$addr,$sex)=fgetcsv($fp,2048)) !== false) {
    if( $word == '' || strpos( $name, $word) !== false) {
      $cnt++;
      $z->pr('data');
    }
  }
  fclose($fp);
  if( !$cnt) $z->pr('nodata');
}
else {
  $z->pr('nodata');
}
$z->end();
?>

例えば「一郎」を検索すると表示は以下のようになります。

名前住所性別
山田一郎北海道
鈴木一郎京都府

出力データが多い場合「前へ」,「後ろへ」を表示する

検索結果の行数が多いとき,以下のように「前へ」,「後ろへ」を表示したいことがあります。

検索結果を一度に 表示します。
全部で28件検索しました。6件目から5件表示します。
前5件 後ろ5件
名前 住所 性別
山田六郎 沖縄県
山田七郎 神奈川県
山田八郎 東京都
山田九郎 北海道
山田十郎 沖縄県

「前へ」,「後ろへ」の部分を表示するだけならばそれほど難しいことではありません。1件目から表示しているのに「前へ」が表示されるのは違和感があるので,1件目から表示する場合は空白を表示するようにします。こういう制御は三日完を使うと簡単に出来ます。プログラムを以下に示します。

<?
require('mthtml.php');
expand_var('word,p_act,st,n,d');
if( $n <= 0) $n = 5;            // 一度に表示する件数(デフォルト)
if( $d == 1) $st -= $n;
else if( $d == 2) $st += $n;
if( $st <= 0) $st = 0;
# $st から $n 件表示する
$st1 = $st+1;
$z = new Html('test-mthtml-7-2.html');
${"sel_n$n"} = 'selected';      // 件数の候補値
$z->pr('file');
$fp = @fopen( 'test-mthtml-7-2.csv', 'r');
$ndisp = 0;
$a = array();
if( $fp != false) {
  // 最初に検索件数を全て求める。
  while( (list($name,$addr,$sex)=fgetcsv($fp,2048)) !== false) {
    if( $word == '' || strpos( $name, $word) !== false) {$numFind++;}
  }
  fclose($fp);
  $z->pr('result'); // 検索結果は必ず表示する, 実はこの行は無くても表示してくれる
  if( $numFind > 0) {
    $z->pr('find');
    if( $st > 0) $z->pr('prev'); else $z->pr('noprev');
    if( $st+$n < $numFind) $z->pr('next'); else $z->pr('nonext');
  }
  $fp = @fopen( 'test-mthtml-7-2.csv', 'r');
  while( (list($name,$addr,$sex)=fgetcsv($fp,2048)) !== false) {
    if( $word == '' || strpos( $name, $word) !== false) {
      if( $cnt >= $st && $cnt < $st+$n) {
        $z->pr('data');
      }
      $cnt++;
    }
  }
  fclose($fp);
  if( !$cnt) $z->pr('nodata');
}
else {
  $z->pr('nodata');
}
$z->end();
?>

HTMLファイルは「前へ」,「後ろへ」部分を表示するしないを制御できるようにブロックとして定義します。

<!--KEY_start_file -->
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS">
</head>
<body>
<form action="test-mthtml-7-2.php" method="post">
<input type="hidden" name="p_act" value="1">
<input type="text" name="word" value="$word"><input type="submit" name="btnGO" value="検索">
検索結果を一度に
<select name="n" onChange="javascript:document.location='test-mthtml-7-2.php?word=$urlenc_word&st=$st&d=0&n='+this.value">
<option value="2" $sel_n2>2件
<option value="3"  $sel_n3>3件
<option value="5"  $sel_n5>5件
<option value="10"  $sel_n10>10件
</select>表示します。
$e_word
<!--KEY_start_result 出力を指示されないブロックは出力されない -->
<!--KEY_start_find -->
<br>
全部で${numFind}件検索しました。${st1}件目から${n}件表示します。<br>
<!--KEY_end_find -->
<!--KEY_start_prev -->
<a href="test-mthtml-7-2.php?word=$urlenc_word&st=$st&n=$n&d=1">前${n}件</a>
<!--KEY_split_noprev -->
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<!--KEY_end_prev -->

<!--KEY_start_next -->
<a href="test-mthtml-7-2.php?word=$urlenc_word&st=$st&n=$n&d=2">後ろ${n}件</a>
<!--KEY_split_nonext -->
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<!--KEY_end_next -->

<table width="600" border="1" cellpadding="0" cellspacing="0">
<tr><td>名前</td><td>住所</td><td>性別</td></tr>
<!--KEY_start_data -->
<tr><td>$name</td><td>$addr</td><td><img src="img/sex$sex.gif"></td></tr>
<!--KEY_split_nodata -->
<tr><td align="center" colspan="3">データがありません</td></tr>
<!--KEY_end_data -->
</table>
<!--KEY_end_result -->
</form>
</body>
</html>

入力が無い場合エラーメッセージを表示する

上記検索例では検索語の入力がないとき全件表示していたが,入力がないときは以下のようにエラーメッセージを表示したいことがあります。


検索語を入力してください

入力が無い場合入力を促すメッセージを表示し,検索結果を表示しないようにプログラムを修正します。

<?
require('mthtml.php');
expand_var('word,p_act');
$z = new Html('test-mthtml-7-1.html');
if( $word == '') {
  if( $p_act != 0) $e_word = html_br_error('検索語を入力してください');
  $z->pr('file');
  $z->end();
  exit(0);
}
$z->pr('file');
$z->pr('result'); // 検索結果は必ず表示する, 実はこの行は無くても表示してくれる
$fp = @fopen( 'test-mthtml-7.csv', 'r');
if( $fp != false) {
  while( (list($name,$addr,$sex)=fgetcsv($fp,2048)) !== false) {
    if( $word == '' || strpos( $name, $word) !== false) {
      $cnt++;
      $z->pr('data');
    }
  }
  fclose($fp);
  if( !$cnt) $z->pr('nodata');
}
else {
  $z->pr('nodata');
}
$z->end();
?>

p_act はHTMLファイルからリンクされた場合と,PHPの出力から来た場合を区別するためにPHPの出力にHIDDEN項目として入れておきます。HTMLからリンクされたときエラーメッセージが出るのは違和感があるので,これは三日完を使ったときの常套手段となります。

HTMLはそれほど修正しなくて良いです。検索語が入力されないとき,検索結果を表示するテーブル自体を表示させなくするようにテーブルをブロックで囲みます。エラーメッセージを$e_wordで表示できるようにします。

<!--KEY_start_file -->
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS">
</head>
<body>
<form action="test-mthtml-7-1.php" method="post">
<input type="hidden" name="p_act" value="1">
<input type="text" name="word" value="$word"><input type="submit" name="btnGO" value="検索">$e_word
<!--KEY_start_result 出力を指示されないブロックは出力されない -->
<table width="600" border="1" cellpadding="0" cellspacing="0">
<tr><td>名前</td><td>住所</td><td>性別</td></tr>
<!--KEY_start_data -->
<tr><td>$name</td><td>$addr</td><td><img src="img/sex$sex.gif"></td></tr>
<!--KEY_split_nodata -->
<tr><td align="center" colspan="3">データがありません</td></tr>
<!--KEY_end_data -->
</table>
<!--KEY_end_result -->
</form>
</body>
</html>

この例では検索語が入力されないときのエラーメッセージをプログラムで設定するようにしたが,事前にHTMLに「見つかりません」と同じように埋め込んでおきブロックで囲むことも出来ます。ただエラーの種類はプログラムに依存することが多いので,プログラムで設定するようにした方が良いです。

入力チェック

HTMLで利用できる入力コントロールを使ってデータを入力,また入力にエラーがあったときそれを表示するための手法を示します。入力項目にエラーがあると次ページでエラーメッセージだけを表示しているWEBサイトもありますが,エラーメッセージは入力コントロールの直下に表示したほうが判りやすいです。

コントロール 用途
text 文字列を入力する
list 選択肢の中から1つを選ぶ
radio 選択肢の中から1つを選ぶ
checkbox 選択肢の中から複数を選ぶ

上記コントロールを使った入力画面のサンプルは以下の通りです。

名前※
住所※
性別※
好きな食べ物※ ラーメン カレー うどん その他
ご意見
※は必須入力です。

住所は都道府県を選択します。何もしないで入力チェックボタンを押すと以下のような画面になります。入力コントロール直下にエラーメッセージを表示します。

名前※
必ず入力してください
住所※
必ず入力してください
性別※
必ず入力してください
好きな食べ物※ ラーメン カレー うどん その他
最低一つ指定してください
ご意見
※は必須入力です。

ここで,名前以外を設定して登録ボタンを押すと以下のような画面になります。

名前※
必ず入力してください
住所※
性別※
好きな食べ物※ ラーメン カレー うどん その他
ご意見
※は必須入力です。

入力した値が次の画面にも引き継がれているのが判ります。エラーメッセージを同じ画面に表示しても入力した項目が消えてしまっては意味がありません。

これを実現するプログラムは以下の通りです。

<?
require('mthtml.php');
expand_var('name,addr,sex,food,opinion,p_act');
$prefectures = array('北海道','青森県','岩手県','宮城県','秋田県','山形県','福島県','茨城県',
'栃木県','群馬県','埼玉県','千葉県','東京都','神奈川県','新潟県','富山県','石川県',
'福井県','山梨県','長野県','岐阜県','静岡県','愛知県','三重県','滋賀県','京都府',
'大阪府','兵庫県','奈良県','和歌山県','鳥取県','島根県','岡山県','広島県','山口県',
'徳島県','香川県','愛媛県','高知県','福岡県','佐賀県','長崎県','熊本県','大分県',
'宮崎県','鹿児島県','沖縄県');
$asex = array(1=>'女',2=>'男');
$afood = array(1=>'ラーメン',2=>'カレー',3=>'うどん',4=>'その他');
if( !isset($food)) $food = array();
if( $p_act != 0) {
  if( $name == '') $e_name = html_br_error('必ず入力してください');
  if( $addr == '') $e_addr = html_br_error('必ず入力してください');
  if( $sex  == '') $e_sex  = html_br_error('必ず入力してください');
  else ${"sel_sex$sex"} = 'checked';
  $strsex = $asex[$sex];
  if( count($food) == 0) $e_food = html_br_error('最低一つ指定してください');
  if( !html_is_error()) $msg = html_encode("必須項目が全て入力されました<br>");
}
$z = new Html('test-mthtml-8.html');
$z->pr('file');
foreach( $prefectures as $prefec) {
  if($addr == $prefec) {$sel_prefec = 'selected'; } else {$sel_prefec = '';}
  $z->pr('prefec');
}
foreach( $afood as $ifood => $sfood) {
  if( in_array($ifood,$food)) $sel_food = 'checked'; else $sel_food = '';
  $z->pr('food');
}
$z->end();
?>

ラジオボタン,チェックボックスの展開

上記例で好きな食べ物を住所の上に持っていくと,pr('prefec')とpr('food')の出力順序を変更しなければなりません。そこで,事前に好きな食べ物と住所を展開しておく以下のコードを利用します。

<?
require('mthtml.php');
expand_var('name,addr,sex,food,opinion,p_act');
$prefectures = array('北海道','青森県','岩手県','宮城県','秋田県','山形県','福島県','茨城県',
'栃木県','群馬県','埼玉県','千葉県','東京都','神奈川県','新潟県','富山県','石川県',
'福井県','山梨県','長野県','岐阜県','静岡県','愛知県','三重県','滋賀県','京都府',
'大阪府','兵庫県','奈良県','和歌山県','鳥取県','島根県','岡山県','広島県','山口県',
'徳島県','香川県','愛媛県','高知県','福岡県','佐賀県','長崎県','熊本県','大分県',
'宮崎県','鹿児島県','沖縄県');
$asex = array(1=>'女',2=>'男');
$afood = array(1=>'ラーメン',2=>'カレー',3=>'うどん',4=>'その他');
if( !isset($food)) $food = array();
if( $p_act != 0) {
  if( $name == '') $e_name = html_br_error('必ず入力してください');
  if( $addr == '') $e_addr = html_br_error('必ず入力してください');
  if( $sex  == '') $e_sex  = html_br_error('必ず入力してください');
  else ${"sel_sex$sex"} = 'checked';
  $strsex = $asex[$sex];
  if( count($food) == 0) $e_food = html_br_error('最低一つ指定してください');
  if( !html_is_error()) $msg = html_encode("必須項目が全て入力されました<br>");
}
$z = new Html('test-mthtml-8-expand.html');
$z->expandselected('prefec', $prefectures, 'p_prefec', 'sel_prefec', 'str_prefec', $addr);
$z->expandchecked('food', $afood, 'ifood', 'sel_food', 'sfood', $food);
$z->pr('file');
$z->end();
?>

expandselect()の引数はKEY名,候補値の入った配列,値,選択状態,表示文字列を指定します。

HTMLファイルは以下のようにします。

<tr><td>住所※</td><td><select name="addr"><option value="">選択して下さい
<!--KEY_start_prefec -->
<option value="$p_prefec" $sel_prefec>$str_prefec
<!--KEY_end_prefec -->
</select>$e_addr
</td></tr>

入力/確認/処理完了

WEBプログラミングでは入力,入力データの確認,実際の処理をしてから完了メッセージを出力するというプログラミングが多い。

上記入力チェックプログラムを改良し入力,確認,処理完了を作ります。

入力画面と入力チェックは上記画面と同じです。以下のような入力確認画面を表示します。

以下の情報で処理します。
名前 田所義照
住所 神奈川県
性別
好きな食べ物 ラーメン,うどん
ご意見 三日完って
どういう意味?

処理実行ボタンを押されたら実際にデータベースに登録するとかして結果を表示します。

処理を完了しました。
田所義照さんは神奈川県に住み男で好きな食べ物はラーメン,うどんで以下のような意見を持っている。
三日完ってどういう意味?

この処理をするプログラムは以下の通りです。出力するHTMLファイルを変更することで,1つのプログラムで全て処理することが出来ます。

<?
require('mthtml.php');
expand_var('name,addr,sex,food,opinion,p_act');
$prefectures = array('北海道','青森県','岩手県','宮城県','秋田県','山形県','福島県','茨城県',
'栃木県','群馬県','埼玉県','千葉県','東京都','神奈川県','新潟県','富山県','石川県',
'福井県','山梨県','長野県','岐阜県','静岡県','愛知県','三重県','滋賀県','京都府',
'大阪府','兵庫県','奈良県','和歌山県','鳥取県','島根県','岡山県','広島県','山口県',
'徳島県','香川県','愛媛県','高知県','福岡県','佐賀県','長崎県','熊本県','大分県',
'宮崎県','鹿児島県','沖縄県');
$asex = array(1=>'女',2=>'男');
$afood = array(1=>'ラーメン',2=>'カレー',3=>'うどん',4=>'その他');
if( !isset($food)) $food = array(); else if( !is_array($food)) $food = explode(';', $food);
$ifoods = implode(';',$food);
if( $p_act != 0) {
  if( $name == '') $e_name = html_br_error('必ず入力してください');
  if( $addr == '') $e_addr = html_br_error('必ず入力してください');
  if( $sex  == '') $e_sex  = html_br_error('必ず入力してください');
  else ${"sel_sex$sex"} = 'checked';
  $str_sex = $asex[$sex];
  $str_addr = $prefectures[$addr];
  if( count($food) == 0) $e_food = html_br_error('最低一つ指定してください');
}
if( html_is_error() || $p_act == 0) $z = new Html('test-mthtml-8-1.html');
else if( $p_act == 1) $z = new Html('test-mthtml-8-1-confirm.html');
else if( $p_act == 2) $z = new Html('test-mthtml-8-1-end.html');
$favfood = implode( ',', array_map('favfood',$food));
if( $p_act == 2 && $opinion == '') $opinion = '意見なし';
$z->expandselected('prefec', $prefectures, 'p_prefec', 'sel_prefec', 'str_prefec', $addr);
$z->expandchecked('food', $afood, 'ifood', 'sel_food', 'sfood', $food);
$z->pr('file');
$z->end();
function favfood( $ifood) {
  global $afood;
  return $afood[$ifood];
}
?>

ユーザ入力

ユーザ入力方法

ブラウザからSUBMITされたデータを入力するには以下のようにします。

expand_var('foo');

これで,$foo に値が設定されます。カンマ区切りで複数の変数を設定できます。

expand_var('foo,bar');

expand_var()はグローバル変数を設定します。値を取得したいだけの場合は get_input_variable() を使います。

function () {
  $foo = get_input_variable( 'foo');
  ・・・
}

expand_var() と違いget_input_variable() は引数に指定できる名前は1つに限ります。

入力データはどこから来たか?

三日完ではPOST,GET,COOKIEの値を区別しません。そのためexpand_var() ではデータがSUBMITされたのか,URLの引数で指定されたのかを意識する必要がありません。

expand_var('foo');

このコードはまず $_POST[foo] が設定されているかどうか調べ,設定されていれば $foo に $_POST[foo] を設定します。

設定されていなければ, $_GET[foo] が設定されているかどうか調べ,設定されていれば $foo に $_GET[foo] を設定します。

設定されていなければ, $_COOKIE[foo] が設定されているかどうか調べ,設定されていれば $foo に $_COOKIE[foo] を設定します。

POST,GET,COOKIEのどれを優先するかは議論のあるところだが,三日完ではこの順に取得することにしました。

setcookie() で設定するとき文字コードの変換の問題があるので,html_setcookie()を使うようにします。

CSVファイルから検索するプログラムを以下のようにURLで指定しても「一郎」の検索結果を表示します。

http://utl.mediatips.co.jp/test-mthtml-7.php?word=%88%EA%98Y

入力データに入っているゴミを除去する

名前の入力欄を用意したとき,名前の前後に空白が入っていてそのままデータベースに登録してしまったりするとシステムとしては好ましい状況ではありません。

項目毎に前後の空白を調べ除去するのも面倒です。expand_var()は入力データの前後に付いている空白,TAB,改行を除去します(trim()を使って除去している)。全角の空白は除去しません(この仕様は将来変更するかも知れません)。
ブラウザから利用される場合は無いことですが,ツールを使って直接アクセスされたときバイナリデータが入力される場合があります。バイナリデータ(空白未満の改行を除くコード)は自動で削除します。これはセキュリティ強化のためです。

この仕様で殆ど問題ないが,textarea に入力した複数行の文字列で最初に空行を入力した場合それを有効にしたままにしたいことがあります。

そのような場合 $notrim_POST に名前をカンマ区切りで設定します。

<?
require('mthtml.php');
$notrim_POST = 'msg';
・・・
expand_var('msg');

入力データの書式チェック

入力データの書式チェックはどこにでも出現します。使いそうな書式チェックを作成しました。以下のように使用します。$e_tel にエラーメッセージが代入されます。

html_chkitem( $e_tel, $tel, 'null,tel');
if( html_is_error()) { /* エラーがあった */; }
指定文字 チェック内容 メッセージ
null 空文字列ならエラー 必ず指定してください
number 数字のみ可能 数字のみ入力してください
mail メールアドレスが正しいかどうか メールアドレスが正しくありません
tel 電話番号が正しいかどうか。
数字と'-'を使っていればOKと見なします。
電話番号が正しくありません
fax FAX番号が正しいかどうか。
数字と'-'を使っていればOKと見なします。
FAX番号が正しくありません
zip 999または999-9999形式の郵便番号かどうか調べる 郵便番号が正しくありません
n-m 長さチェック。n,mには数字を指定する。
例)3-6
3文字以上6文字以下の文字列を入力してください

特殊変数

HTMLファイル内に展開する変数に関して以下のようなルールがあります。

変数名 解説
foo_br $fooを参照し,改行を<br>に変換して出力する。空文字列は&nbsp; に変換する。
foo_null $fooを参照し,空文字列を&nbsp; に変換しない。
urlenc_foo $fooを参照し文字コードの変換とurlencode() して出力する。
e_foo, err_foo, error_foo 空文字列を&nbsp; に変換しない。エラーメッセージを出力するときに使用する。
sel_foo 空文字列を&nbsp; に変換しない。コンボボックス,ラジオボタン,チェックボックスの選択状態を示すために使用する。

文字コードの問題

三日完では以下の種類の文字コードを分けて指定出来ます。

変数名 デフォルト値 用途
$Html_code_file EUC-JP コンストラクタで指定するファイル名の文字コード。
$Html_code_output Shift_JIS 読み込んだHTMLのヘッダで指定してある文字コードを指定する。
<meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS">
$Html_code_internal EUC-JP mb_internal_encoding()で指定した文字列。
$Html_code_input Shift_JIS ブラウザから送られてくるデータの文字コード。ブラウザからの応答なので,ブラウザに出力した文字コードを割り当てる。

デフォルトはWEBサーバにUNIX(Apache)を,デザインファイル(HTMLファイル)はWindowsで作成しWEBサーバにEUCでアップロードすることを想定しています。

デザインファイル(HTMLファイル)をWEBサーバにShift_JISでアップロードした場合,以下のように設定します。

<?
$Html_code_file = 'EUC-JP';
require('mthtml.php');
・・・

オプション

以下のようにオプションを指定することが出来ます。

$config[cache] = false;
$z = new Html( $fn, $config);

指定できる項目は以下の通りです。

項目 デフォルト値 解説
nokey false pr('BlockName') としたときBlockName が存在しなくてもエラーを表示しないかどうか?
true:表示しない
false:表示する
BlockName が存在するかどうか確認するには is_exist('BlockName') を使用する。
cache true 出力をキャッシュして最後に一気に出力するかどうか?
true:キャッシュして最後に出力する。
false:pr()が呼ばれる毎に出力する。
output なし 画面でなくファイルに出力する。
新着情報等をDBから取得してファイルを作成し,FTPでアップロードするという使い方が出来る。
'#'を指定した場合ファイルにも出力せずメモリ内にキャッシュする。end()でキャッシュしていた展開データを取得する。
nohtml false true:変数に含まれる文字を&lt;&gt;等への変換を行わずそのまま展開します。主にメールを送信するときに使います。
false:&amp;&gt;&lt;&quot;に変換する。

outputとnohtmlを組み合わせてメールファイルのテンプレートを作っておき展開した文章をmb_send_mail()で送信できます。

$z = new Html( "mail-uketuke.txt", array('output'=>'#','nohtml'=>true);
$z->pr('...');
...
$body = $z->end();
mb_send_mail( $to, "お問い合わせありがとうございます", "$body", "From: $from", "-f $mail_syserror");

ちょっと難しい話

三日完を本格的に使用したいと思う場合以下のことも是非知って置いて貰いたい。

最後には必ずend()をコールすること

三日完では出力を指示されたとき,確定されたブロックまでしか出力しません。

Hello World を出力するプログラムで end() をコールしないと何も表示されません。

<?
require('mthtml.php');
$z = new Html('test-mthtml-0-3.html');
$z->pr('file');
for( $isize=1; $isize< 8; $isize++) {
  $z->pr('font');
}
$z->end();  ← このend()を省くと何も表示されません
?>

親ブロックの表示

以下のような構成のブロックがあるとします。

<!--KEY_start_foo -->
This is block foo1.
<!--KEY_start_bar -->
This is block bar.
<!--KEY_end_bar -->
This is block foo2.
<!--KEY_end_foo -->

このとき以下のコードを実行すると,

$z->pr( 'foo');
$z->end();

出力は以下のようになります。

This is block foo1.
This is block foo2.

barは明示されないので出力されません。barを明示したコードでは,

$z->pr( 'foo');
$z->pr( 'bar');
$z->end();

出力は以下のようになります。

This is block foo1.
This is block bar.
This is block foo2.

foo を明示しないで,bar だけを明示したコードでは,

$z->pr( 'bar');
$z->end();

出力は以下のようになります。foo の出力は明示されないが,bar がfoo の中に入っているので,bar を出力するにはfoo を出力することが必要になります。

This is block foo1.
This is block bar.
This is block foo2.

ブロックには親子関係があり,子ブロック出力するように指定されたら親ブロックを出力します。既に親ブロックが出力されている場合二重に出力しません。

過去ブロックを表示できない

以下のような構成のファイルがあるとします。

<!--KEY_start_foo -->
This is block foo.
<!--KEY_end_foo -->
<!--KEY_start_bar -->
This is block bar.
<!--KEY_end_bar -->

このとき以下の呼び出しでエラーが表示されます。

$z->pr( 'foo');
$z->pr( 'bar'); // 過去ブロックを表示できないというエラーメッセージが表示される。

三日完では同じレベルにある,以前出現したブロックを表示することが出来ません。

エラーを表示させないためにはブロックの構成を以下のように変更します。

<!--KEY_start_foo -->
This is block foo.
<!--KEY_split_bar -->
This is block bar.
<!--KEY_end_bar -->

split は同じレベルのブロックであることを明示します。

もう一つの方法として上位ブロックを定義し,上位ブロックを再度呼び出します。

<!--KEY_start_parent -->
<!--KEY_start_foo -->
This is block foo.
<!--KEY_end_foo -->
<!--KEY_start_bar -->
This is block bar.
<!--KEY_end_bar -->
<!--KEY_end_parent -->

呼び出しは以下のようにします。

$z->pr( 'parent');
$z->pr( 'foo');
$z->pr( 'parent');
$z->pr( 'bar');

end('BlockName')をコールしなければならない場合

普通はend('BlockName') をコールする必要はありません。三日完が自動で判断し出力してくれるからです。

しかし,以下のようなブロックの構成の時は例外です。

<!--KEY_start_foo -->
$foo
<!--KEY_start_msg -->
This is sample
<!--KEY_end_msg -->
$bar
<!--KEY_end_foo -->

このとき以下のプログラムを実行すると $bar の出力が望むものではなくなります。

while( (list($foo,$bar)=get_foobar()) !== false) {
  $z->pr( 'foo');
  if( is_this_output()) $z->pr( 'msg');
}

例えば最初のgetで ($foo,$bar)=('AAA','BBB'),次のgetで ($foo,$bar)=('CCC','DDD') とします。

このとき出力は以下のようになり,'BBB'が出力されません。

AAA
This is sample
DDD    ←ここは 'BBB' を出力して欲しいが, 2回目の $z->pr('foo') の呼び出しの時の $bar の値が展開された
CCC
This is sample
DDD

これは end_bar から end_foo までのブロックを出力することが明示されていないため,2回目の start_foo の呼び出しで,end_bar から end_foo のブロックが出力されるからです。このとき既に$bar の値は新しい値がgetされているので,出力は 'DDD' になります。

これを避けるため以下のように end('foo') を明示します。

while( (list($foo,$bar)=get_xxx()) !== false) {
  $z->pr( 'foo');
  if( is_this_output()) $z->pr( 'msg');
  $z->end( 'foo'); // ここで end_bar から end_foo までのブロックが出力される
}

こうすることで以下のように'BBB'が出力されます。

AAA
This is sample
BBB      ← $z->end('foo') を呼び出したときの $bar の値が展開される
CCC
This is sample
DDD

空文字の出力

IEの場合テーブルに空文字列を表示すると見た目が悪いです。

空文字列""を出力すると=>
半角の空白" "を出力すると=>
全角の空白" "を出力すると=>  
&nbsp;を出力すると=>  

このため三日完では空文字列を出力するとき&nbsp;に置き換えています。但し,以下のように変数を展開するところでは,""を&nbsp;に置き換えることは望ましくないです。

<input type="text" name="foo" value="$foo">

そこで,変数名の直前の文字が「"」,「=」の場合と行頭の場合はそのまま空文字を出力し,それ以外は&nbsp; に変換して出力します。

変数名が「e_」,「err_」,「error_」で始まるときも空文字列はそのまま出力します。

HTMLのTAGをプログラムから出力する

以下のように変数にHTMLのTAGを含む文字列を設定すると,

$foo = "<b>Hello World</b>";

そのまま出力されHTMLのTAGとして機能しません。

<b>Hello World</b>

HTMLのTAGとして機能させるためには html_encode() を呼び出して変数に設定します。

$foo = html_encode( "<b>Hello World</b>");

これで $foo を埋め込んだ部分でBoldで出力されます。

Hello World

テキストエリアに変数を展開する

三日完では以下のような場合$opinion が空文字列の場合

<textarea name="opinion" rows="5" cols="40">$opinion</textarea>

以下のように展開します。

<textarea name="opinion" rows="5" cols="40">&nbsp;</textarea>

見た目は何も表示されていないので判らないのですが,これをプログラムが受け取ると次は &nbsp; をエスケープして出力するので

<textarea name="opinion" rows="5" cols="40">&amp;nbsp;</textarea>

と出力されます。こうなると見た目も &nbsp; になってしまいます。そこでtextareaに変数を割り当てるときは,以下のように変数が行頭に来るようにします。

<textarea name="opinion" rows="5" cols="40">
$opinion</textarea>

行頭の変数は空文字列を変換しないのでプログラムへの入力も空文字列のままになります。

サフィックス _null を付けることでも同じことが可能です。

<textarea name="opinion" rows="5" cols="40">$opinion_null</textarea>

テキストエリアから入力した文字列をそのまま出力すると改行は改行のまま出力されます。ブラウザは改行を無視するので入力データをそのまま出力するのはダメで,改行を<br>に変換して出力しなければなりません。

この作業をプログラマが意識して行うのは面倒なので,HTML内で変数名の後ろに「_br」を付けると「_br」を除いた変数名の値を出力します。

<table>
<tr><td>ご意見</td><td>$opinion_br</td></tr>
</table>

このように書いた場合 $opinion の値を表示します。その場合改行は<br>に変換して表示します。

abort()とbye()

あるべきファイルが無かったりして処理を継続できないような場合以下のようにabort()を呼び出します。

abort( "$fn が見つかりません");

以下のように表示されます。

障害発生

システム障害が発生しました。担当者に連絡下さるようにお願いします。
原因:../data/q01.txt が見つかりません

abort()は画面にメッセージを表示し終了します。$mail_syserror に指定したメールアドレスに同じ内容のメッセージをサーバの情報と共に送信します。

三日完を使う場合は必ず $mail_syserror を自分のメールアドレスに変更してください

bye() はabort() と同じように表示するが,メールを送信しません。ユーザの入力にエラーの原因が依存するような場合abort() でなく,bye() を使ってメッセージを表示します。

エラー発生

エラーが発生しました。
原因:../data/q01.txt が見つかりません

abort()はプログラムに起因するエラーに,bye()はユーザが原因のエラーに利用してください。

すぐに出力する

$z->pr('foo') によって出力した後,重大な障害が見つかりabort()またはbye()を実行するとそれまでに出力済みのデータがあるので,そこに障害情報を出力してもうまく出力されない場合があります。特にリスト(コンボボックス)に出力中の場合など障害情報が全く見えなくなります。そこで三日完ではpr()関数が呼ばれるとHTMLデータをすぐに出力するのでなく,一旦出力をキャッシュしておき,end()が呼ばれると一気に出力するようにしました。

$z = new Html( "$fn");
$z->pr('foo'); <= この出力はキャッシュされているので,まだブラウザに表示されない
if( ...) abort( "ファイルが見つからない"); <= 上のpr('foo')の出力は廃棄され,このエラーだけが表示される

ただ時間が掛かる処理をする場合,途中出力が無くてブラウザとの接続が切れることがあるので,そのような場合キャッシュをOFF(false)にします。

$z = new Html( "$fn", array('cache'=>false));

ファイルに出力する

直接画面に出力するのでなく,ファイルに出力したいことがあります。例えば,静的なトップページを作成するような場合が考えられます。CSVファイルやDBに入っている新着情報をindex.htmlに埋め込み,FTPでアップロードするなどの応用が考えられます。出力するファイル名はフルパスを指定してください。

$config[output] = "$dir_output/index.html";
$z = new Html( "$fn", $config);
$z->pr('foo');

ちょっとしたテクニック

入力/確認/処理完了

WEBプログラミングでは1つの処理が入力画面,確認画面,処理完了画面で構成されることが多い。

例えば,ユーザ登録画面は,ユーザ情報の入力,ユーザ情報の確認,ユーザ情報のDBへの登録というような処理です。

これを1つのプログラムで処理するためにどの画面からの入力であるか確認する必要があります。

<form action="foo.php" method="POST">
<input type="hidden" name="p_act" value="1">
・・・
</form>

p_actに入力画面では1,確認画面では2 を設定しておくと以下のようなプログラムを作成することが出来ます。

expand_var('p_act');
if( $p_act != 0) {
   エラーチェック,エラーがあれば $e_item = html_br_error("エラーメッセージ");
}
if( html_is_error() || $p_act == 0) $z = new Html( "入力画面");
else if( $p_act == 1) $z = new Html( "確認画面");
else if( $p_act == 2) $z = new Html( "処理完了画面");

エラーチェックは入力画面からだけで良いので以下のようにしても良いのですが,実はこの方法は間違いです。上に示したように0以外はエラーチェックをすべきです。

if( $p_act == 1) {
   エラーチェック,エラーがあれば $e_item = html_br_error("エラーメッセージ");
}

あなたの作ったプログラムにブラウザを使わないでアクセスしてくるユーザがいるかも知れません。その場合エラーチェックを回避するために直接 p_act=2 を指定する事が可能です。確実にエラーチェックをしてから処理を行うようにしましょう。

ブロックにコメントを付ける

<!--KEY_start_result --> とするだけでなく,以下のようにコメントを付けて置くと判りやすい。

<!--KEY_start_result この部分は検索結果を表示しますが,丸ごと表示しない場合があります -->

デザインファイルを置く場所

プログラムが読み込むHTMLファイルはユーザ(ブラウザ)に見えないように別ディレクトリに置きます。PHPプログラムはHTMLと同じディレクトリに置いて良いので,同じ場所に置くとユーザ(ブラウザ)から見えてしまいます。ここで使用したサンプルプログラムはブラウザから見えるようにデザインファイル(HTMLファイル)を敢えてPHPプログラムと同じディレクトリに置いています。

HTMLファイルを置くディレクトリ(最近はフォルダと呼ぶ人が増えた)はhtml.org という名前でDOCUMENT_ROOTの1つ上にしておくと良いです。プロジェクトで共通に使用するファイルにhtml.org のディレクトリを示す $dir_html を定義しておき以下のように呼び出します。

$z = new Html( "$dir_html/foo.html");

変数名規則

ユーザ入力は $p_,エラーメッセージは $e_,$err_,$error_,URLエンコードした文字列は $urlenc_ というように変数名のプレフィックスを決めておくとプログラムもHTMLファイルも判りやすくなります。

特にエラーメッセージ,URLエンコード文字列,改行の変換に関してはそのような変数名を付けることを前提としています。

プログレスバー

サーバ側の処理に時間が掛かる場合ハングアップしたのでなく処理に時間が掛かっていることを示すために処理単位毎に'*'を表示して処理中であることを示します。

キャッシュをOFFにして,'*'を表示します。

OperaやNN4.7の場合'*'を出力するだけではブラウザがキャッシュしてしまうので,ブラウザを判断して表示方法を変更します。

<?php
include_once('mthtml.php');
$z = new Html( "test-mthtml-progressbar.html", array('cache'=>false));
$n = 5;
$sec = 1;
$browser = $_SERVER[HTTP_USER_AGENT];
if( stristr( $browser, 'Opera') !== false) { // MSIEより前に判定すること
  $prefix_browser = 'opera';
}
else if( stristr( $browser, 'MSIE') !== false) {
  $prefix_browser = 'ie';
}
else if( stristr( $browser, 'Firefox') !== false) {
  $prefix_browser = 'ie';
}
else if( stristr( $browser, 'Gecko') !== false) {
  $prefix_browser = 'ie';       // Geckoだけの場合ってあるのかな?
}
else if( stristr( $browser, 'Mozilla/4.7') !== false) {
  $prefix_browser = 'nn47';
}
else {
  $prefix_browser = 'ie';
}
$z->pr('file');
print str_repeat(' ',256);      // IEが256バイト未満の出力をキャッシュしてしまうので
for( $i=0; $i< $n; $i++) {
  $z->pr( "${prefix_browser}_bar");
  sleep( $sec); // 時間が掛かる処理のつもり
}
$z->end();
exit(0);
?>

HTMLファイルは以下のようにします。

<!--KEY_start_file -->
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS">
<title>プログレスバー(ProgressBar)を表示できるか?</title>
</head>
<body>
プログレスバー(ProgressBar)を表示できるか?<br>
全部で${n}個の'*'を表示する予定。<br>
'*'を1つ表示するのに${sec}秒掛かります。<br>
<!--KEY_start_browser -->
<!--KEY_start_ie_bar -->*<!--KEY_end_ie_bar -->

<!--KEY_split_opera -->
<table border="0" cellspacing="0" cellpadding="0"><tr>
<!--KEY_start_opera_bar -->
<td>*</td>
<!--KEY_end_opera_bar -->
</tr></table>

<!--KEY_split_nn47 -->
<!--KEY_start_nn47_bar NN4.7は改行をしないと表示されない-->
*<br>
<!--KEY_end_nn47_bar -->
</table>
<!--KEY_end_browser -->
<br>これでプログラムは終了です
</body>
</html>
<!--KEY_end_file -->

<!--KEY_end_ie_bar --> を行頭に置かないのは行頭に置くと実行したとき'*'の間に空白が空くのを避けるためです。空白を空けて表示することを希望するのであれば,改行して次の行に置くようにします。

別ファイルのブロックをincludeする

別ファイルにあるブロックをincludeできます。

<!--KEY_start_file -->
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS">
<title>別ファイルのブロックをincludeする</title>
</head>
<body>
このファイルにはCopyrightが含まれてませんが,別ファイルのcopyrightを読み込んで表示します。
<!--include_index.html#copyright -->
</body>
</html>
<!--KEY_end_file -->

バグ(BUG)

現在判っている障害は以下の通り。

PHPに関する雑感

ビルトインの関数が数多く用意されているのでPerlより使いやすく感じるが,クラスを使ってプログラムを組もうとすると色々使いづらいことが判ります。

これらの話はPHP4.3を元に書いています。PHP5では改良されている項目もあります。

ダウンロード

メディアチップスホームページから最新版をダウンロードできます。

インストール

ファイルを展開すると mtutl.php,mthtml.php の2つのファイルが作成されます。これでインストールは完了。

質問

質問がある方はこちらからどうぞ。いたずら防止のためワンクッション置くようにしました。

動作環境

PHP4.3で開発しているのでPHP4.3で動くことは確か。

PHP5でも動作するでしょう。

著作権

田所義照@メディアチップス

利用

法人,個人を問わず無料で利用できます。

配布

自由に行ってください。特に連絡は必要ありません。

改造

改良,改造,改悪は自由に行って貰って結構ですがその場合個人利用,自社利用に限定し,再配布しないでください。

これは幾つも異なった仕様のプログラムが混在するのを防ぐためです。

免責

三日完を使ったことによって生じるいかなる紛争にも著作権者は関与しないものとします。  

テスト

上記プログラムが実際に動作する様子を見るには以下のリンクをクリックしてください。SRCはプログラムのソースファイルを,HTMLはプログラムで利用しているテンプレートHTMLファイルを,BROWSEはテンプレートHTMLファイルをブラウザで表示します。

HelloWorldを表示するだけ (SRC)
HelloWorldをサイズを変えて複数出力する (SRC)
HelloWorldをサイズを変えて複数出力する(三日完を使用する) (SRC,HTML,BROWSE)
HelloWorldのデザインを変更してみた (SRC,HTML,BROWSE)
簡単な例(変数をHTMLファイルに埋め込む) (SRC,HTML,BROWSE)
CSVファイルを表示する (SRC,HTML,BROWSE)
「データがありません」を表示する。 (SRC,HTML,BROWSE)
行毎に色を変えて出力する。方法1 (SRC,HTML,BROWSE)
行毎に色を変えて出力する。方法2 (SRC,HTML,BROWSE)
イメージを表示する (SRC,HTML,BROWSE)
CSVファイルを検索する (SRC,HTML,BROWSE)
CSVファイルを検索する(検索語がないときは検索しない) (SRC,HTML,BROWSE)
検索結果で「前へ」,「後ろへ」を表示する。 (SRC,HTML,BROWSE)
URLの引数で検索語を指定する (SRC,HTML,BROWSE)
入力チェック (SRC,HTML,BROWSE)
入力/確認/処理完了 (SRC,HTML,BROWSE,HTML2,BROWSE2,HTML3,BROWSE3)
入力データの書式チェック (SRC,HTML,BROWSE)
プログレスバー(ProgressBar)を表示する (SRC,HTML,BROWSE)
ソースファイルを表示する (SRC,HTML,BROWSE)
ファイルをincludeする (SRC,HTML,BROWSE)

自分のサーバにインストールして使い心地を試してみることが出来るようにtarで纏めてみました。こちらからダウンロードできます。このサンプルには問い合わせプログラムも入ってます。

ファイルの文字コードはEUCです。HTMLディレクトリでファイルを展開すればインストールは完了です。

サンプルアプリケーション

三日完を使って試験問題を出題するプログラム(問象)を作成しました。こちらもフリーなので使ってみてください。