WebTecNote

[PHP5] OOPで掲示板を作ってみる – Step5:設定ファイルと細かな修正

オブジェクト指向プログラミングでフレームワークを使わずに掲示板作成その5。
設定ファイル作成・2重送信防止・メッセージ変更の3本で~す

目次

Step4までで読み書きできるようになった掲示板。

いくつか気になる点が出てきたので今のうちに解決しておくことにする。

§1. メッセージの変更

メッセージが黒いままじゃぱっと見なんのメッセージなのか分かりづらいから、エラーは赤!成功なら緑!みたいに色分けをしたい。
色分け自体はCSSですればいいけど、CSSならクラスやstyle属性が必要になってくる。
そこでエラーと分けていたメッセージのセッターを次のように変更した。

lib/view.php (class View)

public function __construct()
{
    //前後省略//
    $this->_systemMessage = array('type'=>'', 'text'=>'');
}
/**
* システムメッセージのセッター
*/
public function setSystemMessage($type=NULL, $text)
{
    $this->_systemMessage = array('type'=>$type, 'text'=>$text);
}

$_systemMessageを連想配列にして、メッセージの種類を一緒に保存しておくようにする。

これに合わせてコントローラーのセッターも変更。
成功ならsuccess、エラーならerrorという引数を渡すようにする。

lib/controller.php (class Controller – function execute)

$viewInstance->setSystemMessage('success', '保存しました');//メッセージをセット
$viewInstance->setSystemMessage('error', $modelInstance->getError());//エラー

getSystemMessageメソッドはキーを指定してメッセージだけ返すようにする。

lib/view.php (class View)

/**
 * システムメッセージのゲッター
 */
public function getSystemMessage()
{
    return $this->_systemMessage['text'];
}

最後にViewのdisplayメソッドを修正する。

lib/view.php (class View – function display)

 $class = empty($this->_systemMessage['type']) ? '' : ' ' . $this->_systemMessage['type'];
 $message = $this->getSystemMessage();
       
 if(isset($message)){
    if( is_array($message) ){
       $message = implode('<br />', $message);
    }
            
    $message = '<p class="message' . $class . '">' . $message . '</p>';
}

$_systemMessage[‘type’]をそのままクラス名として使う。
あとはindex.tplでCSSを書くだけでこの通り 😀

§2. 2重送信防止

今の状態ではF5キーの連打で無限保存が出来てしまうから、
前に書いたCookieを使うサンプルを使って2重送信を防止しておく。
トークンはコントローラーで作って、POST送信されたときにチェック。
テンプレートにはメッセージと同じ要領で変数を使って渡せばいいから、
出力に$tokenという変数を使うことにしてテンプレートにhidden要素を書いておく。

template/index.tpl

<input type="hidden" name="token" value="<?php print($token);?>" />

コントローラーにトークンを作るメソッドを追加。

lib/controller.php (class Controller)

/**
* トークンの作成
*/
public function createToken()
{
    return substr(sha1(uniqid().mt_rand()),-13,10);
}

それからexecuteメソッドの中にある条件分岐にトークンのチェックとクッキーへの書き込みを加える。
POSTされるトークンにサニタイズを掛けたかったのでModelのcleanメソッドを public に変更しています。

lib/controller.php (class Controller – function execute)

if(isset($_POST['mode']) && $_POST['mode'] != ''){
	
	//トークンの確認
	$token = $modelInstance->clean($_POST['token']);
	
	if($token == @$_COOKIE["_token"]){
		$viewInstance->setSystemMessage('error', '不正な送信です');//エラーをViewに渡す
		
	}else{
	
		switch($_POST['mode']){
			
			case 'write':
				$modelInstance->setPostdata($_POST);//送信内容のセット
						
				if($modelInstance->validation()===true){
					$modelInstance->write();//ログに書き込み
					$viewInstance->setSystemMessage('success', '保存しました');//メッセージをセット
					setcookie("_token", $modelInstance->clean($token));
								
				}else{
					$viewInstance->setSystemMessage('error', $modelInstance->getError());//エラーをViewに渡す
				}
		
			break;
			
			default:
			   
		}
	}
	
}

//省略//
$viewInstance->setToken($this->createToken());//Viewにトークンを渡す

ビューへのセットはdisplayメソッドに引数として渡すかどうかで迷ったけど、
セッター&ゲッターを使うことにしました。

lib/view.php (class View)

/**
 * トークンのセッター
 */
public function setToken($token)
{
	$this->_token = $token;
}

/**
 * トークンのゲッター
 */
public function getToken()
{
	return $this->_token;
}

displayメソッドでテンプレートに渡す$tokenにトークンをセットすれば
2重送信防止が出来るようになる。

lib/view.php (class View – function display)

$token = $this->getToken();

§3. 設定ファイルの作成

気になってたところは直したけどまだ何かが足りない。…そうです設定ファイルです。
ディレクトリ名とかは設定ファイルを使っといた方がよりアプリケーションらしさが増すというもの。

よくある設定ファイル

配布されているスクリプトを見たりしているとパターンがいくつかある事が分かる。
どれも一長一短ありますが今回は4のiniファイルを使うことにした。

  1. テキストファイルに独自の形式で書いてある
  2. 設定項目が各PHPファイルの上部にそのファイル内で必要なものだけ書いてある
  3. 共通して読み込む設定ファイルに定数や変数が書いてある(ZeroMailとかがこれ)
  4. iniファイル(php.iniでおなじみ)
  5. データベース(CMSはこれ)

ZeroMailと同じように定数メインの設定ファイルを作るのが一番楽だと思う。
iniファイルはparse_ini_file関数を使う手間がひとつ余計に必要になるけど、
key=valueという書式だからファイル関数使うものの中では設定保存が実装しやすい。
いずれブラウザから設定変更をするつもりなら1,4,5、そうでないなら2,3。かな。

bbs.iniの作成

ModelとViewにある設定をiniファイルに移します。

;;;;;;;;;;;;;;;;;;;
;     bbs.ini     ;
;;;;;;;;;;;;;;;;;;;

;ログのディレクトリ
data_directory = dat

;ログファイル名
logfile_name = bbslog

;ログファイルの拡張子
logfile_extention = dat

;テンプレートディレクトリ
template_directory = template

;テンプレート拡張子
template_extention = tpl

;掲示板の入力項目
bbs_inputs = name,comment

;バリデーションルール
[validation]
name = 名前, notEmpty
comment = コメント, notEmpty

入力項目と一緒にバリデーションの設定もiniファイルにお引越し。
バリデーションキーはCakePHPを真似るとします。

それから設定を読み込んだりするクラスを新たに追加。

lib/config.php

class Config {
    
    private $ini;
    
    public function __construct($iniFilePath)
    {
        if (true == file_exists($iniFilePath)) {
            $this->ini = parse_ini_file($iniFilePath, true);
        }
    }
    
    /**
     * 設定のゲッター
     */
    public function get($name)
    {
        if(array_key_exists($name, $this->ini) === true)
            return $this->ini[$name];
        else
            return false;
    }
    
    /**
     * フォーム設定のゲッター
     */
    public  function getInputs()
    {
        return explode(',', $this->ini['bbs_inputs']);
    }
}

parse_ini_fileの戻り値はクラスのプロパティにしまっておく。
フォームの設定だけ専用のゲッターを作って配列で返すようにした。

コントローラーの変更

Configクラスのインスタンスをコントローラーで作成する。
作ったインスタンスはModelとViewのコンストラクタに渡します。

lib/controller.php (class Controller – function execute)

const INIT_FILE = 'bbs.ini';
//中略//
//execute
$iniFilePath = dirname($_SERVER['SCRIPT_FILENAME']).'/lib/'.self::INIT_FILE;
$config = new Config($iniFilePath);//設定

$modelInstance = new Model($config);//モデル
$viewInstance = new View($config);//ビュー
//中略//
 $modelInstance->setLogfileName($config->get('logfile_name'));//ログファイルをセット

Viewの変更

クラス定数をコメントアウトして、代わりにテンプレートディレクトリ用のプロパティを増やす。
コンストラクタの引数に$configを入れてからgetメソッドを使って設定を取り出す。

lib/view.php (class View)

//const TEMPLATE_DIRECTORY = 'template/';
private $_templateDirectory; //テンプレートディレクトリ
private $_templateName;      //テンプレートファイル名
private $_tempateExtention;  //テンプレートの拡張子
private $_systemMessage;     //システムメッセージ
private $_logs;              //ログデータ
    
public function __construct($config)
{
    $this->_templateDirectory = $config->get('template_directory') . DIRECTORY_SEPARATOR;
    $this->_templateExtention = '.' . $config->get('template_extention');
    $this->_templateName = '';
    $this->_systemMessage = '';
    $this->_logs = NULL;
}

テンプレート名のゲッターで使っていた定数をプロパティに変える。

lib/view.php (class View)

/**
 * テンプレート名のセッター
 */
public function setTemplateName($name)
{
   $this->_templateName = $name . $this->_templateExtention;
}
/**
* テンプレート名のゲッター
*/
public function getTemplateName()
{
   return  $this->_templateDirectory . $this->_templateName;
}

Modelの変更

Viewと同じくModelのコンストラクタとファイル名に関するメソッドを修正。
iniファイルに設定されたバリデーションルールを入れるプロパティを追加。

lib/model.php (class Model)

//const DATA_DIRECTORY = 'dat/';
private $_dataDirectory;    //データディレクトリ
private $_logFilename;      //ログファイル名
private $_logFileExtention; //ログファイルの拡張子
private $_inputs;           //フォームのnameキー
private $_validationRule;   //バリデーションルール
private $_post;             //$_POSTが入る
private $_error;            //エラーが入る
private $_logs;             //ログが入る
    
    public function __construct($config)
    {
		
	$this->_dataDirectory = $config->get('data_directory') . DIRECTORY_SEPARATOR;
	$this->_logFileExtention = '.' . $config->get('logfile_extention');
        $this->_logFilename = '';             
		
        $this->_inputs = $config->getInputs();
	$this->_validationRule = $config->get('validation');

        $this->_post = array();               
        $this->_error = NULL;
        $this->_logs = NULL;
    }
    /**
     * ログファイル名のセッター
     */
    public function setLogfileName($name)
    {
        $this->_logFilename = $name . $this->_logFileExtention;
    }
    /**
     * ログファイル名のゲッター
     */
    private function _getLogfileName()
    {
        return $this->_dataDirectory . $this->_logFilename;
    }
    

バリデーションはとりあえずこんな感じで~。
掲示板はそれほど種類を使わないだろうから、とりあえず
notEmpty、alphaNumeric、email、urlを設定しておいた。

/**
 * データのバリデーション
 */
public function validation()
{
	$data = $this->_getPost();
	$error = array();
	
	foreach($data as $key => $value){
		
		if(!empty($this->_validationRule[$key])){
			
			//$this->_validationRule key = label, Rule1, Rule2...
			list($label, $rules) = explode(',', $this->_validationRule[$key], 2);
			$rules = explode(',', $rules);
			
			foreach($rules as $rule){
				$rule = trim($rule);
				if($rule == 'notEmpty' && $value == ''){
					
					$error[] = $label.'は必ず入力してください';
					 
				}elseif($rule == 'alphaNumeric' 
						&& $value != '' 
						&& !preg_match('/^[\d\w]+$/i', $value)){
					
					$error[] = $label.'は半角英数字のみ使用できます。'; 
					
				}elseif($rule == 'email' 
						&& $value != '' 
						&& !preg_match("/^([a-z0-9_]|\-|\.|\+)+@(([a-z0-9_]|\-)+\.)+[a-z]{2,6}$/i",$value) ){
					
					$error[] = $label.'の書式に誤りがあります。'; 
					
				}elseif($rule == 'url' 
						&& $value != '' 
						&& !preg_match('/^(https|http)(:\/\/[-_.!~*\'()a-zA-Z0-9;\/?:\@&=+\$,%#]+)$/', $value)){
					$error[] = $label.'の書式に誤りがあります。'; 
				}
			}
		}
	}
	
	$this->setError($error);
	
	if(count($error)>0){
		return false;
	}else{
		return true;
	}
}

これで掲示板の設定を弄るのが楽になった 😀

設定ファイルをブラウザから変更するっていう場合にはConfigクラスに書き込むロジックを追加すれば対応出来ます。
今の所はないけど、例えば管理者機能を追加する時にはパスワードの設定とかで必要になってくると思うので。

Step5 Download

ソースファイルのライセンスは Creative Commons BY-NC です。
ローカルサーバーでテストしてください。

“OOPでBBS Step5” をダウンロード

oop-bbs-step5.zip – 1424 回のダウンロード – 12.28 KB
モバイルバージョンを終了