Xdebug3 var_dumpもきれいに表示したいしデバッグもしたいし、というときの設定

Xdebugがバージョン3になってました。

https://xdebug.org/

環境は、Windows+PHPStorm+PHP7.4(Xampp)です。

PHP7.2→PHP7.4 にしたくて、Xamppを新しくし、Xdebugを設定しなおして気づきました。

まず、PHPStormでデバッグできず、ちょっとはまりました。

注意点は

①PHPStormのバージョンが2020.3以降じゃないとXdebug3に対応していない
②ポート番号がデフォルト9003になっている

です。

さて、表題の話なんですが、まず、デバッグしたいために最初は次のようにphp.iniを設定していました。

zend_extension = D:\xampp2\php\ext\php_xdebug-3.0.1-7.4-vc15-x86_64.dll
xdebug.remote_enable=1
xdebug.remote_host=127.0.0.1
xdebug.remote_port=9001
xdebug.var_display_max_children=-1
xdebug.var_display_max_data=-1
xdebug.var_display_max_depth=-1
xdebug.client_host = 127.0.0.1
xdebug.client_port = 9001
xdebug.mode = debug

最後のxdebug.modeがポイントです。

xdebug.mode = debug

にしないとPHPStormでデバッグできません。(ほかのIDEで試してないのでほかのIDEでどうかわかりません。)

しかし、このままにしておくと、PHPerは必ず使うであろう、var_dump関数の実行結果が美しく表示されません。

きれいに表示されないvar_dumpの結果
きれいに表示されないvar_dumpの結果

こんなん見てられないし、辛いですよね~(>_<)


xdebug.mode = develop

にすると、美しく整形されて表示されます。developがデフォルトだそうです。

美しく出力されたvar_dumpの結果。これならだれでもわかるね!⊂(^-^)⊃

しかし、このままではデバッグできません。

一体どうして…。

と、ぼんやりXdebugの作者のDerickさんのTwitterを見ていたら

Xdebug3のModeについて説明動画があるじゃないですか!!!

Xdebugを作ってくれているだけでもありがたいのに、動画まで…。その優しさに涙が溢れますね…。(>_<)

というわけで、上記を見ると、modeが二つ平行で設定できることがわかります。

xdebug.mode = develop,debug

上記のように設定すると、var_dumpも美しく出力され、デバッグもできました!⊂(^-^)⊃

ちな、Xdebug関係のphp.iniの設定を下記に書いておきます。(私はデバッグのポートを9001で運用しています。)

[xdebug]
zend_extension = D:\xampp2\php\ext\php_xdebug-3.0.1-7.4-vc15-x86_64.dll
xdebug.remote_enable=1
xdebug.remote_host=127.0.0.1
xdebug.remote_port=9001
xdebug.var_display_max_children=-1
xdebug.var_display_max_data=-1
xdebug.var_display_max_depth=-1
xdebug.client_host = 127.0.0.1
xdebug.client_port = 9001
xdebug.mode = develop,debug

php.iniを変更した後は、Webサーバーの再起動を忘れないように!

ありがとう、Derickさん!ロンドンいろいろ大変そうですが、がんばってください。

PHPUnit require_once地獄からの脱出 2 autoloadを使うんや編

前回、PHPUnit require_once地獄からの脱出 1 まずはrequire_onceでテスト元のファイルをどう読み込むか編 で大分長々と書きましたが、PHPUnitでテストファイルからrequire_onceでテスト元のファイルを読みだすのが面倒だという話です。

どうすればいいのかといいますと、phpunit.xmlを下記のように書き換えます。

<?xml version="1.0" encoding="UTF-8"?>
<phpunit
        bootstrap = "vendor/autoload.php"
        colors = "true">
</phpunit>

下記が肝心の部分です。

bootstrap = "vendor/autoload.php"

ついでに、色がつくようにしています。

colors = "true"

緑の光あれ。

MonkeyTest.phpから、

require_once “class/Monkey.php”;

を消してみます。

<?php

use MonkeyWorld\Monkey; //追加
use PHPUnit\Framework\TestCase;

class MonkeyTest extends TestCase
{

    public function testScream()
    {
        $monkey = new Monkey();
        ob_start();
        $monkey->scream();
        $actual = ob_get_clean();
        $expected = 'ウキー!!!';
        $this->assertEquals($expected, $actual);
    }

}

testScreamを実行してみます。

OKです!緑の色もついて、みやすいですね!( ˊᵕˋ )

テスト元のMonkey.php に talkToChimpanzee()というメソッドが追加されました。

<?php
namespace MonkeyWorld;

class Monkey
{

    public function scream(){
        echo "ウキー!!!";
    }

    public function talkToChimpanzee(){
        Chimpanzee::talk();
    }

MonkeyTest.phpもtalkToChimpanzeeをテストできるように書き換えます。

use PHPUnit\Framework\TestCase;

class MonkeyTest extends TestCase
{

    public function testScream()
    {
        $monkey = new Monkey();
        ob_start();
        $monkey->scream();
        $actual = ob_get_clean();
        $expected = 'ウキー!!!';
        $this->assertEquals($expected, $actual);
    }

   public function testTalkToChimpanzee()
    {

        $monkey = new Monkey();
        ob_start();
        $monkey->talkToChimpanzee();
        $actual = ob_get_clean();
        $expected = 'こんにちは。おいらチンパンジーだよ。';
        $this->assertEquals($expected, $actual);

    }
}

今度はMonkeyTest.phpのMonkeyTest.classをテストとして実行してみます。

OKですね!

ちなみに、上記はPHPStormから実行していますが、コマンドプロンプトから実行すると、次のようになります。

phpunitの後に引数をつけずに、


phpunit tests/MonkeyTest.php

とするだけです 。

PHPUnitの設定ファイル、phpunit.xmlで実行の初期に

ところで、実験的にclass/Hoge.phpというやつを追加します。

<?php

class Hoge
{

    public function talk(){
        print "おいらHogeクラスです";
    }
}

こいつには、namespaceの宣言がないことに注意してください。

Monkey.phpにtalkToHogeというメソッドを追加します。

<?php
namespace MonkeyWorld;

class Monkey
{

    public function scream(){
        echo "ウキー!!!";
    }

    public function talkToChimpanzee(){
        Chimpanzee::talk();
    }

    public function talkToHoge(){
        Hoge::talk();
    }
}

MonkeyTest.phpに次のテストメソッドを足します。


    public function testTalkToHoge()
    {

        $monkey = new Monkey();
        ob_start();
        $monkey->talkToHoge();
        $actual = ob_get_clean();
        $expected = 'おいらHogeクラスです';
        $this->assertEquals($expected, $actual);

    }

MonkeyTestクラスを実行します。

Error : Class 'MonkeyWorld\Hoge' not found
D:\xampp\htdocs\autoloadSample\class\Monkey.php:16
D:\xampp\htdocs\autoloadSample\tests\MonkeyTest.php:38

というエラーが出て、実行が失敗したみたいです。(>_<)

というか、そもそも普通にこれはブラウザからPHPを実行しても実行できません。最初のサンプルでも使った、トップディレクトリにあるhoge.phpを実行します。

ディレクトリ・ファイル構成

hoge.phpは次の通りです。

<?php

require_once "vendor/autoload.php";

use MonkeyWorld\Monkey;
require_once "class/Hoge.php";

$monkey = new Monkey();
$monkey->scream();
echo "<br>";

$monkey->talkToChimpanzee();
$monkey->talkToHoge();

これを実行すると次のようになります。

 Fatal error: Cannot declare class Hoge, because the name  is already in use in D:\xampp\htdocs\autoloadSample\class\Hoge.php on  line 3

というエラーが出て実行できません。

あんまり深堀りできてないですが、これ、Hogeクラスが重複してますよってエラーなんですよね。なんででしょうか。わかる人、教えてください。m(_ _)m

仕方ないので、Hoge.phpに次のようにnamespaceをつけます。

<?php
namespace MonkeyWorld;

class Hoge
{

    public function talk(){
        print "おいらHogeクラスです";
    }
}

すると、エラーもなく実行できるようになります。

hoge.phpの実行結果↓

PHPUnitの実行結果↓

namespaceは大変大事ですね。

ちなみに、autoloadで読み込むnamespaceの指定については、下記の記事をご参考にしてみてください。

PHP require_onceを使わない、 autoloadを使ってクラスを読み込む方法 

PHPUnit require_once地獄からの脱出 1 まずはrequire_onceでテスト元のファイルをどう読み込むか編

前回、PHPUnit require_once地獄から脱出したい!という理由で、autoloadについて紹介しました。

まぁ、まだあわてるような時間じゃない。( ˊᵕˋ )  本題のPHPUnitで使うのがどうやるかって話をゆっくりとしていきます。

ということで、そもそもPHPUnitというかPHPでのファイル読み込みがどうなっているかを書いておきたいと思います。

require_once地獄は、結局はファイルが思うように読み込みできずに 次のエラーが出てしまう地獄です。

Warning: require_once(class/Monkey.php): failed to open stream: No such file or directory in...

なぜこうなってしまうのかをサンプルを使って紐といていきます。

というわけで、まずは、前回の「PHP require_onceを使わない、 autoloadを使ってクラスを読み込む方法」で使ったのと同じサンプルを使います。

下記に、tests/MonkeyTest.phpというディレクトリとphpファイルを置きました。

MonkeyTest.phpの中身は下記の通り。

<?php

use PHPUnit\Framework\TestCase;

require_once "../class/Monkey.php";

class MonkeyTest extends TestCase
{

    public function testScream()
    {
        $monkey = new Monkey();
        ob_start();
        $monkey->scream();
        $actual = ob_get_clean();
        $expected = 'ウキー!!!';
        $this->assertEquals($expected, $actual);
    }
}

require_once “../class/Monkey.php”;

にご注目ください。MonkeyTest.phpから見て、テスト元のファイルである、Monkey.phpを相対パスで指定しています。

テスト元のファイル、Monkey.phpは次の通りです。

<?php
namespace MonkeyWorld;

class Monkey
{

    public function scream(){
        echo "ウキー!!!";
    }
}

で、Webサーバーにインストール済みのPHPUnitを走らせます。
PHPStormのPHPUnitを走らせる設定は、下記の場所にあります。 
ファイル→Settings→Languages&Frameworks→PHP→Test Frameworks

ここでは、私はWindows10でXamppを使って開発してますんで、ここにすでにインストールしてあったphpunitのpharファイルを使います。

D:\xampp\php\phpunit.phar

pharファイルとは、かみ砕いていうと、Windowsで言うexeファイルみたいなものです。これを実行するとPHPUnitが実行できるというわけです。

PHPStormからは、下記の赤丸の部分にある、再生みたいな緑の三角のボタンをクリックで、テストがメソッド単位で実行できます。(PHPStorm使ってないよ!という方は後でコマンドプロンプトからやる方法書いてますので、スルーしてください。)

クリックすると下記のような画面が開いて、MonkeyTest.testScream の実行の設定をしてください、と出ます。

Interpreterとは、PHPの実行環境です。

ここではサンプルなので、次のように指定しておきます。

Use alternative configuration fileを指定しないと、
Error:Path to conficuration file should be specified in … というエラーが出てダメだったので、図の下にあるように、phpunit.xmlを適当に作ります。

下記がphpunit.xmlです。ちなみに、このファイルはPHPUnitをどう実行するかを決めるとても大切なファイルです。あとでちゃんとやりますが、ここではもうこれ以上設定をなくせないぐらいのミニマムな設定で、とりあえずおいておきます。

<?xml version="1.0" encoding="UTF-8"?>
<phpunit>
</phpunit>

phpunit.xmlはディレクトリのトップにおきます。

やっと、MonkeyTestのtestScream()を実行することができます!

(;゚∀゚)=3ハァハァ、ここまで長かった…。

Shift+F10でテストが実行できます。

すると、残念!

Warning: require_once(../class/Monkey.php): failed to open stream: No such file or directory in D:\xampp\htdocs\autoloadSample\tests\MonkeyTest.php on line 7
Call Stack:
0.0087 898656 1. {main}() D:\xampp\php\phpunit.phar:0
0.0965 10543104 2. PHPUnit\TextUI\Command::main() D:\xampp\php\phpunit.phar:660
0.0965 10551968 3. PHPUnit\TextUI\Command->run() phar://D:/xampp/php/phpunit.phar/phpunit/TextUI/Command.php:69
0.0986 10557432 4. PHPUnit\TextUI\TestRunner->getTest() phar://D:/xampp/php/phpunit.phar/phpunit/TextUI/Command.php:81
0.0992 10595224 5. PHPUnit\Framework\TestSuite->addTestFiles() phar://D:/xampp/php/phpunit.phar/phpunit/Runner/BaseTestRunner.php:81
0.0992 10595224 6. PHPUnit\Framework\TestSuite->addTestFile() phar://D:/xampp/php/phpunit.phar/phpunit/Framework/TestSuite.php:340
0.0992 10595224 7. PHPUnit\Util\FileLoader::checkAndLoad() phar://D:/xampp/php/phpunit.phar/phpunit/Framework/TestSuite.php:267
0.0992 10595432 8. PHPUnit\Util\FileLoader::load() phar://D:/xampp/php/phpunit.phar/phpunit/Util/FileLoader.php:40
0.0994 10623056 9. include_once('D:\xampp\htdocs\autoloadSample\tests\MonkeyTest.php') phar://D:/xampp/php/phpunit.phar/phpunit/Util/FileLoader.php:49

上記のようなエラーが出て、../class/Monkey.php が読み込めませんでした、と出ますね。

これは、コマンドプロンプトでPHPUnitを次のように走らせた場合と同じです。

D:\xampp\htdocs\autoloadSample>phpunit testScream tests/MonkeyTest.php

( テストをメソッド単位で走らせるには、メソッド名をファイル名の前に置きます。)

つまり、PHPUnitはプロジェクトのトップ(ここではD:\xampp\htdocs\autoloadSample)で実行されているので、MonkeyTest.phpで指定するテスト元のファイルの指定は一番最初に実行されるので、Monkey.phpの読み込み指定は、プロジェクトのトップから行わないといけないわけですね。

それでは、次のようにMonkeyTest.phpを変更します。


<?php

use PHPUnit\Framework\TestCase;

require_once "class/Monkey.php"; //書き換え

class MonkeyTest extends TestCase
{

    public function testScream()
    {
        $monkey = new Monkey();
        ob_start();
        $monkey->scream();
        $actual = ob_get_clean();
        $expected = 'ウキー!!!';
        $this->assertEquals($expected, $actual);
    }
}

Shift + F10で実行します。


Testing started at 12:37 …
D:\xampp\php\php.exe D:\xampp\php\phpunit.phar --configuration D:\xampp\htdocs\autoloadSample\phpunit.xml --filter "/(MonkeyTest::testScream)( .*)?$/" --test-suffix MonkeyTest.php D:\xampp\htdocs\autoloadSample\tests --teamcity
PHPUnit 8.5.8 by Sebastian Bergmann and contributors.
Error : Class 'Monkey' not found
D:\xampp\htdocs\autoloadSample\tests\MonkeyTest.php:12
Time: 98 ms, Memory: 12.00 MB
ERRORS!
Tests: 1, Assertions: 0, Errors: 1.
Process finished with exit code 2

エラーですね…。

「結局、Class ‘Monkey’ not found って出ますけど!なんなんですか!!!」

まぁまぁ、まだあわてるような時間じゃないって冒頭に言いましたね。( ˊᵕˋ )

テスト元のファイル、Monkey.phpをよく見てみると

<?php
namespace MonkeyWorld;

class Monkey
{

    public function scream(){
        echo "ウキー!!!";
    }
}
namespace MonkeyWorld;

ってなってるんですよね。前回の投稿、PHP require_onceを使わない、 autoloadを使ってクラスを読み込む方法 で、MonkeyクラスはMonkeyWorldというnamespaceの中のクラスになっていたのでした。

というわけで、次のようにMonkeyTest.phpを書き換えます。

<?php

use MonkeyWorld\Monkey; //追加
use PHPUnit\Framework\TestCase;

require_once "class/Monkey.php";

class MonkeyTest extends TestCase
{

    public function testScream()
    {
        $monkey = new Monkey();
        ob_start();
        $monkey->scream();
        $actual = ob_get_clean();
        $expected = 'ウキー!!!';
        $this->assertEquals($expected, $actual);
    }
}

Shift+F10で実行します。

Time: 97 ms, Memory: 12.00 MB
OK (1 test, 1 assertion)
Process finished with exit code 0

となって、やっとOKが出ました!⊂(^-^)⊃

コマンドプロンプトからも実行します。

OKです!!

しかし、上記は上記のようにやって事なきを得ましたが、実際は例えばクラスからほかのクラスを読み込んだり、実際に動作するプログラムでの実行時に読み込まれるファイルがいっぱいあったりして、それらをrequire_onceでファイルパスを考えてどうにかするのはかなり大変なのです。

そこで、autoloadが活躍します。が、長くなったので続きます…。

間違いなどありましたら、お気軽にご指摘ください。

秋の山って気持ちいいですね!

次回はこちら↓

PHP require_onceを使わない、 autoloadを使ってクラスを読み込む方法 

私、恥ずかしながら…。(*ノωノ)

今までPHPをやっていて、autoloadという仕組みがあることは知っていましたが、ライブラリを読み込むときとかに使うぐらいでちゃんと自分で使えていませんでした。

ただ、今回あるプロジェクトを見ていて、PHPUnitがrequire_once地獄になっておる…(´ω`) と思い、どうにかしたく、autoloadに取り組むことにしました。

まず、シンプルなPHPプロジェクトを作ってみます。

環境はWindows10 Pro, PHP7.2.31です。

下記のように、classの下にHogeとMonkeyというクラスがあり、トップディレクトリにhoge.phpがあります。

hoge.phpの初期状態はこんな感じです。

<?php

echo "ほげだよ<br>";

$monkey = new Monkey();
$monkey->scream();

class/Monkey.phpの初期の中身はこんな感じです。

<?php

class Monkey
{

    public function scream(){
        echo "ウキー!!!";
    }
}

autoloadを導入するにあたり、まず必要なのはcomposerです。

composerのインストールなどは、こちらなどをご覧ください。
ここではcomposerは普通に使えるものとして進めます。

composer.jsonを次のように作ります。

{
  "autoload": {
    "psr-4": {
      "MonkeyWorld\\": "class/"
    }
  }
}

composer install

をします。

私はPHP Stormを使っていて、PHP StormではcomposerのコマンドをPHP Stormから使えるようになってるので、次の赤丸の箇所をポチリとやるだけです。便利!✧٩(ˊωˋ*)و✧

そうすると、下記のようにvendorというディレクトリができて、autoload〇〇ってファイルたちもできてますね。

composer.jsonに書いた記述の内容は、classというディレクトリ以下が”MonkeyWorld というnamespaceになって自動的に読み込みができるという内容のはずです。

hoge.phpを次のようにします。

<?php

use MonkeyWorld\Monkey;
echo "ほげだよ<br>";


$monkey = new Monkey();
$monkey->scream();

Monkeyクラスを次のようにします。

<?php
namespace MonkeyWorld;

class Monkey
{

    public function scream(){
        echo "ウキー!!!";
    }
}

上記のコードでは、namespaceを追加したんですね。

これでhoge.phpからMonkeyクラスが読み込めそうじゃないですか。

しかし、結果は

( ! ) Fatal error: Uncaught Error: Class ‘MonkeyWorld\Monkey’ not found in D:\xampp\htdocs\autoloadSample\hoge.php on line 7
( ! ) Error: Class ‘MonkeyWorld\Monkey’ not found in D:\xampp\htdocs\autoloadSample\hoge.php on line 7

となってしまいます。(>_<)

私、これautoloadのつまづきポイントだと思うんですけどね。

そりゃ、autoload.phpを読み込んでないんですもんね。なんの恩恵もないわけですよ。

<?php

require_once "vendor/autoload.php";

use MonkeyWorld\Monkey;
echo "ほげだよ<br>";


$monkey = new Monkey();
$monkey->scream();

上記のファイルでは

require_once "vendor/autoload.php";

が追加になっています。

ブラウザから実行すると、次のように表示され、うまくいきました!

次に例えば、Chimpanzeeクラスを追加します。

<?php
namespace MonkeyWorld;

class Chimpanzee
{

    public function scream(){
        echo "おいらチンパンジー。";
    }
}

これも、次のようにやるとちゃんと表示されます。

<?php

require_once "vendor/autoload.php";

use MonkeyWorld\Monkey;
use MonkeyWorld\Chimpanzee;

echo "ほげだよ<br>";


$monkey = new Monkey();
$monkey->scream();

$chimpan = new Chimpanzee();
$chimpan->scream();

ブラウザから実行すると、次のようになります。

よかった✧٩(ˊωˋ*)و✧

ちなみに、たとえばMonkeyクラスのファイルをmonkey.phpなどとしている場合があると思います。そうすると、Linuxだと読み込めなかったりするみたいです…。(自分が遭遇したわけではないので参考までですが)

https://stackoverflow.com/questions/41784405/need-to-dump-autoload-to-everytime-i-add-a-new-class

autoload使う場合は、クラス名と同じファイル名にする必要がありますが(例えばここでは、MonkeyクラスはMonkey.php)、大文字小文字も同じにした方がよいでしょう。

PHPUnitで使う話は?となりますが、長くなったので次回へ続きます!

次回はこれ↓

Scala リストに要素を追加する &その時の注意事項

ScalaのSeq型の一つである、List型の宣言は下記のようにします。

var ages = List(42, 61, 29, 64)

リストの末尾に要素を追加する場合には、次のようにします。

ages :+ 10

で、この時の注意なんですが、いかにも ages ってリストに 10 という要素が足されたようになってますが、そうではありません。

object Main extends App {
  var ages = List(42, 61, 29, 64)
  println(s"The oldest person is ${ages.max}")

  ages :+ 10
  println(s"The youngest person is ${ages.min}")

}

上記の出力は次のようになります。

The oldest person is 64
The youngest person is 29

ほげえええ

ages :+ 10 とやったときに、なんと新しいSeqが生まれていて、そこに追加されているのです。元のagesに追加されているわけではないのです。

下記のように変更してみます。

object Main extends App {
  var ages = List(42, 61, 29, 64)
  println(s"The oldest person is ${ages.max}")

  var new_ages = ages :+ 10
  println(s"The youngest person is ${new_ages.min}")

}

出力は次のようになります。

The oldest person is 64
The youngest person is 10

あー、よかった、できたできた。(๑>◡<๑)