業務アプリ開発講座

アプリケーションの共通データファイルの管理方法

/includes/classes/AppData.phpを作っていきましょう。ソースは下記の通りです。関数errorLog()は前回までに作成したものをここに移動します。

class AppData{

	public static function sitemap($decodeType){

		return AppData::getJson('sitemap', $decodeType);
	}

	private static function getJson($dataName, $decodeType){

		if(!$data = json_decode(AppData::getAppData($dataName), $decodeType)){
			AppData::errorLog(json_last_error_msg()." [AppData::getAppData('{$dataName}')]");
			return false;
		}
		return $data;
	}

	private static function getAppData($dataName){

		if(!$data = @file_get_contents($GLOBALS['path'][$dataName])){
			AppData::errorLog(error_get_last());
			return false;
		}
		return mb_convert_encoding($data, 'UTF8', 'ASCII,JIS,UTF-8,EUC-JP,SJIS-WIN');
	}

	public static function errorLog($err){
	}
}

それでは、順番に見ていきましょう。

まず、class名をファイル名と同じAppDataとします。classを格納するするファイルは、必ず、class名とファイル名を同じにしましょう。一つのファイル内に複数のclassを記述すると、どの機能がどのファイルにあるのかが分かりにくくなります。その結果同じような処理を複数のファイルに記述してしまうような事態に陥りかねません(チームで開発していると実際に起こります)。

public static function sitemap($decodeType){
    return AppData::getJson('sitemap', $decodeType);
}

sitemap.jsonを読み込み、phpで利用できるようにオブジェクトあるいは連想配列にして返す関数です。引数$decodeTypeには、前回のInit.phpに書いた、classJsonDecodeTypeが入ります。そして、同じclass内の関数getJson()の戻り値をそのまま返しています。関数getJson()の引数として、Init.php内で設定した、$GLOBALS['path']['sitemap']のキー「sitemap」と、引数で渡ってきた、classJsonDecodeTypeの値をそのまま渡しています。

この関数は、要するにsitemap情報を取得するだけのもので、しかも関数getJson()に対し、sitemap情報を指定のデータ形式で取ってくるように依頼しているだけです。

関数の原則として、1関数1機能にしなければなりません。そうすることで、機能拡張や修正が容易になります。

例えば、validation.jsonファイルを読み込む関数を追加するとします。しかもデータ形式が連想配列のみだと分かっていたとします。

public static function validation(){
    return AppData::getJson('validation', JsonDecodeType::TYPE_ARRAY);
}

この3行を追加すればvalidation.jsonを利用できるようになります。

そんな1行だけの処理を持った関数を作る必要があるのかと思われるかもしれません。確かに、関数をgetJson()をアクセス修飾子をprivateからpublicにして、

class AppData{
    public static function getJson($dataName, $decodeType){
    }
}

$sitemap = AppData::getJson('sitemap', JsonDecodeType::TYPE_OBJECT);

とすることも可能でし、決して間違いではありません。しかしそれでも不都合な場合があるのです。それは'sitemap'というリテラル文字列です。グローバル変数のキーであるsitemapという文字列を開発途中で変更することになったらどうなるでしょう。さらにもし、

$sitemap = AppData::getJson('sitemap', JsonDecodeType::TYPE_OBJECT);

という記述をアプリ内の様々な箇所で書いていたらどうなるでしょう。それらを全てきちんと修正する必要がありますし、また、修正漏れも発生する可能性もあります。さらに修正漏れがないようにテストも必要ですね。つまり、修正や変更に対する耐性が低いプログラミング方法と言えます。プログラミングではリテラル文字の記述を最低限にするように工夫することがとても大切なのです。数字も同じ意味で直書きしてはいけません。ちなみに直書きした数字のことをマジックナンバーと言いいます。

そんなグローバル変数のキーのような大切な情報を開発途中で変更するようなことがあるのかと思われるかもしれませんが、実際にあります。その理由は様々で、必ずしも合理的な理由があるわけでもありませんが、それでも鶴の一声で「変更する」と命令が下ればそうするしかない場合もあるのです。

続いてgetJson($dataName, $decodeType)です。

if(!$data = json_decode(AppData::getAppData($dataName), $decodeType)){
    AppData::errorLog(json_last_error_msg()." [AppData::getAppData('{$dataName}')]");
    return false;
}

PHP組み込み関数であるjson_decode()の引数のデータに、class内の関数getAppData()を渡しています。データ形式は引数で渡ってきたJsonDecodeTypeを渡していますね。json_decode()の戻り値を変数$dataに代入します。このifは代入と評価を同時に行っています。関数json_decode()は処理に失敗するとfalseを返します。つまり、処理に失敗した場合、変数$dataにはfalseが代入されます。ですので、!$dataは変数$dataがfalseだったらという意味になります。

そして、falseだったら、エラーログを関数を呼び出し、この関数の処理結果としてfalseを返します。エラーラログの引数部分にある関数json_last_error_msg()はjson_decode()でエラーが発生した場合のエラー情報を返す関数です。「Syntax error」のような、シンプルなメッセージしか返しませんので、メッセージ文字列に関数名とデータ名を付加しています。

関数json_decode()が正しくデータを取得できれば、変数$dataにはsitemap情報が格納され、それを返します。

続いてgetAppData($dataName)です。

if(!$data = @file_get_contents($GLOBALS['path'][$dataName])){
    AppData::errorLog(error_get_last());
    return false;
}
return mb_convert_encoding($data, 'UTF8', 'ASCII,JIS,UTF-8,EUC-JP,SJIS-WIN');

これも基本的に関数getJson()と同じです。指定したファイルを読み込み、その文字コードをエンコードして返します。読み込みに失敗したらエラーログ書き込み関数を呼び題して、処理結果として、falseを返します。
ちなみに関数mb_convert_encoding()はデータ内に日本語が含まれる場合には必須の処理ですが、ASCII文字しか含まれない場合は省いても構いません。

エラーログの引数であるerror_get_last()は直近で発生したエラー情報を連想配列として複数の情報を返します。

最後に関数errorLog()を以前作成したものに下記の変更を加えます。

if(ini_get('display_errors')){
	echo "
{$msg}

";
}
else{
error_log($msg, 3, $GLOBALS["path"]["root"]."/log/error-{$date}.log");
header("HTTP/1.0 500 Internal Server Error");
print(file_get_contents($GLOBALS["path"]["tempHtml"].'/500.html'));
}
exit;

変更箇所はelse以下と最後のexitです。

関数error_log()の引数のログファイルのパス情報をInit.phpで設定したグローバル変数を当てはめます。次のheader()関数はページの処理結果コードであるHTTPステータスコードを出力しています。500は内部サーバーエラーが発生したことを示すコードです。HTTPステータスコードはHTTPの規約で決まっています。処理が成功し、ページを表示できる状態、つまり通常ページが表示される場合は、200番を返しています。ブラウザはこのHTTPステータスコードに応じて表示を切り替えています。

続いて、temp-htmlディレクトリ内の500.htmlファイルを読み込んでそれを出力しています。
こうすることで、エラーが発生した場合のカスタムエラーページをブラウザに返すことができます。

最後のexitはここで、ページ処理を終了するという機能です。例えば、classAppDataの関数getAppData()でエラーが発生したら、次の関数getJson()へは処理が移りません。

つまり、エラーログを出力するようなエラーが発生した場合は、そこで処理を中断し、カスタムエラー画面を表示するという仕様ということになります。別の言い方をすれば、処理を中断しなければならない場合にはエラーログを出力するとなります。

次回はTempMasterHtml.phpindex.phpに対して、これまでに作成したclassを当てはめる修正です。