【Windows】Scoop+VSCode+PHPでローカルデバッグ環境を構築する

PHPのWebアプリ（Apache, PHP7.2, MySQL, Composer）をWindows10のローカル上でデバッグするための環境構築手順です。

既に同じような記事がありますが、Scoopを使ったものがなかったので、備忘の用途も含めて投稿してみました。

※今回、記事で使用しているPowerShellの関数を[Gist](https://gist.github.com/nimzo6689/8857babc86006bce60f3a638ae286d86)にまとめているので、逐一関数を宣言するのが面倒な場合はご使用ください。

# この記事でできること

- Visual Studio Codeを使って補完入力や使用箇所の参照・移動など
- Apache, PHP（記事を書いている段階ではPHP7.2）, MySQLでWebアプリの実行
- MySQL Workbenchを使ったデータベース操作
- Webアプリや単一スクリプトのデバッグ
- Composerを使ったライブラリの管理
- ScoopによるPHPのバージョン更新・切替

# 環境構築する上での方針

1. 環境構築手順をコード（or ps1スクリプト）で管理しやすいような方法を用いる。  
→ PowerShellやScoop（パッケージ管理ツール）などを使ってインストールしていきます。[^1]
[^1]: 実際には設定ファイルを作成する手順をコマンドで書いてないのですが、PowerShellで全てやろうとすると、UTF-8で書き込んだ場合にBOMが付いてしまいます。もちろん、それを付けないように作成する方法もあるのですが、それらを記事に記載するとややこしくなるので、今回は設定ファイルをコマンドで作成する手順を排除しました。例えば、職場で開発環境をスクリプトで一気に作成できるようにする場合は[PowerShellでBOM無しUTF8を簡単に扱う、デフォルト設定を簡単に変える方法](https://blog.shibata.tech/entry/2016/10/02/154329)を参考にすれば、問題なく実施できると思います。
1. ネカフェとかのPCでも構築できる。  
    → 管理者権限やPC再起動、コントロールパネル操作を不要にしています。
1. 全て無料のソフトを使用する。  
    → PHPStormだと有料になるので、Visual Studio Codeを使うことにしました。  

# Scoopで各アプリをインストール

以下、全てPowerShellコンソール上で実行してください。
※ PowerShellのv3以上が必要ですが、Windows7の初期状態だとv2のためアップグレードが必要となります。
　 ここでは割愛しますが、ググると[いろいろ](https://www.google.co.jp/m?q=powershell+%E3%82%A2%E3%83%83%E3%83%97%E3%82%B0%E3%83%AC%E3%83%BC%E3%83%89)出てきます。

```bash:PowerShell
# Scoop自身をインストール（Scoop内部でGitも使用しているため、そちらもインストールが必要）
Set-ExecutionPolicy RemoteSigned -scope CurrentUser
iex (new-object net.webclient).downloadstring('https://get.scoop.sh')
scoop install git

# bucket(パッケージリポジトリ的な奴)を追加
scoop bucket add extras
scoop bucket add versions

# Visual Studio Codeのインストール
scoop install vscode

# 各アプリをインストール
# phpとxdebugでバージョンが異なる可能性があるため、バージョン指定のScoop Appをインストールする。
# mysql-workbenchはGUIユーザ向けなので不要な人は割愛してください。
scoop install apache php72 php72-xdebug composer mysql-workbench mysql

## -- 6つのアプリを一気にインストールするので、ちょっと時間がかかります。--

# PHPを実行するのに必要なコンポーネントをインストール。
# 一度インストール完了すれば、Scoop上からは不要なので削除する。
scoop install vcredist2017; scoop uninstall vcredist2017

# インストールしたアプリの確認
# 7zipはScoop内部で使用しているので、勝手にインストールされます。
scoop list
#=> 7zip 18.06
#=> apache 2.4.38
#=> composer 1.8.3
#=> git 2.20.1.windows.1
#=> mysql 8.0.15
#=> mysql-workbench 8.0.15
#=> php72 7.2.14 [versions]
#=> php72-xdebug 2.6.1-7.2 [versions]
#=> vscode 1.31.0 [extras]
```

## 補足１：PHPをScoopでインストールするときはバージョン指定のものを推奨

Scoopからインストール可能なPHPにはいくつかのエディションがあり、検索すると以下のようになっています。

```bash:PowerShell
scoop search php
'main' bucket:
    php-nts (7.3.1)
    php (7.3.1)

'extras' bucket:
    appengine-go (1.9.70) --> includes 'php_cli.ps1'
    eclipse-php (2018-12)
    php-nts-xdebug (2.6.1-7.2)
    php-xdebug (2.6.1-7.2)

'versions' bucket:
    php54 (5.4.45)
    php55-xdebug (2.5.5-5.5)
    php55 (5.5.38)
    php56-xdebug (2.5.5-5.6)
    php56 (5.6.40)
    php70-xdebug (2.6.1-7.0)
    php70 (7.0.33)
    php71-xdebug (2.6.1-7.1)
    php71 (7.1.26)
    php72-xdebug (2.6.1-7.2)
    php72 (7.2.14)
    php73 (7.3.1)
```

ここでphpとphp-xdebugに注目してほしいのですが、
phpのバージョンが7.3.1になっているのに対し、php-xdebugの方は2.6.1-7.2とphp7.2用のXdebugしか利用できない状況となっています。
記事を書いているときにたまたまバージョンが違っているだけですが、この2つは常に同じバージョンになることが保証されていないため、
インストールする際は基本的にversions bucket内で提供されているPHPバージョン付きのAppを使用した方がトラブル回避できると思います。

なお、versions bucketだと現時点でノンスレッドセーフ版(NTS)が登録されていないため、
ノンスレッドセーフ版を使いたい場合は、[PHP bucket](https://github.com/nueko/scoop-php)からインストールすることが可能です。
こちらを使用したい場合は、`scoop bucket add php`とすることでローカルのScoopから`install`や`search`が実行できるようになります。
今回はこちらは使用せず、phpもphp-xdebugもversions bucketのものを使用していきます。

## 補足２：Composerのグローバル環境

通常、Composerでglobal環境にライブラリをダウンロードした場合、
`%APPDATA%/Composer`配下に`composer.json`や`vendor`などが作成されますが、
Scoop経由でインストールしたComposerの場合、`~\scoop\persist\composer\home\`配下に`%APPDATA%/Composer`同様のリソースが作成されます。

なお、`~\scoop\persist`配下のファイルはアプリのバージョンが更新（`scoop update composer`など）されても保持される設定ファイル等が配置されています。
Composerの場合、global環境にインストールしたことにより更新された`composer.json`や`vendor`ディレクトリはComposer自体が更新された後も保持され続けます。
逆に言えば、それ以外のデータはバージョン間での引継ぎはありません。

## 補足３：カスペルスキーのデフォルト設定だとComposerでrequireできない

Composerの[Issues](https://github.com/composer/composer/issues/6870)で既に報告されていますが、
カスペルスキー使用中のPCだと初期設定ではComposerが利用できないみたいです。実際に私も遭遇しました。

```bash:エラー内容
composer require phpunit/phpunit
#=> The "https://repo.packagist.org/packages.json" file could not be downloaded: SSL operation failed with code 1. #=> #=> OpenSSL Error messages:
#=> error:1416F086:SSL routines:tls_process_server_certificate:certificate verify failed
#=> Failed to enable crypto
#=> failed to open stream: operation failed
#=> https://repo.packagist.org could not be fully loaded, package information was loaded from the local cache and may be out of date
#=> ...
```

解決方法としては、設定->詳細->ネットワーク->暗号化された接続のスキャンで「暗号化された接続をスキャンしない」に変更すれば解決しました。[^2]
[^2]: 上記の方法だとセキュリティレベルが低下してしまうため、それが気になる方はエラーが出るたびにカスペルスキーのデスクトップ通知が来ると思うので、そちら経由で除外リストにその都度除外するホスト名を登録する方法でも問題ないかと思います。

# Apache関連の設定

Scoop経由でインストールしたApacheですと、`httpd.conf`が`~\scoop\persist\apache\conf\httpd.conf`に作成されるため、
以降こちらのファイルに対して必要な設定を入れていきます。
なお、こちらのファイルは既述の通りApache自体を更新してもバージョン間で共有されるファイルとなります。

## LoadModule、AddHandler、PHPIniDirの設定

ScoopのWikiで記載されている通りのやり方で、Scoop経由でインストールしたPHPの情報を`http.conf`に追記します。

```posh
iex (new-object net.webclient).downloadstring('https://gist.githubusercontent.com/nilkesede/c98a275b80b6d373131df82eaba96c63/raw/apache-php-init.ps1')
```

上記のコマンド[^3]によってPHPの設定情報が反映できるようになっています。
[^3]: 元々は[こちら](https://github.com/lukesampson/scoop/issues/1149)のIssuesで提供されたスクリプトになります。PHP8系になると、また同じ問題が起きそうです。

以下、反映される内容です。

```diff:~\scoop\persist\apache\conf\httpd.conf
+  # php setup
+	LoadModule php7_module 'C:/Users/xxxxx/scoop/apps/php72/current/php7apache2_4.dll'
+	AddHandler application/x-httpd-php .php
+	PHPIniDir "C:\Users\xxxxx\scoop\apps\php72\current"
```

### DocumentRootの変更

Apacheの`httpd.conf`でDocumentRootを `~\scoop\persist\apache\htdocs` から同階層下に作成する予定の`public`ディレクトリに変更します。
対した処理ではないので、普通に手で編集してもいいと思いますが、
自動化したいニーズが発生した時にすぐ自動化できるよう、あえてPowerShellで書き換えます。

```posh
# BOMなしのUTF-8で書き込むため、割と複雑になってます。
function replaceFileContentWithoutBOM($path, $regex, $replacement) {
  Get-Content -Encoding UTF8 $path `
    | % {$_ -replace $regex, $replacement}`
    | Out-String `
    | % { [Text.Encoding]::UTF8.GetBytes($_) } `
    | Set-Content -Path $path -Encoding Byte
}

replaceFileContentWithoutBOM ~\scoop\persist\apache\conf\httpd.conf '\${SRVROOT}/htdocs' '${SRVROOT}/htdocs/public'
```

反映される内容は以下の通りです。

```diff:~\scoop\persist\apache\conf\httpd.conf
-DocumentRoot "${SRVROOT}/htdocs"
-<Directory "${SRVROOT}/htdocs">
+DocumentRoot "${SRVROOT}/htdocs/public"
+<Directory "${SRVROOT}/htdocs/public">
```

## php.iniに設定事項を反映

Scoopでインストールした`php.ini`に設定を適用したい場合は、
`~\scoop\persist\php72\cli\conf.d`配下に${任意のファイル名}.iniを作成し、
その中に追加分の設定値を記入していきます。

### 基本的な設定
新しく`custom.ini`というファイルを作成し、そのファイルに設定値を記入します。
以下は`php.ini`の最小限の設定例として記載してます。

```ini:~\scoop\persist\php72\cli\conf.d\custom.ini
extension=curl
extension=mbstring
extension=openssl
extension=pdo_mysql
extension=pdo_sqlite

[Date]
date.timezone = Asia/Tokyo

[mbstring]
mbstring.language = Japanese
mbstring.encoding_translation = Off
mbstring.detect_order = auto
mbstring.substitute_character = none
```

### Xdebugに関する設定
Xdebugに関する設定値は`xdebug.ini`というファイルにまとめていきます。
既にいくつかの設定値が記入されているので、その後に設定値を追記します。

```ini:~\scoop\persist\php\cli\conf.d\xdebug.ini
zend_extension=C:\Users\xxxxx\scoop\apps\php72-xdebug\current\php_xdebug.dll
[xdebug]
xdebug.remote_enable=on
xdebug.remote_autostart=on
;xdebug.remote_connect_back=on
xdebug.remote_host=127.0.0.1
xdebug.remote_port=9000
```

`xdebug.remote_connect_back`がデフォルトだとonですが、それだと`xdebug.remote_host`の値が無視されるためコメントアウトにしています。
Xdebugの設定に関する情報は [[PHP] Xdebug のリモートデバッグ、理解していますか？ - Qiita](https://qiita.com/castaneai/items/d5fdf577a348012ed8af) が参考になりました。

# ユーザ環境変数「PATH」にPHPIniDirとComposerのbinを追加
Scoopのデフォルト設定だと、特別な方法で`php.exe`や`php-cgi.exe`、`phpdbg.exe`のみにPATHを通していますが、
Apacheと連携させるためには、php自体のディレクトリにPATHを通す必要があります。
また、ComposerでglobalにインストールしたライブラリにもPATHが通されるよう、合わせて設定を追加していきます。

今回は冒頭で書いた「環境構築する上での方針」に従うため、コンソール上でそのまま追加することにします。
（PowerShellの再起動とかは不要です）

```powershell:PowerShell
function env($name,$global,$val='__get') {
    $target = 'User'; if($global) {$target = 'Machine'}
    if($val -eq '__get') { [environment]::getEnvironmentVariable($name,$target) }
    else { [environment]::setEnvironmentVariable($name,$val,$target) }
}

function ensure_in_path($dir, $global) {
    $path = env 'PATH' $global
    $dir = (Resolve-Path $dir).Path
    if($path -notmatch [regex]::escape($dir)) {
        env 'PATH' $global "$dir;$path" # for future sessions...
        $env:PATH = "$dir;$env:PATH" # for this session
    }
}

ensure_in_path ~\scoop\apps\php72\current
ensure_in_path ~\scoop\persist\composer\home\vendor\bin
```

PowerShellでPATHを通す作業はいろいろ辛いです。
上記のコードはScoop内部で使用している関数をそのまま拝借しました。

# VSCodeでPHP開発に便利な拡張アプリをインストール

PHPの拡張機能で人気どころをインストールします。

```bash:PoserShell
# 補完、ドキュメント参照、リファクタリング、コードナビゲーション、など
code --install-extension felixfbecker.php-intellisense
# デバッグ機能（ブレークポイント、ステップ（オーバー｜イン｜アウト）、変数ウォッチ）
code --install-extension felixfbecker.php-debug
```

※ PHP Intellisenseを使う場合、以前はlanguage-serverを別途インストールする必要がありましたが、
　 現在はこちらの拡張機能内に同梱されているので、別途作業は不要となります。

# プロジェクト固有の設定

ここからはプロジェクト固有の設定について説明していきます。

まず、任意の場所に`sample_project `ディレクトリを作成し、
プロジェクト固有の設定を追加していきます。

```bash:PowerShell
New-Item -ItemType Directory -Name sample_project -Force
cd sample_project

# Visual Studio Code用に各設定ファイルの保存先を作成
New-Item -ItemType Directory -Name .vscode -Force
# ApacheのDocumentRootとなるディレクトリ
New-Item -ItemType Directory -Name public -Force

# Visual Studio Codeでプロジェクトフォルダを開く
code .
```

# PHP関連の設定

プロジェクト内で使用するPHP関連の設定です。

## Composer: 依存管理ツール

PHPプロジェクトで使用するライブラリを管理するために既にScoopでインストールしているComposerを使っていきます。

まず、`sample_project `ディレクトリ直下に以下の`composer.json`を作成します。

```json:sample_project\composer.json
{
  "name": "nimzo6689/sample_project",
  "description": "Sample Project - sample application for demo purpose.",
  "type": "project",
  "license": "Unlicense",
  "authors": [
    {
      "name": "Taro Nimzo",
      "email": "nimzo6689@example.com"
    }
  ],
  "require": {
    "twig/twig": "^2.0"
  },
  "require-dev": {}
}
```

次にターミナルで`composer install`を実行すれば、Composerが`composer.json`の設定値を元に
require、または、require-devに指定したライブラリをvendorディレクトリ配下にダウンロードし、
実際にインストールした内容を`composer.lock`に書き込んでくれます。

```bash:PowerShell(pwd=sample_project)
# 依存しているライブラリをインストール
composer install
#=> Loading composer repositories with package information
#=> Updating dependencies (including require-dev)
#=> Package operations: 3 installs, 0 updates, 0 removals
#=>   - Installing symfony/polyfill-ctype (v1.10.0): Loading from cache
#=>   - Installing symfony/polyfill-mbstring (v1.10.0): Loading from cache
#=>   - Installing twig/twig (v2.6.2): Downloading (100%)
#=> Writing lock file
#=> Generating autoload files
```

Composerのより詳細に関しては`composer --help`を見ればコマンドの使い方については大体わかるようになっています。
Composer自体の基礎的なところに関しては [PHP開発でComposerを使わないなんてありえない！基礎編 - Qiita](https://qiita.com/niisan-tokyo/items/8cccec88d45f38171c94) がわかりやすいですし、
[公式ドキュメント](https://getcomposer.org/doc/) もよく整備されていて読みやすいです。

# Visual Studio Code

Visual Studio Codeではプロジェクト（正確にはワークスペース）単位でいくつかのjsonファイルによってチーム間で設定を共有することができます。
よって、以降PHPに関連する設定の仕方を定義していきます。

## settings.json: エディタの基本的な設定

チーム全体でプロジェクトの設定が共有できるように、`.vscode\settings.json`内にプロジェクト固有の設定を反映します。
ここでいう設定には、Visual Studio Code自体の設定と拡張機能の設定、両方が入ります。

```json:sample_project\.vscode\settings.json
{
  "php.validate.executablePath": "C:\\Users\\xxxxx\\scoop\\apps\\php72\\current\\php.exe",
  "php.executablePath": "C:\\Users\\xxxxx\\scoop\\apps\\php72\\current\\php.exe",
  "php.suggest.basic": false,
  "files.watcherExclude": {
    "**/.git/objects/**": true,
    "**/.git/subtree-cache/**": true,
    "**/node_modules/**": true,
    "**/vendor/**": true
  }
}
```

上記のsettings.jsonを作成すると、Visual Studio Codeの再起動が求められるので、指示通り再起動します。

### 解説１：PHP Intellisense関連の設定

上記で記載したjsonファイルをそのままコピペすると実行可能なPHPが見つかりません、というエラーが出ると思います。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/114728/195a136a-9785-0096-cff2-b173b0bd841a.png)

PHP IntellisenseではInstallationの説明文にある通り、ローカルにインストールしたphpのパス（`php.*.executablePath`）を指定する必要があるので、こちらは各PC環境に応じて修整してください。

ちなみにですが、`\`がエスケープ済みのphpのパスを取得する場合、以下のコマンドで取得できます。

```bash:PowerShell
(scoop which php) -replace '\\', '\\'
#=> C:\\Users\\xxxxx\\scoop\\apps\\php\\current\\php.exe
```

また、PHP IntellisenseではVisual Studio Codeが持っている機能と競合をさけるために`php.suggest.basic`を無効化にする必要もあるので、こちらの値をfalseにしています。

詳しくは、[PHP IntelliSense - Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=felixfbecker.php-intellisense)のInstallationを参照してください。

### 解説２：パフォーマンス低下への対策

規模の大きいPHPプロジェクトの場合、標準設定のままだとCPUの使用量が上がり動作が遅くなりがちになるため、
いくつかその対策となる設定を加えることをお勧めします。

今回は、`files.watcherExclude`で`vendor`ディレクトリ配下のソースコードをファイル監視の対象から除外することにしました。
`files.exclude`でエクスプローラー上から非表示にする方法もありますが、パフォーマンスが深刻すぎない限り、
そこまでする必要なないかと個人的に思っています。
（むしろ、そこまで大きな規模の場合は、EclipseやNetBeansを使いたい。）

## tasks.json: エディタに登録する自動化タスクの設定

Visual Studio Codeで開発を進めていく中で、エディタから実行したい特定のタスクを設定します。

Apache連携で実行されるPHPコードをデバッグするためには、
DocumentRoot（デフォルトは`~\scoop\persist\apache\htdocs`配下）にソースコードをコピーする必要があります。
そこでデバッグ開始時に実行するそのコピー処理をtasks.jsonに設定します。

```json:sample_project\.vscode\tasks.json
{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "Deploy under Apache htdocs",
      "type": "process",
      "command": "powershell",
      "args": [
        "-ExecutionPolicy",
        "Unrestricted",
        "-NoProfile",
        "-Command",
        "Remove-Item \"$(scoop which httpd | split-path)\\..\\htdocs\\*\" -Recurse -Force;",
        "Copy-Item -Recurse * -Exclude *.vscode, composer.* \"$(scoop which httpd | split-path)\\..\\htdocs\" -Force"
      ],
      "group": {
        "kind": "build",
        "isDefault": true
      },
      "presentation": {
        "reveal": "always",
        "panel": "new",
        "echo": true
      }
    }
  ]
}
```

これで設定完了です。PowerShellで書けるので、かなり柔軟な対応ができるのがいいですね。
次にデバッグ実行の際にtasks.jsonで定義したタスクを実行できるようにします。

## launch.json: デバッグ実行の設定

F5でデバッグ開始する際に必要な設定をします。

```json:sample_project\.vscode\launch.json
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Listen for XDebug",
      "type": "php",
      "request": "launch",
      "preLaunchTask": "Deploy under Apache htdocs",
      "pathMappings": {
        "${env:USERPROFILE}\\scoop\\persist\\apache\\htdocs": "${workspaceFolder}"
      },
      "port": 9000
    },
    {
      "name": "Launch currently open script",
      "type": "php",
      "request": "launch",
      "program": "${file}",
      "cwd": "${fileDirname}",
      "port": 9000
    }
  ]
}
```

ちなみにですが、デバッグ実行したい場合は「フォルダーを開く（Ctrl+K, O）」でプロジェクトを開く必要があります。
デバッグしたい単体のファイルを開いただけでは実行できないため注意が必要です。
また、単一スクリプトのデバッグをする場合は、デバッグ対象となるファイルを開いている必要があります。

## extensions.json: 推奨する拡張機能の設定ファイル

推奨されている拡張機能を定義します。
一応、既にcodeコマンドでインストール済みですが、設定をgit管理する場合や、
何かの拍子に拡張機能をアンインストールした際にエディタのポップアップで気づくことができるので、
設定しておくと便利です。

```json:sample_project\.vscode\extensions.json
{
  "recommendations": [
    "felixfbecker.php-intellisense",
    "felixfbecker.php-debug"
  ]
}
```

## VS Codeでサンプルアプリを作成してみる

それでは、実際にサンプルのWebアプリを作成します。
今回インストールした全てのツールを動作確認用に使っていきたいため、
「MySQLから取得したデータをComposerでインストールしたTwigを用いてHTMLを生成し、
そのページをApache経由で表示する」だけのコードを書き、
かつ、デバッグも問題なくできるか試していきます。

サンプルコードは[PHP Select Data From MySQL - W3Schools](https://www.w3schools.com/php/php_mysql_select.asp)のソースコートを参考に、簡単なものにしました。

### MySQL

PHPからMySQLにアクセスするため、my.iniの編集とテーブル、データの作成を先にしておきます。

```diff:~\scoop\apps\mysql\current\my.ini
[mysqld]
datadir=C:/Users/xxxxx/scoop/persist/mysql/data
+character-set-server=utf8
[client]
user=root
+default-character-set=utf8
```

次にMySQLサーバーを起動します。
単純に実行してしまうと余計なターミナルウィンドウが増えてしまうので、バッググラウンド実行で起動しています。

```posh:PowerShell
# mysqldをバックグランドで実行。（ターミナルを終了すれば自動で終了するため、停止方法については割愛します）
Start-Job {mysqld}

#=> Id     Name            PSJobTypeName   State         #=> HasMoreData     Location             Command
#=> --     ----            -------------   -----         -----------     --------             -------
#=> 1      Job1            BackgroundJob   Running       #=> True            localhost            mysqld

mysql -u root
#=> Welcome to the MySQL monitor.  Commands end with ; or \g.
#=> ...
```

データベース、テーブル、サンプルデータを作成していきます。

```sql:PowerShell(MySQL)
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'P@ssw0rd';

CREATE DATABASE sample_project;

USE sample_project;

CREATE TABLE guests (
  id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY, 
  firstname VARCHAR(30) NOT NULL,
  lastname VARCHAR(30) NOT NULL,
  email VARCHAR(50),
  created_at TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO guests (firstname, lastname, email, created_at) VALUES ('太郎', '田中', 'tanaka@example.com', CURRENT_TIMESTAMP);
INSERT INTO guests (firstname, lastname, email, created_at) VALUES ('花子', '山田', 'yamada@example.com', CURRENT_TIMESTAMP);
INSERT INTO guests (firstname, lastname, email, created_at) VALUES ('稲造', '新渡戸', 'nitobe@example.com', CURRENT_TIMESTAMP);

SELECT * FROM guests;
--#=> +----+-----------+----------+-------------------+---------------------+
--#=> | id | firstname | lastname | email             | created_at          |
--#=> +----+-----------+----------+-------------------+---------------------+
--#=> |  1 | 太郎      | 田中      | john@example.com  | 2019-02-08 12:12:52 |
--#=> |  2 | 花子      | 山田      | mary@example.com  | 2019-02-08 12:12:52 |
--#=> |  3 | 稲造      | 新渡戸    | julie@example.com | 2019-02-08 12:12:52 |
--#=> +----+-----------+----------+-------------------+---------------------+

-- デフォルトはオートコミットなので、本当は不要ですがあえて書いておきます。
COMMIT;

-- MySQLを終了するときはexit（または、quitでも可）
exit
```

上記の手順通りに実行した場合、次回以降MySQLにログインする際は以下のようなコマンドになります。

```bash:PowerShell
mysql -u root -pP@ssw0rd sample_project
```

#### 補足１：MySQL Workbenchでの利用方法

mysqlコマンドではなくGUIで操作したい場合はMySQL Workbenchでも可能です。
特に詳しくは解説しませんが、以下その手順です。

1. 「スタートメニュー」→「Scoop Apps」→「MySQL Workbench」で起動
2. MySQL Connections横の「+」ボタンをクリック
3. 接続情報を以下のように設定し、「Test Connection」で成功すれば、「OK」をクリック
![image.png](https://qiita-image-store.s3.amazonaws.com/0/114728/62169537-0dc3-ff94-67cc-5857e98097dd.png)
4. 「sample_project」をクリックすると接続でき、以下のようにクエリなどを実行できるようになります。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/114728/ca312c6f-1754-b7ee-9dbe-5fd850c51b9a.png)

### PHPコード

先ほど作成したテーブルのデータを表形式でそのまま出力するだけの処理を実装します。

はじめにTwigを自動起動させるためにプロジェクト直下に`bootstrap.php`を作成します。

```php:bootstrap.php
<?php

require_once __DIR__ . '/vendor/autoload.php';

$loader = new Twig_Loader_Filesystem(__DIR__ . '\templates');
$twig = new Twig_Environment($loader);
```

続いて、ブラウザからアクセス先となる`index.php`を作成します。

```php:public\index.php
<?php

require_once __DIR__ . '/../bootstrap.php';

try {
  $pdo = new PDO('mysql:dbname=sample_project;host=localhost;charset=utf8mb4', 'root', 'P@ssw0rd', [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
  ]);
  $rows = $pdo->query("SELECT id, firstname, lastname FROM guests")->fetchAll();
} catch (PDOException $e) {
  header('Content-Type: text/plain; charset=UTF-8', true, 500);
  exit($e->getMessage());
}
header('Content-Type: text/html; charset=utf-8');

echo $twig->render('index.html', ['rows' => $rows]);
```

最後にTwigのテンプレートとなる`index.html`を作成します。

```twig:templates\index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Sample Project</title>
  </head>
  <body>
    <table border="1" style="width: 80%;">
      <tr>
        <th>Id</th>
        <th>Firstname</th>
        <th>Lastname</th>
      </tr>
      {% for row in rows %}
      <tr>
        <td>{{ row.id }}</td>
        <td>{{ row.firstname }}</td>
        <td>{{ row.lastname }}</td>
      </tr>
      {% endfor %}
    </table>
  </body>
</html>
```

用意するファイルは以上です。
それでは、Apacheを起動します。こちらもバッググラウンド実行です。

```posh:PowerShell
Start-Job -Name httpd {httpd}
```

## デバッグ実行

Visual Studio Codeの左のナビゲーションペインに表示されている「デバッグ」をクリックし、
デバッグの構成が`"Listen for XDebug"`になっていることを確認します。

なっていることが確認できたら、デバッグ実行ボタン（または、F5）を押下します。

http://localhost/index.php にアクセスすると、以下のページが表示できます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/114728/4afbc12d-3d23-3b14-374a-b43e67f3c5be.png)

試しにブレークポイントをindex.phpの17行目に設定します。
ブレークポイントを貼る際は、行ナンバーが表示されているところをクリック、または、F9で可能です。

ブレークポイントの箇所でちゃんと停止でき、変数も確認できます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/114728/10d1e771-9e3d-5a84-7ed4-106528c7be29.png)

# PHPのバージョン管理

今回PHP7.2で構築しましたが、将来的にPHP7.3に更新するかもしれませんし、
違うプロジェクトで古いバージョンを使っているなどあれば、適宜切替したい状況もあるかと思うので、
その際に使えるコマンドを残しておきます。

記事を書いている段階では、まだPHP7.3のXdebugがリリースされていないため、
PHP7.1にバージョンを下げる手順にしました。
※アップデートもまたその逆も手順自体は変わりません。

## バージョンの更新方法（PHP7.2→PHP7.1）

全部コマンド化しましたが、結構長くなったのでps1スクリプトにした方がいいかもしれません。

```posh:PowerShell
# 関数を宣言
# env, ensure_in_pathも定義済みであることが前提です。
# もし、コンソールを一度終了していれば、再度定義してください。
function strip_path($orig_path, $dir) {
    if($null -eq $orig_path) { $orig_path = '' }
    $stripped = [string]::join(';', @( $orig_path.split(';') | Where-Object { $_ -and $_ -ne $dir } ))
    return ($stripped -ne $orig_path), $stripped
}

function remove_from_path($dir,$global) {
    $dir = (Resolve-Path $dir).Path
    # future sessions
    $was_in_path, $newpath = strip_path (env 'path' $global) $dir
    if($was_in_path) {
        env 'path' $global $newpath
    }

    # current session
    $was_in_path, $newpath = strip_path $env:PATH $dir
    if($was_in_path) { $env:PATH = $newpath }
}

# PHP7.1をインストール
scoop install php71 php71-xdebug

# ユーザー環境変数に登録しているPHPIniDirをPHP7.2から7.1に切り替える。
remove_from_path ~\scoop\apps\php72\current
ensure_in_path ~\scoop\apps\php71\current
scoop which php
#=> C:\Users\xxxxx\scoop\apps\php71\current\php.exe

# custom.iniを移行
cp ~\scoop\persist\php72\cli\conf.d\custom.ini ~\scoop\persist\php71\cli\conf.d\custom.ini
# xdebug.iniを移行
cp ~\scoop\persist\php72\cli\conf.d\xdebug.ini ~\scoop\persist\php71\cli\conf.d\xdebug.ini
replaceFileContentWithoutBOM ~\scoop\persist\php71\cli\conf.d\xdebug.ini 'php72' 'php71'

# httpd.confに指定しているphpのパスをphp72からphp71に変更する
replaceFileContentWithoutBOM ~\scoop\persist\apache\conf\httpd.conf 'php72' 'php71'

# 動作確認
php --ini
#=> PHP Warning:  PHP Startup: Unable to load dynamic library 'ext\curl' - 指定されたモジュールが見つかりません。
#=>  in Unknown on line 0
#=> PHP Warning:  PHP Startup: Unable to load dynamic library 'ext\mbstring' - 指定されたモジュールが見つかりません。
#=>  in Unknown on line 0
#=> PHP Warning:  PHP Startup: Unable to load dynamic library 'ext\openssl' - 指定されたモジュールが見つかりません。
#=>  in Unknown on line 0
#=> PHP Warning:  PHP Startup: Unable to load dynamic library 'ext\pdo_mysql' - 指定されたモジュールが見つかりません。
#=>  in Unknown on line 0
#=> PHP Warning:  PHP Startup: Unable to load dynamic library 'ext\pdo_sqlite' - 指定されたモジュールが見つかりません。
#=>  in Unknown on line 0
#=> Configuration File (php.ini) Path: C:\WINDOWS
#=> Loaded Configuration File:         (none)
#=> Scan for additional .ini files in: #=> C:\Users\xxxxx\scoop\apps\php71\current\cli;C:\Users\xxxxx\scoop\apps\php71\current\cli\conf.d;
#=> Additional .ini files parsed:      C:\Users\xxxxx\scoop\apps\php71\current\cli\php.ini,
#=> C:\Users\xxxxx\scoop\apps\php71\current\cli\conf.d\custom.ini,
#=> C:\Users\xxxxx\scoop\apps\php71\current\cli\conf.d\xdebug.ini
```

通常は上記の手順で問題ないですが、PHP7.2からphp.iniで指定するextension名の命名規則が一気に変わったため、
上記のようなエラーが出てしまいました。
こういったケースでは、`~\scoop\persist\php71\cli\php.ini`を開き、正しいextension名を指定しなおしてください。

```ini:~\scoop\persist\php71\cli\conf.d\custom.ini
extension=php_curl.dll
extension=php_mbstring.dll
extension=php_openssl.dll
extension=php_pdo_mysql.dll
extension=php_pdo_sqlite.dll
; 以下、省略
```

```posh:PowerShell
php --ini
#=> Configuration File (php.ini) Path: C:\WINDOWS
#=> Loaded Configuration File:         (none)
#=> Scan for additional .ini files in: #=> C:\Users\xxxxx\scoop\apps\php71\current\cli;C:\Users\xxxxx\scoop\apps\php71\current\cli\conf.d;
#=> Additional .ini files parsed:      C:\Users\xxxxx\scoop\apps\php71\current\cli\php.ini,
#=> C:\Users\xxxxx\scoop\apps\php71\current\cli\conf.d\custom.ini,
#=> C:\Users\xxxxx\scoop\apps\php71\current\cli\conf.d\xdebug.ini
```

## バージョンの切替方法

更新時とは異なり、設定ファイルに関しては`php.ini`関連は不要で`httpd.conf`の書き換えのみとなります。

```posh:PowerShell
# php72-xdebugはreset対象となるファイル（実行ファイルや環境変数など）がないため、指定不要です。
scoop reset php72
remove_from_path ~\scoop\apps\php71\current
ensure_in_path ~\scoop\apps\php72\current

# httpd.confに指定しているphpのパスをphp71からphp72に変更する
replaceFileContentWithoutBOM ~\scoop\persist\apache\conf\httpd.conf 'php71' 'php72'

# 動作確認
php --ini
#=> Configuration File (php.ini) Path: C:\WINDOWS
#=> Loaded Configuration File:         (none)
#=> Scan for additional .ini files in: #=> C:\Users\xxxxx\scoop\apps\php72\current\cli;C:\Users\xxxxx\scoop\apps\php72\current\cli\conf.d;
#=> Additional .ini files parsed:      C:\Users\xxxxx\scoop\apps\php72\current\cli\php.ini,
#=> C:\Users\xxxxx\scoop\apps\php72\current\cli\conf.d\custom.ini,
#=> C:\Users\xxxxx\scoop\apps\php72\current\cli\conf.d\xdebug.ini
```

# 気になったところ

開発環境構築という目的には支障あまりないですが、作業中に遭遇して解決できなかった挙動についてメモします。

- PHPのビルドインWebサーバーだけでデバッグができない。  
　→ NetBeansだとできるのですが、VSCodeだと上手くできないようです。
- httpdをWindowsのサービスとして起動した場合、実行ができない。  
　→ Apache自体の起動はできているのですが、PHPのWebアプリは動作できていないようでした。  
　　 PHPディレクトリをシステム環境変数のPATHに追加しても挙動変わらず。  
　　 ただ、開発環境構築という目的でサービス起動はあまり用途がないと思うので、詳細は追ってません。
- 例えば、`array_key_exists`や`file_exists`など、PHP標準の関数の実装を見たい場合、NetBeans等のIDEでは  
  宣言場所（`php.php`）に移動できるようソースアタッチができるのですが、Visual Studio Codeだとそれが難しいようです。

# 参考

[【PHP】XAMPP + VSCodeでPHPの開発環境を作った(Windows) - ぺやろぐ](https://www.peyarogu.com/entry/php_xampp_vscode "【PHP】XAMPP + VSCodeでPHPの開発環境を作った(Windows)")
[Custom PHP configuration · lukesampson/scoop Wiki · GitHub](https://github.com/lukesampson/scoop/wiki/Custom-PHP-configuration)
[Apache with PHP · lukesampson/scoop Wiki · GitHub](https://github.com/lukesampson/scoop/wiki/Apache-with-PHP)
[[PHP] Xdebug のリモートデバッグ、理解していますか？ - Qiita](https://qiita.com/castaneai/items/d5fdf577a348012ed8af)
[PHP開発でComposerを使わないなんてありえない！基礎編 - Qiita](https://qiita.com/niisan-tokyo/items/8cccec88d45f38171c94)
[MySQL8.0 認証方式を変更する(Laravel5) - Qiita](https://qiita.com/ucan-lab/items/3ae911b7e13287a5b917)
[PHP Select Data From MySQL - W3Schools](https://www.w3schools.com/php/php_mysql_select.asp)
[PHPでデータベースに接続するときのまとめ - Qiita](https://qiita.com/mpyw/items/b00b72c5c95aac573b71)
[PHPでWebアプリ開発！人気テンプレートエンジン「Twig」を使ってみよう](https://www.webprofessional.jp/twig-popular-stand-alone-php-template-engine/)