PHPのデバッグについて




php-debug-function

プログラミングをする、ということはデバッグをするということ

あまりプログラミング経験のない方だとプログラミング、というと

  1. ある問題をどうやったら解決できるかを考える
    1. で考えた方法を使用するプログラミング言語でどのようにしたら具体的に実装できるのかを考える
  2. 実際にプログラミングをする

  3. 動いた!完成!

というようなイメージを持たれる方も多いかもしれません。 しかし、少しでもプログラミング経験のある方は(多少の苦い思い出とともに)「3. と4. の間にはデバッグという恐ろしい難敵がおってな・・・」ということを思われるのではないでしょうか。 実際、少しプログラミングをしてみるとわかることですが、びっくりするほど思った通りにいかないことがあるものです。表示されるはずのものが表示されない、表示されているけれど全く予期しないものが表示されている、そもそもエラーが出ていて動かない、etc.・・・。これらの原因を探ってみるとこれまたびっくりするほどつまらないミスだったりするものです。変数の名前を少し書き間違えていた、サンプルの一部をコピペしてみたら必要な関数などをコピーし忘れていてエラーが出る、などなど。 趣味の範囲であれば「なんか動かなかった!」で諦めてしまっても良いのですが、仕事でプログラミングをしている場合はそうもいかないものです。趣味の場合も何かすっきりしないモヤモヤ感が残るのではないでしょうか。 そこで、あるエラーや予期しない出来事が起こった時になぜそれが発生するのか、どのような条件で発生するのか、どの部分を改善すれはそのエラーや不具合は解決されるのか、ということを調べるためにデバッグを行うことになります。webプログラミングを考えると大きくフロントエンドとバックエンドでそれぞれフロントエンドではJavaScript、バックエンドではPHPやRubyなどがよく使われます。 このうちJavaScriptのエラーや不具合をデバッグするのは比較的楽です(実際のエラー修正が楽、というわけではない)。これはなぜかというと、普段からよく使っているwebブラウザにはだいたい「開発ツール」のようなものが付いていて、エラーが起こればだいたいそのことを表示してくれますし、JavaScriptを1行ずつ実行していってプログラムの動作を少しずつ追っていく、ということが比較的やりやすい環境が整っているからです。 一方webサーバ上で動作するPHPなどをデバッグする、というのはそれに比べると少し面倒です。それは

  • JavaScriptのようにブラウザでプログラムの動作を追いかけることが難しい
  • どんなエラーが出ているかは基本的にサーバのログファイルに出力されるので、エラーの正確な内容が知りたければログファイルを見る必要がある

などの理由があるからです。 (実際はリモートのサーバ上のPHPをうまいことデバッグする方法もあるようですが、そのサーバをどこまで自由に触れるのか、なども関係してきそうです) 私がPHPのでバッグを行うときにとる方法は結局のところ、古典的かつ少し面倒ですが、確実な方法です。 すなわち、

  • プログラムのどこまで実行されているかをブラウザ上に出力する(echoなどを使う)
  • 怪しそうな変数の中身も全てブラウザ上に出力する(var_dump()などを使う)

というよくある方法ですね。 実際にどんなことを行うか、を見てみましょう。以下はそふぃのPHP入門からお借りした今月のカレンダー(一覧表示)を行う簡単なプログラムの例です(エラーを出すために少しだけ改変しています)。

<?php
//Copyright © 2004-2016 Sophy.Y All Rights Reserved.
//http://php-beginner.com/sample/date_time/calendar1.html

$now_year = date("Y"); //現在の年を取得
$now_month = date("n"); //現在の月を取得
$now_day = date("j"); //現在の日を取得
$countdate = date("t"); //今月の日数を取得
$weekday = array("日","月","火","水","木","金","土"); //曜日の配列作成

//見出し部分出力
echo "<div>".$now_year.'年'.$now_month."月のカレンダー</div>\n";

//一覧表示
for( $day=1; $day <= $countdat; $day++ ){ //今月の日数分ループする

//各日付の曜日を数値で取得
//曜日用の配列$weekdayを使い、$weekday[$w]で日本語の曜日が取得できる
$w = date("w", mktime( 0, 0, 0, $now_month, $day, $now_year ) );

//スタイルシートの値設定ここから-----------------------------------

switch( $w ){
case 0: //日曜日の文字色
$style = "color:#C30;";
break;
case 6: //土曜日の文字色
$style = "color:#03C;";
break;
default: //月~金曜日の文字色
$style = "color:#333;";
}

if( $day == $now_day ){
$style = $style." background:silver"; //今日の背景色
}
//スタイルシートの値設定ここまで-----------------------------------

$line = $day."日(".$weekday[$w].")"; //1行の定義:日付(曜日)

//スタイルシートを挿入・1行ごとに改行して出力
echo '<div style="'.$style.'">'.$line."</div>\n";
}
?>

このコードに適当な名前をつけてサーバにアップロードし、そのファイルにブラウザでアクセスしてみます。ちゃんと動作すれば今月のカレンダー表示されるはずです。しかし、上記のコードをそのままコピペしてもちゃんと表示されません。 さて、どこが間違っていてちゃんと動かないのでしょうか?しっかり目を凝らして見ると間違っている部分があるのがわかるのですが、すぐに見つけるのは難しいでしょう。(これくらいの短いコードであっても一箇所のミスを探し出すのは結構大変なものです) そこで、とりあえずコードのどこまで実行されているのかを見るためにechoを仕込んでみましょう。

<?php
//Copyright © 2004-2016 Sophy.Y All Rights Reserved.
//http://php-beginner.com/sample/date_time/calendar1.html

$now_year = date("Y"); //現在の年を取得
$now_month = date("n"); //現在の月を取得
$now_day = date("j"); //現在の日を取得
$countdate = date("t"); //今月の日数を取得
$weekday = array("日","月","火","水","木","金","土"); //曜日の配列作成

//見出し部分出力
echo "<div>".$now_year.'年'.$now_month."月のカレンダー</div>\n";

echo "checkpoint 1<br>"; //------------------------------------------------------ ①

//一覧表示
for( $day=1; $day <= $countdat; $day++ ){ //今月の日数分ループする

//各日付の曜日を数値で取得
//曜日用の配列$weekdayを使い、$weekday[$w]で日本語の曜日が取得できる
$w = date("w", mktime( 0, 0, 0, $now_month, $day, $now_year ) );

echo "checkpoint 2<br>"; //------------------------------------------------------ ②

//スタイルシートの値設定ここから-----------------------------------

switch( $w ){
case 0: //日曜日の文字色
$style = "color:#C30;";
break;
case 6: //土曜日の文字色
$style = "color:#03C;";
break;
default: //月~金曜日の文字色
$style = "color:#333;";
}

if( $day == $now_day ){

echo "checkpoint 3<br>"; //------------------------------------------------------ ③

$style = $style." background:silver"; //今日の背景色

echo "checkpoint 4<br>"; //------------------------------------------------------ ④

}
//スタイルシートの値設定ここまで-----------------------------------

$line = $day."日(".$weekday[$w].")"; //1行の定義:日付(曜日)

echo "checkpoint 5<br>"; //------------------------------------------------------ ⑤

//スタイルシートを挿入・1行ごとに改行して出力
echo '<div style="'.$style.'">'.$line."</div>\n";

echo "checkpoint 6<br>"; //------------------------------------------------------ ⑥

}
?>

6箇所に「現在どこまで実行できているか」を出力するechoを挿入しました。この部分が実行されると「checkpoint X」が表示されます。原始的なブレークポイントみたいなものですね。 さて、上記のコードを実行してみると「checkpoint 1」は表示されますが、「checkpoint 2」は表示されません。ということはコードの中の①と②の間にミスがありそうです。よく探してみると以下の部分

//一覧表示
for( $day=1; $day <= $countdat; $day++ ){ //今月の日数分ループする

で変数っぽい「$countdat」というものがありますが、変数定義のところを見るとこれは正しくは「$countdate」です。これを修正すると正しく動くようになります。 このようにコードの中にechoなどを挿入してどこまで実行されているかを見ることでエラーがどこにあるか、という範囲を絞ることができます。 さて、次はvar_dump()で変数の中身を表示してみる例です。先ほどと同じカレンダー表示を行う次のコードをコピペして実行してみます。

<?php
//Copyright © 2004-2016 Sophy.Y All Rights Reserved.
//http://php-beginner.com/sample/date_time/calendar1.html

$now_year = date("Y"); //現在の年を取得
$now_month = date("n"); //現在の月を取得
$now_day = date("N"); //現在の日を取得
$countdate = date("t"); //今月の日数を取得
$weekday = array("日","月","火","水","木","金","土"); //曜日の配列作成

//見出し部分出力
echo "<div>".$now_year.'年'.$now_month."月のカレンダー</div>\n";

//一覧表示
for( $day=1; $day <= $countdate; $day++ ){ //今月の日数分ループする

//各日付の曜日を数値で取得
//曜日用の配列$weekdayを使い、$weekday[$w]で日本語の曜日が取得できる
$w = date("w", mktime( 0, 0, 0, $now_month, $day, $now_year ) );

//スタイルシートの値設定ここから-----------------------------------

switch( $w ){
case 0: //日曜日の文字色
$style = "color:#C30;";
break;
case 6: //土曜日の文字色
$style = "color:#03C;";
break;
default: //月~金曜日の文字色
$style = "color:#333;";
}

if( $day == $now_day ){
$style = $style." background:silver"; //今日の背景色
}
//スタイルシートの値設定ここまで-----------------------------------

$line = $day."日(".$weekday[$w].")"; //1行の定義:日付(曜日)

//スタイルシートを挿入・1行ごとに改行して出力
echo '<div style="'.$style.'">'.$line."</div>\n";
}
?>

一見動作していそうですが、背景が灰色になる日は今日の日付になるはずがおかしな日がハイライトされてしまっていると思います。 背景をハイライトしているのは以下の部分です。

if( $day == $now_day ){
$style = $style." background:silver"; //今日の背景色
}

ループごとに1づつ増えていく変数$dayと$now_dayの値が一致した日の背景をシルバーにする、という処理なのでこの$now_dayの値がおかしい可能性があります。そこで以下のようにしてこの$now_dayがどんな値か見てみましょう。

if( $day == $now_day ){
echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!";
var_dump($now_day);
echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!";
$style = $style." background:silver"; //今日の背景色
}

実行してみると以下のようになりました。

1 図1 実行例

この記事を書いているのは20日なので$now_dayには20が入っていて欲しいのですが、$now_dayの値は5になっています。この$now_dayは以下のようにして定義されています。

$now_day = date("N");

date関数に”N”という引数を与えた時に返ってくるのは「今日の曜日を1(月曜日)~7(日曜日)で表した数字」です。(PHPのdate関数で日付のフォーマット変更”参照) つまり、ここは以下のようになっているのが正しいのですね。

$now_day = date("j"); //1~31のゼロなしの日を返す。

このように、コードの実行結果が意図しない結果になった時に、関連する部分の変数の中身を見てみる、というのは重要です。 本来はこうあるべき、という実行結果を想定しながら、とりあえずおかしな結果になってしまっているところに深く関わる変数、次にその変数に関わる別の変数、と順にたどっていくことによってバグを見つけられることが多いと思います。 この方法は実際面倒くさいのですが一番基本的なことでもあると思うので困った時はとりあえずechoとvar_dumpと思っておいていいと思います。 もう少し効率的にデバッグを行いたい場合、デバッグ用の関数を作ってしまう、というのも良いでしょう。

function display_debug($key, $value, $debug=false) {
    if($debug){
        echo ''.$key . " = ";
        switch (gettype($value)) {
            case 'string' :
            echo $value;
            break;
            case 'array' :
            case 'object' :
            default :
            echo '';
            print_r($value);
            echo ''; 
            break;
        } 
    } 
}

上のコードはあるところで見かけたデバッグ用関数の例です。引数に表示したい変数名と表示するかどうかのフラグを渡してやると、変数の型によって表示方法を変えてくれる、という関数ですね。

display_debug("表示したい変数名",$表示したい変数名,1);

とすると

表示したい変数名 = $表示したい変数名の中身の値

という形で表示してくれます。 このように自分がデバッグしやすいように独自の関数を作っていく、というのも良い考えです。