こんにちは。新原です。PHPプログラミング診断室はじまりました。巷に溢れる病めるPHPコードを診断していきたいと思います。
PHPのコードと聞くとどういったイメージを想像されるでしょう? 昔からPHPを知っている方であれば、
初めての診断は、
関数定義がなく、流れるようなコードの妙技
今回のPHPコードは、
では、
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=euc-jp">
<title></title>
</head>
<body>
<center>
<form method="POST" action="">
<table border=1 cellspacing=2 cellpadding=5 width=700><tr><td>
<?php
$err_idx = -1;
switch($mode) {
case "":
print "<input type=\"hidden\" name=\"mode\" value=\"add_check\">\n";
print "<div align=\"center\">\n";
print "<br><br>\n";
print "<br>\n";
print "<table border=0 cellspacing=2 cellpadding=5>\n";
print "<tr><td align=\"center\" colspan=2 nowrap>メールマガジン購読</td></tr>\n";
print "<tr><td align=\"left\" colspan=2 nowrap>メールアドレスをご記入下さい。</td></tr>\n";
print "<tr>\n";
print "<td align=\"right\" nowrap>E-mail : </td>\n";
print "<td align=\"left\" nowrap><input type=\"text\" name=\"frm_email\" size=30 maxlength=100 value=\"$frm_email\"></td>\n";
print "</tr>\n";
print "</table>\n";
print "</div>\n";
$cn = pg_connect('user=vagrant dbname=app');
if ( $cn == false ) { $err_idx++; $err_value[$err_idx] = "system error"; break; }
$sql = "select * from magazines;";
$rec_magazines = pg_exec($cn,$sql);
if ( $rec_magazines == false ) { $err_idx++; $err_value[$err_idx] = "system error"; pg_close($cn); break; }
$mailm_cnt = pg_numrows($rec_magazines);
if ( $mailm_cnt == 0 ) {
$err_idx++; $err_value[$err_idx] = "購読できるメールマガジンがありません";
} else {
if ( $mailm_cnt != 1 ) {
print "<div>\n";
print "<hr>\n";
print "購読するマガジンにチェック後、購読ボタンをクリックしてください。\n";
print "</div>\n";
print "<table border=0 cellspacing=2 cellpadding=5>\n";
for ( $idx = 0;$idx < $mailm_cnt;$idx++ ) {
$id = pg_result($rec_magazines,$idx,id);
$name = pg_result($rec_magazines,$idx,name);
print "<tr><td align=\"left\" valign=\"top\">\n";
print "<input type=\"checkbox\" name=\"frm_id[$idx]\" value=$id>\n";
print "<b>$name</b><input type=\"hidden\" name=\"frm_name[$idx]\" value=\"$name\">\n";
print "</td></tr>\n";
}
print "</table>\n";
} else {
$id = pg_result($rec_magazines,0,id);
$name = pg_result($rec_magazines,$idx,name);
print "<input type=\"hidden\" name=\"frm_id[0]\" value=$id>\n";
print "<input type=\"hidden\" name=\"frm_name[0]\" value=\"$name\">\n";
}
print "<input type=\"hidden\" name=\"frm_mailmg_cnt\" value=$mailm_cnt>\n";
print "<div align=\"center\">\n";
print "<br>\n";
print "<input type=\"submit\" value=\"購読\">\n";
print "</div>\n";
print "<div align=\"center\">\n";
print "<div class=\"mainlink\">\n";
print "<br><br>\n";
print "<br><br>\n";
print "</div>\n";
print "</div>\n";
}
pg_freeresult($rec_magazines);
pg_close($cn);
break;
case "add_check":
if ( trim($frm_email) == "" ) {
$err_idx++; $err_value[$err_idx] = "メールアドレスを入力して下さい!";
}
elseif ( eregi("^[_]{1}|^[\.]{1}|^[\-]{1}",$frm_email) == True ) {
$err_idx++; $err_value[$err_idx] = "メールアドレス記入に誤りがあります!";
}
elseif ( eregi("(^[0-9A-Za-z_\.\-]+[@]{1})([^_\.\-]{1})([0-9A-Za-z_\.\-]+)([^_\.\-]{1})$",$frm_email) == false ) {
$err_idx++; $err_value[$err_idx] = "メールアドレス記入に誤りがあります!";
}
else {
$cn = pg_connect('user=vagrant dbname=app');
if ( $cn == false ) { $err_idx++; $err_value[$err_idx] = "system error"; break; }
$sql = "select * from members where lower(trim(email)) = '" . strtolower(chop($frm_email)) . "';";
$rec_members = pg_exec($cn,$sql);
if ( $rec_members == false ) { $err_idx++; $err_value[$err_idx] = "system error"; pg_close($cn); break; }
$members_cnt = pg_numrows($rec_members);
pg_freeresult($rec_members);
pg_close($cn);
if ( $members_cnt != 0 ) {
$err_idx++; $err_value[$err_idx] = "すでにメールマガジン購読受付が完了しています!<br>" . "[ " . $frm_email . " ]";
break;
}
}
$ok_flg = 0;
print "<input type=\"hidden\" name=\"frm_mailmg_cnt\" value=$frm_mailmg_cnt>\n";
if ( $frm_mailmg_cnt > 1 ) {
for ( $idx = 0;$idx < $frm_mailmg_cnt;$idx++ ) {
if ( $frm_id[$idx] ) { $ok_flg = 1; break; }
}
} else {
$ok_flg = 1;
}
if ( $ok_flg == 0 ) { $err_idx++; $err_value[$err_idx] = "購読希望するマガジンへチェックをお願いします。"; }
if ( $err_idx != -1 ) { break; }
$mode = "add";
break;
}
switch($mode) {
case "add":
$cn = pg_connect('user=vagrant dbname=app');
if ( $cn == false ) { $err_idx++; $err_value[$err_idx] = "system error"; break; }
$sql = "select * from members order by id desc;";
$rec_members = pg_exec($cn,$sql);
if ( $rec_members == false ) { $err_idx++; $err_value[$err_idx] = "system error"; pg_close($cn); break; }
$members_cnt = pg_numrows($rec_members);
$gid = 0;
if ( $members_cnt != 0 ) { $gid = pg_result($rec_members,0,id); }
$gid++;
$gentymd = date("Y/m/d",time());
$gemail = ereg_replace("\t","",$frm_email);
$sql = "BEGIN TRANSACTION;";
$result = pg_exec($cn,$sql);
if ( $result == false ) { $err_idx++; $err_value[$err_idx] = "system error"; pg_close($cn); break; }
$ng_flg = 0;
$sql = "insert into members values('".$gid."','".chop($gemail)."','".$gentymd."');";
$rd = pg_exec($cn,$sql);
if ( $rd == false ) { $err_idx++; $err_value[$err_idx] = "system error"; $ng_flg = 1; break; }
if ( $frm_mailmg_cnt > 1 ) {
for ( $idx = 0;$idx < $frm_mailmg_cnt;$idx++ ) {
if ( !$frm_id[$idx] ) {
} else {
$sql = "insert into member_magazines values($gid,$frm_id[$idx]);";
$rec_member_magazines = pg_exec($cn,$sql);
if ( $rec_member_magazines == false ) { $err_idx++; $err_value[$err_idx] = "system error"; $ng_flg = 1; break 2; }
}
}
} else {
$sql = "insert into member_magazines values($gid,$frm_id[0]);";
$rec_member_magazines = pg_exec($cn,$sql);
if ( $rec_member_magazines == false ) { $err_idx++; $err_value[$err_idx] = "system error"; $ng_flg = 1; break; }
}
if ( $ng_flg == 1 ) {
$sql = "ROLLBACK TRANSACTION;";
$result = pg_exec($cn,$sql);
if ( $result == false ) { $err_idx++; $err_value[$err_idx] = "system error"; pg_close($cn); }
break;
} else {
$sql = "COMMIT TRANSACTION;";
$result = pg_exec($cn,$sql);
if ( $result == false ) { $err_idx++; $err_value[$err_idx] = "system error"; pg_close($cn); break; }
}
pg_freeresult($result);
pg_freeresult($rec_members);
pg_freeresult($rec_member_magazines);
pg_close($cn);
print "<div align=\"center\">\n";
print "<table border=0 cellspacing=2 cellpadding=2>\n";
print "<tr><td colspan=3 align=\"center\" nowrap> メールマガジン購読完了</td></tr>\n";
print "<tr><td colspan=3 align=\"center\">\n";
print "登録が完了しました。";
print "</td></tr>\n";
print "</table>\n";
print "</div>\n";
break;
}
if ( $err_idx != -1 ) {
print "<br>\n";
print "<div align=\"center\">\n";
print "<table border=0 cellspacing=2 cellpadding=5>\n";
print "<tr><td><font color=\"#ff0000\">\n";
for ($idx=0;$idx<=$err_idx;$idx++) {
print "・$err_value[$idx]<br>\n";
}
print "</font></td></tr>\n";
print "<tr><td align=\"center\">\n";
print "<input type=\"button\" value=\"戻 る\" onclick=\"history.back()\">\n";
print "</td></tr>\n";
print "</table>\n";
print "</div>\n";
}
?>
</td></tr></table>
</form>
</center>
</body>
</html>
診断
1. ロジックとビューの混在
このコードを見て、
先頭の<html>
タグからHTMLが開始しているのですが、<table><tr><td>
タグ以下から、</td></tr></table>
タグまでの200行がPHPコードになります。このPHPコードでは、print
文によるHTMLの出力処理も行われています。
このように、
ビューを動的に出力するためにHTMLの中にPHPコードが混在するのは、
理解しづらいコードは、
まずは、
2. print文によるHTML出力
PHPコードの中では、 下記が実際にコードの中で このコードを、 HTMLの中でPHPを書く場合、 PHPコード全体の流れを見ていきます。今回のコードは大きく分けて14行目から123行目の 1つ目の 単純に考えれば、 この2つの 改善策としては、 前項の つまり、 一見便利なようにも見えるのですが、 また、 このコードは実に実直です。まじめにこつこつと書かれています。まったく同じコードが何度も何度も丁寧に書かれています。同じことを同じように寸分違わず、 たとえば、 では、 さらに接続情報などの設定値は環境に応じて変わるものなので、 前項でロジックとビューを分離したように、 治療すべきポイントを洗い出したところで、print
文にて出力されています。print
文での出力自体が悪いわけではないのですが、print
文でHTMLを出力している個所の一つです。 print "<tr><td><font color=\"#ff0000\">\n";
for ($idx=0;$idx<=$err_idx;$idx++) {
print "・$err_value[$idx]<br>\n";
}
print "</font></td></tr>\n";
<tr><td><font color=#ff0000>
<?php for ($idx=0;$idx<=$err_idx;$idx++): ?>
<?php echo htmlspecialchars($err_value[$idx]) ?>;
<?php endfor; ?>
</font></td></tr>
3. 処理の流れが追いづらい
switch
文と、switch
文に分けられます。どちらも同じ$mode
変数の値で分岐を行っているのですが、switch
文が2つに分けられています。実は1つ目のswitch
文の中で$mode
変数の値を書き換えており、switch
文による処理を実行するか否かが決定されます。switch
文では、$mode
変数が空の場合とadd_
の場合で処理を分岐しています。前者はフォームを表示する初期状態、add_
の場合、$mode
の値をadd
に変更します。そして2つ目のswitch
文では、$mode
変数がadd
の場合のみ登録処理を行う、add_
の処理で入力値検査が正常に終了したあとにそのまま登録処理を続ければよいと思うのですが、switch
文を分けるという構造になっています。なぜこうなったかの理由はわかりませんが、switch
文による条件分岐は、$mode
変数の値はフォームから送信されてくることが想定されているので、$mode
の値をadd
に改ざんして送信すると、switch
文を飛ばして、switch
文にて登録処理を実行できてしまいます。こうなると、4. 外部から送信されてきた値の参照
$mode
変数の値は、$_GET
や$_POST
、$_REQUEST
などHTTPリクエストで送られてくる値を参照するスーパーグローバル変数が見当たりません。では、$mode
変数に送信されてきた値が入るのでしょうか。実は、register_
という機能があり、mode
というパラメータがあると、$mode
変数にその値が格納されるという具合です。5. 同じ処理を実直に書き続けている
pg_
関数が何度も書かれています。しかもDBに接続するための情報も引数に何度も書かれています。同じ作業に嫌気をささずに、pg_
関数であれば、connect_
などの関数を定義して、connect_
だけを修正すればよいのです。function connect_db() {
return pg_connect('user=vagrant dbname=app');
}
// コード上部で設定値を定数定義
// DBの接続先が変わっても、ここだけ変更すればよい
define('DB_USER', 'vagrant');
define('DB_NAME', 'app');
(snip)
// 関数では定数を参照
function connect_db() {
$dsn = sprintf('user=%s dbname=%s', DB_USER, DB_NAME);
return pg_connect($dsn);
}