弊社では、デフォルトではPHPStormというIDEを使っています。
で、下記に書くことは他のIDEでもできるかもしれませんが、今回検証やサンプルに使うのはPHPStormなので、タイトルが
「PHPStorm デバッグの小技」
となってますが、ほかのIDEでも同様の機能があるものはあると思います。
この記事は、普通のステップイン、ステップオーバーなどの基本のデバッグはもうできているというプログラミング中級者さん向けでお話していきます。
では早速。
①ブレークポイントの機能をうまく使う
1.ある例外が起こった時だけブレークするようにする
今回、この記事を書こうと思ったきっかけなんですが、こんな便利な機能があるのを私は知りませんでした!!
ある例外が起こった時に、エラーログで例外の起こるソースコードの場所はわかったとしても、パラメーターが多い場合など、どういう条件で例外が起こるのかがわからなかったりすると思います。
そんな時、例外が起こるまでステップオーバーをポチポチ押したりしていませんか?
この機能を使うと、例外が起きたときだけブレークしてくれます
次のサンプルスクリプトを使います。
<?php
$array = ['りんご','メロン','バナナ'];
foreach ($array as $key => $value){
if($key === 2){
throw new LogicException('バナナはダメ');
}
print $value;
}
やり方です。
①ヘッダーメニューのRun→View Break Points をクリック
②下記図のような画面になるので、左上のプラスボタンをクリックし、PHP Exception Breakpointsをクリック

③Exceptionの名前を入力 ここでは、LogicExceptionを入力します。
上記③までやってデバッグをしてみてください。下記のExceptionで止まります。

デバッグウィンドウは、次のようになっています。

便利!
2. 特定の条件の時だけブレークするようにする
ブレークポイントを設定してデバッグする際、例えば100回ループが回るとして、99回目で問題が起こるとして、98回ポチポチステップオーバーを回していませんか??
99回目に手っ取り早くワープする方法があります。ちなみに、ほかの方法でもできますが、それは後述します。
やり方です。
①ブレークポイントを右クリックします。

② Conditionとあるところに条件を入れます。ここでは、サンプルとして$keyが1の時だけブレークするようにします。

③ ?がついたブレークポイントのアイコンができました!

デバッグを実行してみてください。
$key が 0 の時をすっ飛ばして、$keyが1の時だけブレークしてくれます!

3. 何度も不要なブレークポイントでブレークするのを避ける
プログラムの流れを追いたい時、最初の1回だけブレークすればいい場合がありますよね。しかし、最初の一回だけでいいのに繰り返すループ処理があって、何度もブレークする時、
「ああ、面倒だな」
とつぶやきながら、ポチポチステップオーバーやResumeをしていませんか??
1回ブレークしたら、後はブレークしないようにすることができます。
①普通にブレークポイントを設定する

②例えば、最初のブレークだけで、後はもう必要ないという場合は、デバッグウィンドウのMute Breakpointsという串団子みたいなアイコンをクリックします。

③Resumeをクリックすると、もうブレークポイントでブレークしません。

ただし、このやり方だと、ほかにもブレークポイントがあるときに一気にブレークしなくなります。
一部のブレークポイントだけ、1回ブレークされればよい、という場合を次に紹介します。
4. 1回だけブレークされればよいブレークポイントを作る
3のやり方で、1回ブレークしたら後はミュートという方法がありますが、一気にミュートされると困る、ほかのブレークポイントでは止まってほしい、という場合に使える方法です。
①普通にブレークポイントを設定する。
②Run → View Break Points とクリックすると、下記のようなペイン(表示領域のこと)が表示されます。①のブレークポイントをクリックして
Remove once hit
というチェックボックスにチェックをつけます。

そうすると、上記は例えば7行目と10行目にブレークポイントを設定していますが、7行目だけ Remove once hit にチェックします。

すると7行目では最初の1回にブレークしてその後ブレークポイントがなくなりますが、10行目では2回ブレークします。
4. 特定の値があった場合のみ、consoleに表示する
ブレークポイントを通った時に、変数が特定の値になる条件を知りたい場合があると思います。
そんな時にこれが使えそうです。先程から何度か登場している、ブレークポイントペイン。皆様、もうこいつと仲良しですね?
実はLogという機能があり、さらにはそれに式を入れることができます。
$valueがメロンになるときがいつなのか、記録したい時があったとします。

すると、デバッグコンソールに次のように表示されます。
2回目のループだけ、1と表示されているのがわかるでしょうか?

2. 特定の条件の時だけブレークするようにする と似ていますが、こっちは例えばループをざっと回して、どういうパターンの時に値が〇〇なのか、などを俯瞰で見たい時などに使えると思います。
②変数の中身を書きかえる
ハァハァ… ブレークポイントだけで、結構もりだくさんでしたね💦
これはメジャーなのでご存じの方も多いと思いますが、紹介しておきます。
2の②でも書きましたが、ブレークポイントを設定してデバッグする際、例えば100回ループが回るとして、99回目で問題が起こるとして、98回ポチポチステップオーバーを回していませんか??
ブレークポイントに特定の条件をつけることも可能ですが、デバッグ中に変数の値を書き換えることで、このような無駄な作業を省くことができます。
サンプルでいいますと、例えば7行目にブレークポイントを設定してデバッグします。

たとえば、このループのデバッグが面倒なので、一気にLogicExceptionを起こす$keyを2にしたいと思った場合、デバッグコンソールで次のようにします。
①編集したい変数を右クリックして、Set Value をクリック

②好きな値を入れます。ここでは、2を入れちゃいます。

③するとすぐ評価されて、LogicExceptionを投げるところに行きます。

これってめちゃくちゃ応用が効いて、いろんなことに使えるんですよね。
普通に関数を開発中に、「この値飛んで来たらどういう処理にしよー?」とか思っている時に、実際にその値を関数に投げるのが呼び出しが深くて面倒な時があります。
そんな時にも使えるし、例えば、Cookieの設定だとか、サーバー変数の設定だとか、特定の環境を再現しないといけないデバッグだとかに、いちいち本物の環境を設定するわけにいかない場合だとかにも使えます。
③Watchを使う
あんましデバッグ時にWatchって使いませんよね(笑)。実は私もです。
ただ、使った方がいいケースがあると思うので、ご紹介しておきます。
今までのサンプルだとあまりに簡単すぎるので、少しだけ複雑なサンプルを作ります。
<?php
class Fruit{
public string $name;
public bool $with_milk; // 牛乳とまぜてよいか
private int $calorie;
public function __construct(string $name, bool $with_milk, int $calorie){
$this->name = $name;
$this->with_milk = $with_milk;
$this->calorie = $calorie;
}
function mixWithMilk():bool{
if($this->with_milk){
$this->name = $this->name.'みるく';
$this->calorie = $this->calorie + 100;
return true;
}else{
return false;
}
}
/**
* 一日の消費カロリーの何分の1かを表示
* @return float|int
*/
function getCalorieOfOneDay(){
return $this->calorie/3000;
}
}
$apple = new Fruit('りんご', false, 50);
$strawberry = new Fruit('いちご', true, 31);
$melon = new Fruit('メロン', false, 70);
$banana = new Fruit('バナナ', true, 65);
$fruits = [$apple, $strawberry, $melon, $banana];
foreach ($fruits as $key => $fruit){
print $fruit->name;
print "<br>";
$result = $fruit->mixWithMilk();
if($result){
print "牛乳とまぜたら". $fruit->name. "になったよ";
}else{
print "牛乳とまぜたらダメー!";
}
print "<br>";
print "-------------------------";
print "<br>";
}
フルーツの配列がクラスになりました。フルーツがいくつかあって、牛乳とまぜると名称が変わり、カロリーもちょっと変わる、というプログラムです。(カロリーについては全くのデタラメです。)
実行すると、結果は下記のようになります。

ここで、このFruitというオブジェクトの
getCalorieOfOneDay()
というメソッドは実行されていないのですが、実行したらどうなるかをシミュレートできます。それがWatchです。
①57行目にブレークポイントを設定しておきます。

②デバッグを実行します。
③デバッガーウィンドウのコンソールの一番右端の+ボタンをクリックします。

New Watchと出ると思うんですが、そこで
$fruit->getCalorieOfOneDay()
と入力します。

すると、ステップ実行により、変化する
$fruit->getCalorieOfOneDay()
の値を見ることができます。

「え?それで何が嬉しいんですか??Watchって何に役立つんですかー?!」
と言いたくなるかもしれません。
しかし、この機能も可能性は無限大。
使うケース①
例えば このプログラムで言えば、一日の総消費量に対するフルーツミックスジュースの割合、つまりgetCalorieOfOneDay()が0.03を超えたときに発生するバグがあったとします。
しかし、このプログラムのように
mixWithMilk()
みたいな関数でプロパティの値が加工されていると、プロパティの値がどこで変わっているのかトンとわからないときがあります。
その場合に使えます。 ずっと見ているのはだるい場合はWatchに式を入れることができますので、
$fruit->getCalorieOfOneDay()>0.03
というWatchを追加します。

すると、TrueとFalseだけを返してくれるので、より見やすいです。
使うケース②
例えば、人の目で見るのがつらい数字とかがありますよね。
弊社では、配送業向けのODIN リアルタイム配送システムというシステムを開発していますが、0時0分0秒を0として、経過秒数を秒で表すという数字をよく扱います。41200秒は、60×60で割ると11.44で、大体11時半ぐらいだなということがわかります。
ただ、11時半ぐらいと言えばイメージがわきやすいのですが、 41200秒 は正直??って感じですよね。
開発している際のこのわかりやすさって開発効率に大事だと思うんですよ。
なので、そのプロパティを3600で割る数字をWatchしておくと、人間が把握しやすいので、ちょっとだけ開発効率が上がります。
Watchには気楽に式を入れられますので、例えばさっきの
getCalorieOfOneDay()
が小数点で見づらいな、と思えば、×100を入れておくこともできます。
下記のようにさっきWatch式をプラスしたところの空白のスペースに
$fruit->getCalorieOfOneDay()*10

と入力すると、その結果をresult というところに表示してくれます。
ただ、「このresult消えるよ…」ということで、すぐに消えてしまうので、実行の際に見てたい場合は、Watch式を入力したほうがいいです。
以上、そのほかにもデバッガってめちゃくちゃいっぱい機能があります。
ぜひPHPStorm本家のサイトなども見てみてください。
https://pleiades.io/help/phpstorm/examining-suspended-program.html#find-execution-point