EC-CUBE4の快適な開発環境をDocker Composeで整える for Windows

12/13(金) EC-CUBE4ハンズオンセミナーに参加したところ、[EC-CUBE AdventCalender 2019](https://qiita.com/advent-calendar/2019/ec-cube)の5日目が空いているということだったので、ちょっと書いてみました。
口調も内容も統一されていない気がしますが、勢いで書いたのでよろしくお願いします

--------------


EC-CUBE4は基本的にMac/Linux環境での開発が推奨されている。
[1日目  phpbrew で EC-CUBE の開発環境を構築する　@nanasess さん](https://qiita.com/nanasess/items/85f0f6a2feb47bcec7b5)

どうやら基盤となるSymfonyとWindowsのファイルシステムの相性が悪く遅いらしい。
また、Shellで記述されているスクリプトもあるため、Windows環境では使用できないコマンドが出てくる。

ところが世の中にはWindowsユーザーは多い。林檎は贅沢品である。
~~Ubuntuデスクトップ使えばいいかもしれない~~

そんな筆者を含むWindowsユーザーがDockerを用いてEC-CUBE4の開発環境を整えるための方法を紹介する。
一応この内容(前半部分)については[Pull Request](https://github.com/EC-CUBE/ec-cube/pull/4391)を出したところ採用いただけたため、[開発ドキュメント](https://doc4.ec-cube.net/quickstart_install)にも手順は記載されている。

Dockerを使用した構築手順自体は以前より確立されており、筆者はそれをDocker Composeで少々工夫を加えてまとめた形に過ぎないが、
本記事にて、Windows開発者向けに構築方法を紹介及び、構築の過程で得た知見、そして自分が使っている環境について共有したいと思う。



# Dockerを使用して環境を整える
まず先述のように、EC-CUBE4(Symfony)とWindows環境は色々と相性が良くないので、まず何かしらの手段でLinux環境を用意する必要がある。

- Dockerを使用する
- Vagrantを使用する
- WSL(Windows Subsystem for Linix)を使用する。

いくつか方法はあるが、ここではローカルと切り離して軽量にLinux環境を用意できるDockerを使用した方法を紹介する。


## 動作環境
- ホストOS: Windows 10 Pro
- Docker Desktop for Windows 
    - (WindowsのエディションがHomeの場合はDocker Toolboxで代用)
    - Docker 19.03.5
    - Docker Compose 1.24.1
    
Docker Desktop for Windowsのインストールについては公式や以下の記事を参照していただくのが良いと思う。
https://qiita.com/gahoh/items/7b21377b5c9e3ffddf4a

Homeエディションの場合はDocker Desktop for Windowsの動作に必要なHyper-Vが使用できないため、
VirtualBoxベースで動作するDocker Toolboxを使用することになる。

## Docker ComposeでEC-CUBE4動作環境を立ち上げる
基本的には開発ドキュメントの[docker-composeを使用してインストールする](https://doc4.ec-cube.net/quickstart_install#docker-compose%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%97%E3%81%A6%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E3%81%99%E3%82%8B)に記載している通りである。


```bash
# GitリポジトリからEC-CUBE4を取得
git clone https://github.com/EC-CUBE/ec-cube.git

cd ec-cube

# .envファイルのコピー
cp .env-dist .env

# ビルド&起動
docker-compose up -d 

# 初回はインストールスクリプトを実行
# DBスキーマ作成・初期データ投入・キャッシュ作成 等
docker-compose exec ec-cube bin/console eccube:install
```

以上でEC-CUBE4が動作する基本的な環境は完成である。
特にdocker-compose.ymlを編集していない場合は、8080ポートでEC-CUBEが表示できる

http://localhost:8080

Dockerは本当に便利。

## 構築のポイント
ここからは、DockerComposeに関するPull Requestを作成するにあたり、ポイントとなった箇所について記述する。

### 課題1. ローカルファイルを全てマウントすると激烈に重い


[開発ドキュメント Dockerを使用してインストールする](https://doc4.ec-cube.net/quickstart_install#docker%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%97%E3%81%A6%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E3%81%99%E3%82%8B)に、以下の通り記述があった

```bash

## ローカルディレクトリをマウントする場合
# var 以下をマウントすると強烈に遅くなるため、 src, html, app 以下のみをマウントする
docker run --name ec-cube -p "8080:80" -p "4430:443"  -v "$PWD/html:/var/www/html/html:cached" -v "$PWD/src:/var/www/html/src:cached" -v "$PWD/app:/var/www/html/app:cached" eccube4-php-apache
```

これは、ホスト上のフォルダとコンテナ上のフォルダをマウントした場合、当然ながら同期にコストがかかるためである。
(同様の問題はSymfonyやLaravel等でもあるらしい)

### 対応

ここで開発ドキュメントより、EC-CUBE4のフォルダ構成について確認してみる

```
app/
     設定ファイルやプラグイン、EC-CUBEをカスタマイズするPHPコードなど、アプリケーションごとに変更されるファイルを配置
bin/
     bin/consoleなど、開発に使用する実行ファイルを配置
html/
     リソースファイル(jsやcssや画像ファイル）を配置
src/
     EC-CUBE本体となり、phpファイルやTwigファイルを配置
tests/
     テストコードを配置
var/
     キャッシュやログファイルなど、実行時に生成されるファイルを配置
vendor/
     サードパーティの依存ライブラリを配置
```

基本的にカスタマイズではapp, html, src しか編集する機会はないはずなので、Dockerのイメージビルドの段階でソースコード全体をイメージ内にコピーし、その後は編集の可能性があるapp, html, srcのみローカルと同期するという形をとっていた。

ただしこれにも一つ落とし穴(ドキュメントにはしっかり記載されているが)があり、プロジェクトフォルダ直下のファイル(.env)等がホストと共有されないのである。`.env`が代表的だが、直下のファイルを編集したくなることもあるため、出来ればパフォーマンスに影響しない範囲で広く同期をしたい。

パフォーマンス低下の原因となるのは、主にキャッシュが作成されるvarフォルダ、それと`composer install`を行った際に露骨に遅くなるvendorの2つである。これら2フォルダについてのみ何とかしたい。

DockerのVolumeやマウントはSFTPやGitのignore指定のように、一部のフォルダだけ同期しないような設定をすることは基本的にはないらしいが、以下の記事にて一部を除外する方法があることが判明
[DockerでVolumeをマウントするとき一部を除外する方法](https://qiita.com/suin/items/e53eee56da23d476addc)

問題のサブフォルダをホストではなく別途Volumeにマウントすることで、同期コストの高い部分を避けて全体をマウントできるようになった。

```yml
# docker-compose.yml

version: "3"

volumes:
  ### ignore folder volume #####
  var:
    driver: local
  vendor:
    driver: local

services:
  ### ECCube4 ##################################
  ec-cube:

# === 略　=====================

    volumes:
      - ".:/var/www/html:cached"
      ### 同期対象からコストの重いフォルダを除外 #####################
      - "var:/var/www/html/var"
      - "vendor:/var/www/html/vendor"

# ※ この記事を書いているときにvendorがvenderにタイポしていることに気が付きました
```

varやvendorに関してはDocker Volumeを用いて同期よりも低コストで永続化される形になるため、これでパフォーマンス低下を避けてEC-CUBE4をDocker環境で構築出来るようになった。



## さらなる環境を目指して

### 課題2 XDebug使えない問題発生
ここからの話は執筆時点(2019/12/13)でリポジトリに取り込まれておらず、取り込んでいただくのとも違う内容の気がするため、必要であれば記事を開発環境の参考にしていただけたらと思う。

PHPの開発においてはXDebugを用いてデバッグを行うことは一般的であるが、ここで新たな問題が発生する。
課題1を対応したことでホストのvarやvendorはコンテナと同期されなくなるため、XDebugを使用した際にエラーが出るようになった。
XDebugはコンテナとホストのソースを対応させてエディタ上で表示させるため、ホストにもvarやvendorの中身が適切に存在しないとマッピングできずにエラーとなる。

一応コンテナのvar, vendorからホストにコピーすることで動作可能となったりはするが、varの中身などはキャッシュなので流動的(ここはあまり理解していません)だったりしそうなので、個人的にはこの方法はあまりやりたくない。

#### 対応 VSCode Remote Developmentを使う
Visual Studio CodeにはRemote Developmentという機能が実装されている。
Insider Previewでないと使えないという情報も多いが、2019年5月より安定版のVSCodeでも提供されるようになった。

[VS Code Remote Development](https://code.visualstudio.com/docs/remote/remote-overview)
[VSCodeのRemote Development機能が革命的な話。](https://crieit.net/posts/VSCode-Remote-Development)


これは大変に便利で、「リモートのフォルダを最初から開けて直接弄れる」とかだけではなく、「VSCodeの拡張機能自体もリモート上のリソースで動く」というところが素晴らしい。
つまり、Remote Development でコンテナに接続してXDebugを実行した場合、コンテナ上にvar, vendorフォルダさえあれば良い状態となるため、パフォーマンスを保ったままデバッグを行えるようになる。また、Windows上にPHPをインストールする必要もなくなる。強い。
 


##### 導入方法

VSCodeの拡張機能にて[Remote Development]をインストール
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242695/078aa89a-7467-05ef-c6bf-a951f06a98ec.png)

EC-CUBE4のプロジェクトフォルダを開き、コマンドパレットより[Remote-Containers: Open Configure]を実行
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242695/cdb419b4-ab0a-06b8-e112-5b0e7d4b02c5.png)

[From 'docker-compose.yml']
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242695/b8e71e17-d409-d9f8-92a4-dc97f31abe6f.png)

[ec-cube]
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242695/8edfbd3c-90fb-4d3f-9979-9086cfdc5819.png)

.devcontainerフォルダ以下にファイルが生成されるため、以下の通り編集する


【.devcontainer/docker-compose.yml】

```yml
# .devcontainer/docker-compose.yml
# 通常のdocker-compose.ymlに対する差分ファイルになります
# 今回は通常のdocker-compose.ymlにて必要なマウントは行っているため、ほぼ設定する内容がありません

#-------------------------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
#-------------------------------------------------------------------------------------------------------------

version: '3'
services:
  # Update this to the name of the service you want to work with in your docker-compose.yml file
  ec-cube:
    # You may want to add a non-root user to your Dockerfile. On Linux, this will prevent
    # new files getting created as root. See https://aka.ms/vscode-remote/containers/non-root-user
    # for the needed Dockerfile updates and then uncomment the next line.
    # user: vscode

    # Uncomment if you want to add a different Dockerfile in the .devcontainer folder
    # build:
    #   context: .
    #   dockerfile: Dockerfile

    # Uncomment if you want to expose any additional ports. The snippet below exposes port 3000.
    # ports:
    #   - 3000:3000
 
### コメントアウトする   
#    volumes:
#      # Update this to wherever you want VS Code to mount the folder of your project
#      - .:/workspace
###

      # Uncomment the next line to use Docker from inside the container. See https://aka.ms/vscode-remote/samples/docker-in-docker-compose for details.
      # - /var/run/docker.sock:/var/run/docker.sock 

    # Uncomment the next four lines if you will use a ptrace-based debugger like C++, Go, and Rust.
    # cap_add:
    #   - SYS_PTRACE
    # security_opt:
    #   - seccomp:unconfined

    # Overrides default command so things don't shut down after the process ends.
### コメントアウトする。
#    command: /bin/sh -c "while sleep 1000; do :; done"
 

```



【.devcontainer/devcontainer.json】

```js

// If you want to run as a non-root user in the container, see .devcontainer/docker-compose.yml.
{
	"name": "Existing Docker Compose (Extend)",

	// Update the 'dockerComposeFile' list if you have more compose files or use different names.
	// The .devcontainer/docker-compose.yml file contains any overrides you need/want to make.
	"dockerComposeFile": [
		"..\\docker-compose.yml",
		"docker-compose.yml"
	],

	// The 'service' property is the name of the service for the container that VS Code should
	// use. Update this value and .devcontainer/docker-compose.yml to the real service name.
	"service": "ec-cube",

	// The optional 'workspaceFolder' property is the path VS Code should open by default when
	// connected. This is typically a file mount in .devcontainer/docker-compose.yml
	// "workspaceFolder": "/workspace",
    
     "workspaceFolder": "/var/www/html",  // ドキュメントルート(ファイルがマウントされている場所)を指定する

	// Use 'settings' to set *default* container specific settings.json values on container create. 
	// You can edit these settings after create using File > Preferences > Settings > Remote.
	"settings": { 
		// This will ignore your local shell user setting for Linux since shells like zsh are typically 
		// not in base container images. You can also update this to an specific shell to ensure VS Code 
		// uses the right one for terminals and tasks. For example, /bin/bash (or /bin/ash for Alpine).
		"terminal.integrated.shell.linux": null
	},

	// Uncomment the next line if you want start specific services in your Docker Compose config.
	// "runServices": [],

	// Uncomment the next line if you want to keep your containers running after VS Code shuts down.
	// "shutdownAction": "none",

	// Uncomment the next line to run commands after the container is created - for example installing git.
	// "postCreateCommand": "apt-get update && apt-get install -y git",

	// Add the IDs of extensions you want installed when the container is created in the array below.
	"extensions": []    // 使いたい拡張機能があれば書いておくと自動インストールされる
}


```


この状態で[Remote-Containers: ReOpen in Container]を実行すると、コンテナ起動&接続が行われる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242695/ef586f8d-e1d7-c672-924d-e17ee6546029.png)

開かれたWindowは、コンテナ上を参照している
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242695/551c075a-3e68-cfcb-a093-c4050bb4da4c.png)

[Remote-Containers: Reopen Locally]でローカル環境に戻れる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242695/a805365f-34d6-9587-f555-85a5400e81a6.png)

#### DockerイメージにXDebugを追加

php.iniにXdebugの設定を追加。

```ini
# dockerbuild/php.ini

# 以下を追記
[xdebug]
zend_extension="xdebug.so"
xdebug.remote_host=127.0.0.1
xdebug.remote_autostart=1
xdebug.remote_enable=1
xdebug.remote_port=9000

```

```Dockerfile

# 最後あたりにXDebugインストール追記
# dockerbuild/Dockerfile


# ------------95行目? 適当な場所に追加 --------------
RUN pecl install xdebug \
  && docker-php-ext-enable xdebug

```

コンテナイメージを再度ビルド
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242695/d5ac8443-95c1-1575-0baf-e8dc572208b5.png)
これでコンテナの準備は完了


VSCodeにPHP Debug拡張をインストール(Install in Dev Container)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242695/d8f22e91-c4ef-520d-27dd-bd3305e2cb8e.png)

左メニューからデバッグを選択し、歯車にてPHPを追加
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242695/1fcc3922-e6cf-add0-d8c3-380d2d4cd298.png)


src/Eccube/Controller/TopController等にブレークポイントを設定し、
[Listen for XDebug]を実行する。
http://localhost:8080 にアクセスしたときにデバッガが反応すれば設定完了
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242695/2ce4a720-0c7d-1003-407e-5641d81b823c.png)






## たぶんこれが一番快適だと思います
何だか後半、VSCode Remote Developmentいいよ！！って話になってしまいました。
快適な開発環境をいい感じに共有するというのは一つのテーマだと思いますので、色々と共有できるといいなと思っています。

Docker Composeを用いて環境を作成し、VSCode Remote Developmentで接続して開発を行う、
今のところWindows環境でEC-CUBE開発をやるならこれがいいかなと個人的には思っています。

とはいえEC-CUBE4歴1ヶ月なので、正直色々見えない部分は多いです。
もっと良さげな環境があればぜひご教示いただきたいです