【 PHP 編 】

1. 特記
2. 共通処理
3. bbs.php と global.php
4. 設定ファイルの読み込みを global.php に実装
5. ログ表示の実装
6. ページジャンプ用のリンク作成
7. ログ書き込みの実装
8. 過去ログ処理の実装
9. ロック処理の実装

  • Perl から PHP の移行は それを前提に記述されておれば簡単なものとなります。また、ASP の知識を
    持っておれば、さらに問題無く全てを理解できるはずです

  • グローバル変数は、全て大文字にします
  • ユーザ関数名は、単語の先頭を大文字にします
  • ローカル変数は全て小文字にします


  • PHP 特有の処理
  • PHP はスーパグローバルである $_GET、$_POST、$_SERVER を良く知って
    おく事が重要になります

  • また、Perl とも ASP とも違った 関数内でグローバル変数を使用する時には
    宣言が必要であるといった事も知っておく必要があります


  •   概要
    1
    http ヘッダの出力
    2
    デバッグ情報出力関数
    3
    文字列出力関数


  • 言語によって、デフォルトの文字列出力は関数であったり、ステートメントであったりメソッドであったり
    しますが、統一した関数名で標準化します


  • bbs.php
  • <?
    require_once( "global.php" );
    require_once( "model.php" );
    $LOG_VIEW = "";
    $PAGE_LINK = "";
     
    if ($_SERVER['REQUEST_METHOD'] == "POST") {
    	WriteLog();
    }
     
    require_once( "head.php" );
    require_once( "input.php" );
     
    LoadLog();
    Out( $LOG_VIEW );
     
    require_once( "foot.php" );
    ?>
    
  • head.php と input.php と foot.php は、基本的に HTML タグのみを記述します。ヒアドキュメントは
    使用できますが、ASP と同様の機能を持つ <? ?> が利用できるので、必要ありません
  • global.php

  • error_repoting は、エラーレベルによるエラー表示の抑制を行います。変数が定義されていないのに
    使用している、というような詳細をチェックしたい場合は E_ALL のみにします

  • その次にあるループは、PHP が自動的に変換する文字列を元に戻しています。0x5C を含む文字列
    による問題の事前回避ができます
  • <?
    header( "Content-Type: text/html; Charset=Shift_JIS" );
    header( "Expires: Wed, 31 May 2000 14:59:58 GMT" );
     
    error_reporting( E_ALL ^ E_WARNING ^ E_NOTICE );
     
    foreach( $_POST as $Key => $Value ) {
    	$Value = str_replace("\\\\", "\\", $Value );
    	$Value = str_replace( "\\\"", "\"", $Value );
    	$Value = str_replace( "\\'", "'", $Value );
    	$_POST[$Key] = $Value;
    }
     
    # **********************************************************
    # デバッグ情報出力
    # **********************************************************
    function DispDebug()
    {
     
    	global $KEY,$VALUE;
     
    	print "<TABLE border=0><TR><TD valign=top>";
    	print "<TABLE border=0 cellspacing=1 bgcolor=black>";
    	print "<TR><TH bgcolor=silver colspan=2>GET</TH></TR>";
    	print "<TR><TH bgcolor=silver>Key</TH><TH bgcolor=silver>Value</TH></TR>";
    	foreach( $_GET as $KEY => $VALUE ) {
    		print "<TR>";
    		print "<TD bgcolor=white>$KEY</TD><TD bgcolor=white>$VALUE</TD>";
    		print "</TR>";
    	}
    	print "</TABLE>";
     
    	print "</TD><TD valign=top>";
     
    	print "<TABLE border=0 cellspacing=1 bgcolor=black>";
    	print "<TR><TH bgcolor=silver colspan=2>POST</TH></TR>";
    	print "<TR><TH bgcolor=silver>Key</TH><TH bgcolor=silver>Value</TH></TR>";
    	foreach( $_POST as $KEY => $VALUE ) {
    		print "<TR>";
    		print "<TD bgcolor=white>$KEY</TD><TD bgcolor=white>$VALUE</TD>";
    		print "</TR>";
    	}
    	print "</TABLE>";
    	print "</TD></TR></TABLE>";
     
    }
     
    # **********************************************************
    # 文字列出力
    # **********************************************************
    function Out( $value )
    {
    	print $value;
    }
     
    # **********************************************************
    # 改行付き文字列出力
    # **********************************************************
    function OutCr( $value )
    {
    	print $value . "\n";
    }
     
    # **********************************************************
    # 配列にファイルの行を全て読み込む
    # **********************************************************
    function GetFile( $path )
    {
    	return file( $path );
    }
     
    # **********************************************************
    # ファイルの存在チェック
    # **********************************************************
    function FileExist( $path )
    {
    	return file_exists( $path );
    }
     
    # **********************************************************
    # 配列の範囲指定の書き込み
    # **********************************************************
    function WriteArray( &$fp, $start, $end, &$array )
    {
    	for( $i = $start; $i <= $end; $i++ ) {
    		fputs( $fp, $array[$i] );
    	}
    }
     
    ?>
    

  • 設定ファイルの処理は、広域な標準化ではありませんが、掲示板というシステムとして考えれば
    共通の領域になります

  • 設定ファイルから読み込んだ情報を、$_GET へ格納します
  • GetIni();
     
    # **********************************************************
    # 設定ファイルの読み込み
    # **********************************************************
    function GetIni( )
    {
     
    	$file = GetFile("bbs.ini" );
    	for( $i = 0; $i < count( $file ); $i++ ) {
     
    		# 改行削除
    		$target = rtrim( $file[$i] );
     
    		# 空行は無視
    		if ( $target == "" ) {
    			continue;
    		}
     
    		# コメント文字
    		if ( substr( $target, 0, 1 ) == ";" ) {
    			continue;
    		}
     
    		# 行を区切り文字で分解
    		$ini = explode( "=", $target );
     
    		$_GET[$ini[0]] = $ini[1];
     
    	}
     
    }
    

  • ログ表示は、掲示板としてもっとも重要な部分であり、アプリケーションとしては「問い合せ」の分類に入
    ります。これだけでもアプリケーションとして有用性が存在します

  • 通常、アプリケーションの開発では、入力処理と表示処理は別々である事があります。そのような場合
    データは、手動で作成されます。この場合も、テキストエディタで6件のデータを作成します
  • 日付,ユーザ,タイトル,本文1<br>改行
    日付,ユーザ,タイトル,本文2<br>改行
    日付,ユーザ,タイトル,本文3<br>改行
    日付,ユーザ,タイトル,本文4<br>改行
    日付,ユーザ,タイトル,本文5<br>改行
    日付,ユーザ,タイトル,本文6<br>改行
    
  • model.php
  • # **********************************************************
    # ログ表示
    # **********************************************************
    function LoadLog()
    {
     
    	global $LOG_VIEW,$PAGE_LINK;
     
    	# ログファイルが存在しない場合はなにもしない
    	if ( !FileExist( $_GET['INI_LOGFILE'] ) ) {
    		return;
    	}
     
    	# 初期表示対応
    	if ( $_GET['page'] == "" ) {
    		$_GET['page'] = 1;
    	}
     
    	# ログデータを配列として読み込み
    	$file = GetFile( $_GET['INI_LOGFILE'] );
     
    	# ページあたりの行数
    	$row_par_page = $_GET['INI_ROWPERPAGE'];
     
    	for( $i = 0; $i < count($file); $i++ ) {
     
    		# ログの表示データ作成 ( 自ページ分のみ )
    		if ( (int)($i / $row_par_page) + 1 == $_GET['page'] ) {
    			# 行を区切り文字で分解
    			$row = explode( ",", $file[$i] );
     
    			# 表示データの作成
    			$LOG_VIEW .= "$row[0] $row[1] $row[2]<BR>";
    			$LOG_VIEW .= "$row[3]";
    			$LOG_VIEW .= "<HR size=1 color=silver>";
    		}
     
    	}
     
    }
    

  • ジャンプ先は、ページの数 - 1 件必要になります。ログ表示で全行を処理するループがあるのでその中
    に記述します
  • # ページリンクの作成
    if ( ( $i % $row_par_page ) == 0 ) {
    	$page = (int)($i / $row_par_page) + 1;
    	# 自ページの場合
    	if ( $page == $_GET['page'] ) {
    		$PAGE_LINK .= "$page&nbsp;&nbsp;";
    	}
    	# 他ページの場合
    	else {
    		$PAGE_LINK .= "<A href='{$_SERVER['SCRIPT_NAME']}";
    		$PAGE_LINK .= "?page=$page'>$page</A>&nbsp;&nbsp;";
    	}
    }
    
  • $PAGE_LINK は、foot.php のヒアドキュメントに埋め込まれています

  • <?= $PAGE_LINK ?>
    </BODY>
    </HTML>
     
    <? DispDebug() ?>
    

  • ログの書き込みは、単純に書き込むだけにします。過去ログへの最終データの移動は、過去ログ処理
    に任せる事にして、必ず OPEN した時には 最大行数 - 1 件以下のデータになるようにします

  • 読み込まれた改行コードはそのまま利用しています
  • # **********************************************************
    # ログ書き込み
    # **********************************************************
    function WriteLog()
    {
     
    	# ログデータを配列として読み込み
    	$file = GetFile( $_GET['INI_LOGFILE'] );
     
    	# ログファイルを書き込みモードでオープン
    	$fp = fopen( $_GET['INI_LOGFILE'], "w" );
     
    	# 日付フォーマット
    	$str_date	= date("Y-m-d(D) H:i:s");
     
    	# HTML 変換
    	$message = htmlentities($_POST['Message'],ENT_NOQUOTES,"SJIS");
    	$user_name = htmlentities($_POST['UserName'],ENT_NOQUOTES,"SJIS");
    	$title = htmlentities($_POST['Title'],ENT_NOQUOTES,"SJIS");
     
    	# カンマのコンバート
    	$user_name	= str_replace( ",", "&#44;", $user_name );
    	$title		= str_replace( ",", "&#44;", $title );
    	$message	= str_replace( ",", "&#44;", $message );
     
    	# 改行のコンバート
    	$message	= str_replace( "\r", "", $message );
    	$message	= str_replace( "\n", "<BR>", $message );
     
    	# POST されたデータの書き込み
    	fputs( $fp, "$str_date,$user_name,$title,$message\n" );
     
    	WriteArray( $fp, 0, count($file)-1, $file );
     
    	fclose( $fp );
     
    }
    

  • 過去ログは、カレントログで MAX を超えた行を履歴として残し、通常別のインターフェイスで表示する
    ためのデータです

  • 過去ログファイルは、1ファイルあたりの MAX 行が決められ、その件数を超えれば新しい過去ログフ
    ァイルが作成されて使用されます。古い過去ログのデータは変更される事無く、直接メンテナンスしな
    い限り行の順序は変わる事はありません

  • 過去ログ処理のトリガは、カレントログのデータが、MAX 行の状態での投稿です。まず、カレントログを
    書き込む前に、そのチェックを行なって MAX 行に達していたら、最終行を変数に保存してカレントロ
    グを MAX - 1 件の状態にしておきます

  • WritePastLog は、WriteLog の先頭でコールして下さい

  • # **********************************************************
    # 過去ログ処理
    # **********************************************************
    function WritePastLog( )
    {
     
    	# ログデータを配列として読み込み
    	$file = GetFile( $_GET['INI_LOGFILE'] );
     
    	# MAX 行数に達していたら、最終行を取得
    	if ( count($file) >= $_GET['INI_MAXROW'] ) {
    		$move_row = $file[count($file)-1];
    	}
    	else {
    		return;
    	}
     
    	# 最終行以外を書き込む
    	$fp = fopen( $_GET['INI_LOGFILE'], "w" );
    	WriteArray( $fp, 0, count($file)-2, $file );
    	fclose( $fp );
     
    }
    
  • この後に、仕様編のフローをコードに直して実装します
  • 	# 過去ログディレクトリが存在しない場合は作成
    	if ( !FileExist( $_GET['INI_PASTDIR'] ) ) {
    		mkdir( $_GET['INI_PASTDIR'] );
    		# 過去ログファイルを作成
    		$target = date("YmdHis") . ".csv";
    		$target = $_GET['INI_PASTDIR'] . "\\" . $target;
    		$fp = fopen( $target, "w" );
    		fclose( $fp );
    	}
     
    	# 過去ログディレクトリの最新ファイル名を取得
    	$dir = opendir( $_GET['INI_PASTDIR'] );
     
    	# 過去ログディレクトリを 読み込み
    	$arr_file = array();
    	$count = 0;
    	while( false !== ($filename = readdir($dir)) ) {
    		if ( $filename != '.' && $filename != '..' ) {
    			$arr_file[$count] = $filename;
    			$count++;
    		}
    	}
    	closedir($dir);
     
    	rsort( $arr_file );
    	$cur_past = $_GET['INI_PASTDIR'] . "\\" . $arr_file[0];
     
    	# 過去ログデータを配列として読み込み
    	$file = GetFile( $cur_past );
     
    	# MAX 行になっていたら、新しいファイルを作成
    	if ( count($file) >= $_GET['INI_PASTMAXROW'] ) {
     
    		# 新しい過去ログファイルを作成して書き込む
    		$target = date("YmdHis") . ".csv";
    		$cur_past = $_GET['INI_PASTDIR'] . "\\" . $target;
    		$fp = fopen( $cur_past, "w" );
    		fclose( $fp );
     
    	}
     
    	# 過去ログデータを配列として読み込み
    	$file = GetFile( $cur_past );
     
    	# ログファイルを書き込みモードでオープン
    	$fp = fopen( $cur_past, "w" );
     
    	# カレントログのはみ出したデータの書き込み
    	fputs( $fp, $move_row );
     
    	# 元々のデータを書き込み
    	WriteArray( $fp, 0, count($file)-1, $file );
     
    	fclose( $fp );
    

  • ロック処理はファイルの書き込みの前に行い、解除は書き込みの後に行います

  • もしロックされていた場合は、現在の投稿データを再表示してエラーメッセージを出力します
  • # ***********************************************************
    # ロック関数
    # ***********************************************************
    function lockbydir( ) {
     
    	$lock = $_GET['INI_LOCKDIR'];
    	$lockcnt = $_GET['INI_LOCKCNT'];
     
    	$ret = true;
     
    	if ( file_exists( $lock ) ) {
    		$astamp = stat($lock);
    		$laststamp = $astamp[9];
    		if ( $laststamp < time() - 60 ) {
    			rmdir( $lock );		# 古いディレクトリの削除
    		}
    	}
     
    	$cnt = 0;
     
    	while( 1 ) {
    		if ( mkdir( $lock ) ) {
    			break;	# 成功
    		}
    		$cnt++;
    		if ( $cnt > $lockcnt ) {
    			$ret = false;	# 失敗
    			break;
    		}
    		sleep(1);
    	}
     
    	return $ret;
     
    }
    
  • bbs.php の変更
  • <?
    require_once( "global.php" );
    require_once( "model.php" );
    $LOG_VIEW = "";
    $PAGE_LINK = "";
     
    if ($_SERVER['REQUEST_METHOD'] == "POST") {
    	if ( LockByDir() ) {
    		WriteLog();
    		rmdir( $_GET['INI_LOCKDIR'] );
    	}
    	else {
    		lightbox_NAME = $_POST['UserName'];
    		$TITLE = $_POST['Title'];
    		$MESSAGE = $_POST['Message'];
    		Out( "ロックされています<br>" );
    	}
    }
     
    require_once( "head.php" );
    require_once( "input.php" );
     
    LoadLog();
    Out( $LOG_VIEW );
     
    require_once( "foot.php" );
    ?>