Vagrant(+Docker) 上のPHPアプリをデバッグ実行する

## :worried: デバッガが反応しないんだけど..
仮想環境で開発をしていると、間に挟まるレイヤーのせいで悩まされることが度々あります。
例えば、ホスト側でIDEを実行し、ゲスト側で開発対象のアプリを起動しているような場合、デバッグ実行をするためには適切な設定が必要になります。
ゲストOSからは、デバッガがリモートホストで実行されているように見えるからです。
ここでは、ゲストOSでPHPを実行して、ホストOS上のデバッガでデバッグする方法を紹介します。

### 想定環境
Vagrant でゲストOSを起動し、ゲストOS上のDockerコンテナでPHPアプリを実行します (Dockerを使わず、ゲストOS上で直接PHPを実行する場合でも、下記の方法でデバッグ実行できるようになるはずです)。

Vagrant のプロバイダには、VirtualBox を使います。
VirtualBox 限定の設定を使うので、他のプロバイダでは他の方法が必要なります。ごめんなさい。

| | バージョン |
|---|---|
| macOS | 10.13.6 |
| Vagrant | 2.1.2|
| VirtualBox | 5.2.16 |
| PHP | 7.2.9 |
| Xdebug | 2.6.1 |

デバッガとしては、PHP Debug 拡張をインストールした Visual Studio Code (以下 VS Code) を使いました。

| | バージョン |
|---|---|
| VS Code | 1.26.1 |
|PHP Debug| 1.12.4 |

## Xdebugによるデバッグの仕組み
PHPアプリのデバッグ実行には、Xdebug というPHP拡張を利用します。
[Xdebug](https://xdebug.org/)

PHP ランタイムに組み込まれたXdebugとデバッガが通信することにより、リモートデバッグが実行されます。
PHPアプリとデバッガを両方ともホストOSで起動していれば、特別な設定をしなくても通信が成立して問題なくデバッグできます。
![img1.png](https://qiita-image-store.s3.amazonaws.com/0/135726/de69ed5f-9ff5-d56e-9219-f3c4067a8019.png)

PHPアプリをゲストOSで実行する場合、通信先のデバッガが localhost にないので、通信に失敗します。
![img2.png](https://qiita-image-store.s3.amazonaws.com/0/135726/6bc2ceb7-fc77-d2fc-66d7-e25d9929e675.png)

# 解決策
要するに、XdebugがホストOSのデバッガと通信できるよう、Xdebugを設定すればいいわけです。
方法は2つあります。

## :neutral_face: 方法1: xdebug.remote_connect_back を有効にする
Xdebug の設定ファイル (xdebug.ini) で、`xdebug.remote_connect_back` を有効にします。

```
xdebug.default_enable = 1
xdebug.remote_autostart = 1
xdebug.remote_connect_back = 1 # <-　ここ
xdebug.remote_enable = 1
xdebug.remote_port = 9000
```

これは、PHPにHTTPリクエストが届いたときに接続元のホストを取得して、Xdebugからそのホストに対して通信を行う設定です。
`$_SERVER['HTTP_X_FORWARDED_FOR']`と`$_SERVER['REMOTE_ADDR']`から接続元のホストを判定します。
PHPが受け取るHTTPリクエストの通信元は、VirtualBoxのNATアダプタによって変換されていますが、このIPアドレスに対して通信を行うことで、デバッグセッションが成立します。
![img3.png](https://qiita-image-store.s3.amazonaws.com/0/135726/9691c01f-310b-3918-48a1-7739f646bbfc.png)

ブラウザからのリクエストをトリガーにしてデバッグする場合はこの設定でOKです。
ただし、CLIなど別の方法で起動したPHPに対しては、この方法ではデバッグを実行することができません。例えば、[PHPUnit](https://phpunit.de/)でユニットテストをするケースなどが該当します。

ブラウザからのリクエストとコマンド実行の両方で、デバッグ実行をしたい場合は、この方法は適しません。
次に説明する方法2の設定を選んで下さい。

## :grinning: 方法2: xdebug.remote_host に、10.0.2.2 を指定する
Xdebug の設定ファイル (xdebug.ini) の `xdebug.remote_host` で、通信先のホストを明示的に指定します。

```
xdebug.default_enable = 1
xdebug.remote_autostart = 1
xdebug.remote_connect_back = 0 # 無効にする
xdebug.remote_enable = 1
xdebug.remote_host = "10.0.2.2" # <-　ここ
xdebug.remote_port = 9000
```

`10.0.2.2` はVirtualBox の NAT アダプターのゲートウェイアドレスです。
[Fine-tuning the VirtualBox NAT engine](https://www.virtualbox.org/manual/ch09.html#changenat)

![img4.png](https://qiita-image-store.s3.amazonaws.com/0/135726/0096a219-ab61-6c7d-27c3-64f4b1b69174.png)

この設定により、Xdebugは常にホスト上のデバッガと通信するようになります (ブラウザからのリクエストとコマンド実行の両方)。
VagrantでDockerを起動して、コンテナ内で PHP を実行する場合でも、この方法でデバッガと通信できます。

## デバッガの設定
`xdebug.ini`の設定で、Xdebugとデバッガは通信できるようになりました。
さらに、仮想環境とホスト側とファイルのパスをマッピングすることで、VS Codeで設定したブレークポイントが有効になります。
例えば、PHPサーバー側(ゲストOS)の `/var/www`を、VS Codeのワークスペースの`www`ディレクトリにマッピングするには、.vscode/launch.json を次のように設定します。

```json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Listen for XDebug",
            "type": "php",
            "request": "launch",
            "port": 9000,
            "log": true,
            "pathMappings": {
                "/var/www": "${workspaceRoot}/www"
            }
        },
        ...
    ]
}
```

## 補足
Xdebugとデバッガとの通信は、Xdebug(ゲストOS)からデバッガ (ホストOS) に対して送信されるので、ポートフォワーディングなどの設定は必要ありません。

* サンプル https://github.com/xanagi/vagrant-docker-php-vscode