Docker上のPHPをxdebugでVisualStudioCodeからデバッグする設定

# はじめに
最近、PHPを使うことになりWindows10でDockerに環境を構築したのでデバッグ環境を構築しようとしたのですが、思いの外はまってしまい、かなり手こずりました。
それでも解決できたので、同じような誰かの手助けになればと思い、以下に記します。

バージョン情報

| プロダクト | バージョン |
|:-:|:-:|
| PHP | 7.2.12 |
| xdebug | 2.6.1 |
| Docker Desktop Community | 2.0.0.0-win81(29211) |
| VSCode | 1.29.1 |
| Windows10 | Pro 1803 |

なお、各プロダクトの基本的な使い方などは他にも色々説明するサイトがあるので省略します。

## 説明内容
1. php.iniの設定
2. launch.json
3. ホストOS(Windows10)のディレクトリ構造

# php.iniの設定
php.iniはPHPの設定ファイルです。

```php.ini
[xdebug]
zend_extension=/usr/local/lib/php/extensions/no-debug-non-zts-20170718/xdebug.so
xdebug.remote_enable=1
xdebug.remote_autostart=1
xdebug.remote_port=9000
xdebug.remote_host="host.docker.internal"
xdebug.remote_connect_back = 0
```
<dl>
<dt>zend_extension</dt>
<dd>xdebugがインストールされた場所です。環境にあったパスを設定します。</dd>

<dt>xdebug.remote_enable</dt>
<dd>リモートデバッグの有無を指定します。1は有効です。</dd>

<dt>xdebug.remote_autostart</dt>
<dd>デバッグ用リモートセッション自動開始の有無を指定します。1は有効です。</dd>

<dt>xdebug.remote_port</dt>
<dd>リモートセッションの接続先ポートです。デフォルトは9000です。
変更しない場合は未指定でもOKです。</dd>
</dl>

以下の二項目は同じリモートデバッグでもwindows版dockerを利用する場合とそれ以外で設定が異なります。

<dl>
<dt>xdebug.remote_connect_back</dt>
<dd>
アクセスしたIPアドレスへの自動接続の有無を指定します。
殆どの説明では有効(1)となっていますが、Windowsでdockerを利用する場合は無効(0)を指定します。
ホスト側から直接接続する場合は有効で良いのですが、Windows版dockerはHyper-V上で動作するためdockerに直接接続するのはHyper-V上の仮想マシンとなり、ホストOSは直接接続しません。従って、無効とします。
なお、有効にした場合、remote_hostの設定は無視されます。
</dd>
<dt>xdebug.remote_host</dt>
<dd>セッションの接続先ホストです。デフォルトはlocalhostです。
dockerを利用する場合は、ホストOSのIPアドレスまたは"host.docker.internal"を指定します。
</dd>
</dl>

ちなみにwindows版docker以外は以下の通り。

```php.ini　Windows版docker以外のリモートデバッグ
xdebug.remote_connect_back = 1
xdebug.remote_host="localhost" ;指定しなくてもいいです
```

## xdebugのログについて
php.iniで

```php.ini
xdebug.remote_log=/tmp/xdebug.log
```

を設定すると指定したファイルにログを出力します。
動かない時にはこれが調査のきっかけになるのですが…あまり当てになりませんでした。

例えば、

```php.ini
xdebug.remote_connect_back = 0
```

のみでremote_host未指定だと以下のログが出力されます。

```xdebugログ
I: Connecting to configured address/port: localhost:9000.
I: Connected to client. :-)
```

ログ上は接続できていますが、ホストOSとは接続できておらずデバッグできません。

remoto_hostを正しく設定(例えば"host.docker.internal")した場合でもかなりの頻度で接続に失敗したと出力されます。

失敗例：

```xdebugログ
I: Connecting to configured address/port: host.docker.internal:9000.
W: Creating socket for 'host.docker.internal:9000', poll success, but error: Operation now in progress (29).
E: Could not connect to client. :-(
```
でも安心してください。ログが接続に失敗となっていてもデバッグはできます。
ログが接続エラーでも一度デバッグできるかを試しましょう。
理由は不明です。

成功した場合は以下の通りです。

```xdebugログ
I: Connecting to configured address/port: host.docker.internal:9000.
I: Connected to client. :-)
<以下、色々出ます>
```


# launch.json
launch.jsonはVScodeのデバッグ用構成ファイルです。
VSCodeの左側の虫のようなアイコンを押下するとデバッグ用のサイドバーが表示されます。
構成ファイルが無い場合は、サイドバー上段の「構成がありません」を「構成の追加」に変更し、右側に表示される環境の選択でPHPを選ぶと作成されます。
ある場合は、サイドバー上段にある歯車のようなアイコンを押下すると表示されます。
記述内容は以下の通りです。

```launch.json
    "configurations": [
        {
            "name": "Listen for XDebug",
            "type": "php",
            "request": "launch",
            "port": 9000,//php.iniで設定したxdebug用のport番号
            "pathMappings": {
                // {docker上のdocument root}:{ローカルのdocument root} 
                "/var/www/html":"/Path/to/your/local/root"
            }
        }
    ]
```

重要なのは__pathMappings__です。
docker側とローカル側のパスは同じにします。
これを異なる値にすると__悩み__ます。

異なる値にするとデバッグ時にブレイクポイントにヒットした際、VSCodeの右下に以下の内容のダイアログが表示されます(zzz.phpはブレイクポイントを設定したファイル)。

```
'zzz.php' を開くことができません: ファイルが見つかりません 
(file:///var/www/html/zzz.php)。
```
これはブレイクポイントを表示する際、docker上のdocument rootのパスを見て対象ファイルを表示しているからです。
このため、一応ブレイクポイントには止まりますが何も確認できません。

とは言え、実行環境と開発環境ではパスは変えたいと考えるのもよくあることだと思います。
少なくとも私はそうです。
それを実現するのが、次の「ホストOS(Windows10)のディレクトリ構造」です。

# ホストOS(Windows10)のディレクトリ構造
ここでは、実行環境と開発環境でパスを分けつつ、pathMappingsで同じパスを設定しても動く構成を説明します。

と言っても簡単な話です。
Unix/Linuxで言うところのシンボリックリンクに相当するWindowsのmklinkを使ってリンクを設定します。
※ショートカットは使えません。

リンクはCドライブ直下に作るので、管理者権限でコマンドプロンプトを起動します。
引数無しでmklinkコマンドを実行するとヘルプが出るのでその説明に沿ってシンボリックリンクを設定します。

```
C:\>mklink
シンボリック リンクを作成します。

MKLINK [[/D] | [/H] | [/J]] リンク ターゲット

        /D          ディレクトリのシンボリック リンクを作成します。既定では、
                    ファイルのシンボリック リンクが作成されます。
        /H          シンボリック リンクではなく、ハード リンクを作成します。
        /J          ディレクトリ ジャンクションを作成します。
        リンク      新しいシンボリック リンク名を指定します。
        ターゲット  新しいリンクが参照するパス (相対または絶対)
                    を指定します。
C:\>mklink /D d:\xxx\yyy\zzz c:\var\www\html
c:\var\www\html <<===>> d:\xxx\yyy\zzz のシンボリック リンクが作成されました
C:\>
```
上記は、d:\xxx\yyy\zzzにホスト側のdocument rootがありdocker側の/var/www/htmlにdocument rootがある場合のリンク設定です。

# おまけ

## pathMappingsのローカルパスの指定
この件を調べた際、海外サイトでpathMappingsのローカルパス設定に対する以下の禁止事項を見ました。

1. 絶対パスを指定する場合、ドライブレターは小文字を指定。大文字はNG
2. \${workspaceRoot}はNG。使うなら${workspaceFolder}。

デバッグできるようになってから上記の禁止事項を試しましたがどれも問題なく動きます。
この辺は気にしなくてよいと考えます。

# 参考サイト
php.iniの設定については以下の説明が大変参考になりました。

- [[PHP] Xdebug のリモートデバッグ、理解していますか？](https://qiita.com/castaneai/items/d5fdf577a348012ed8af)