PhpStorm+DockerでCakePHPの開発環境を作ってみた

この土日にあーでもないこーでもないって楽しんでたんだけど、たぶんできたと思う。結構色々あったな。というか、今日の日記長い。

## 今日のコード

github.com

## 発端:Dockerでできそうじゃない?

CakePHPを勉強しようと思って、PHPBrewでPHPのバージョンを切り替えられる環境を作って

PHPに入門してみる。phpbrewとIDEA+PHPプラグインでCakePHP3の準備。 - Mitsuyuki.Shiiba

IDEとしてIntelliJ IDEA(+各種PHPプラグイン)を触って

JetBrainsのPhpStormワークショップをひととおりざっくりやってみてメモ - Mitsuyuki.Shiiba

その後にごにょごにょ触ったり色んな記事を見たりしてたら「あれ?Dockerの中のPHPを使えばホスト側にPHPなくても大丈夫そう?」と思ってしまった。

なので、PHPとPHPBrewをアンインストールして、Dockerで環境を構築してみることにした。

## 注意

1) 分かりやすいようにと思って「PhpStorm」って書いてるけど、実際はPhpStormじゃなくてIDEAにPHP系のプラグインを追加してやってる。設定画面やメニューの場所などがちょこちょこ違うけど、機能はだいたい同じなんじゃないかと思う。

IntelliJ IDEA 2018.2.3 EAP (Ultimate Edition)
Build #IU-182.4323.6, built on August 22, 2018

2) OSはUbuntuの18.04

3) PHP自体を勉強し始めたばっかりなので変なところとかあるかも

## できたこと

ホストマシンにはPHPをインストールせずに、PhpStormでApache+PHPのDockerイメージを使って、

## 感想

  • 気に入った
    • Dockerなので一度準備が整ってしまえばお手軽で良い
  • けど、最初に考えてたよりは複雑になっちゃった
    • DockerやPhpStormの仕様や、まだサポートされてない機能とかの影響で素直に実現できない部分があって、それを回避する必要があったし、実現できない機能もあった
    • もう何年かしたら色々サポートされててもっと楽なのかもしれない
    • Vagrantは今回は使わない方向でがんばったけど、使う方がシンプルになるかもなぁという気もした
  • それと、速度が心配
    • プロジェクトルートをそのままマウントして使うので、色々ファイルが増えても問題ないスピードで動くのかどうかがが心配

## shin1x1さんの記事

shin1x1さんの記事を何度も読んで、実際にやってみて、最初は「う、動くぞ!(←分かってない」って感じだったんだけど、何度か触ってるうちに仕組みがだんだん分かってきた。

blog.shin1x1.com

## PhpStormワークショップのDockerブランチ

それから、前回はPhpStormワークショップのVagrantバージョンをやったのだけど、Dockerバージョンのブランチがあるのでそっちも触ってみた。shin1x1さんのはDocker Composeだったけど、こっちはDockerなんだなーって思いながら。

https://github.com/JetBrains/phpstorm-workshop/tree/docker#getting-started

## やりたいこと

やりたいのはこういうこと

### Remote Interpreter用のイメージ

f:id:bufferings:20180826202902p:plain

  • PhpStormからPHPのツール(composerとかphpunitとか)を実行するためのphp-cliイメージ
  • プロジェクトのルートディレクトリーがマウントされてるので
  • ツールの実行結果はローカルディレクトリにも反映される(vendorとか)
  • それとphp.iniもマウントされてる

で、それとは別に

### アプリケーション実行用のイメージ

f:id:bufferings:20180826203120p:plain

  • アプリケーションの実行とリモートデバッグをするためのphp-webイメージがあって
  • これは常に起動しておく
  • こっちにもプロジェクトのルートディレクトリーとphp.iniがマウントされてる

という感じ。

### 2つイメージをそれぞれ管理するのも面倒なので

1つのDockerイメージで両方をカバーしてしまおうと思う。こんな感じ。

f:id:bufferings:20180826203247p:plain

それと、Volumeマウントとかの設定をコードとして書いておきたいのでdocker-composeを使う。

## Docker関係の準備

### Dockerfile

色々あってDockerfileはこうなった。順番に説明してく。

FROM php:7.2.9-apache

↑PHP7 + Apacheのイメージを使用。

# Xdebug
RUN pecl install xdebug \
 && docker-php-ext-enable xdebug

Xdebugを入れて有効化。

# For CakePHP
RUN apt-get update && apt-get install -y \
    git \
    libicu-dev \
    libzip-dev \
    zip \
 && rm -rf /var/lib/apt/lists/* \
 && a2enmod rewrite \
 && NPROC=$(grep -c ^processor /proc/cpuinfo 2>/dev/null || 1) \
 && docker-php-ext-configure zip --with-libzip \
 && docker-php-ext-install -j${NPROC} zip \
 && docker-php-ext-install -j${NPROC} intl \
 && docker-php-ext-install -j${NPROC} pdo_mysql

CakePHP動かすのにこんな感じかなぁ?と思いながら書いた。実際に動かしてみたら他にも必要なのがあるかもしれない。

# Composer
ENV COMPOSER_HOME /tmp
COPY --from=composer:1.7.2 /usr/bin/composer /usr/bin/composer

↑Copmoserは、https://github.com/docker-library/docs/tree/master/composer#suggestionsのnoteのところを参考にした。マルチステージビルドの外部からとってくるやつなんやね。

# Change DocumentRoot
WORKDIR /app
ENV APACHE_DOCUMENT_ROOT /app/webroot
RUN sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf \
 && sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf

↑DocumentRootの変更は、https://github.com/docker-library/docs/blob/master/php/README.md#changing-documentrootを参考にした。CakePHPのプロジェクトをこの後生成するのでwebrootディレクトリにした。

# Change uid & gid of www-data
RUN usermod -o -u 1000 www-data && groupmod -o -g 1000 www-data

昨日の記事でも書いたけど、Linuxの場合はVolumeマウントしたときにuid:gidをホスト側のユーザーと揃えておかないと面倒なので1000:1000固定にしておいた。本当は実行時にuidとgidを外から設定するほうがキレイだけど自分用だし良いかなと思って。

# To switch user on runtime
RUN apt-get update && apt-get -y install \
    gosu \
 && rm -rf /var/lib/apt/lists/*
COPY entrypoint.sh /usr/local/bin/docker-php-entrypoint

php-webとしてApacheを動かすときは最初のプロセスをrootで起動する必要があるのだけど、php-cliでComposerとかのコマンドラインツールを使いたいときはwww-dataユーザーを使いたいので、gosuを入れてエントリーポイントのファイルを上書きしといた。

### entrypoint.sh

そのエントリーポイント。

#!/bin/sh
set -e

# first arg is `-f` or `--some-option`
if [ "${1#-}" != "$1" ]; then
  set -- apache2-foreground "$@"
fi

if [ "$1" = "apache2-foreground" ]; then
  exec "$@"
else
  # use www-data for cli
  echo "gosu user to www-data"
  exec gosu www-data "$@"
fi

元々のイメージのエントリーポイント https://github.com/docker-library/php/blob/e92dfe4847c9fe74ea309e66f9d3ef0217f8525d/7.2/stretch/apache/docker-php-entrypoint にgosuの処理を加えた。

### docker-compose.yml

次はdocker-compose。

docker-compose.ymlはプロジェクトルートに置いて、Dockerfileとphp.iniはdockerディレクトリーに置いた。

❯ tree
.
├── docker
│   ├── Dockerfile
│   ├── entrypoint.sh
│   └── php.ini
└── docker-compose.yml

1 directory, 4 files

docker-compose.ymlはこんな感じ。

version: '3'
services:
  php-cli:
    build: ./docker
    command: php -v
    volumes:
      - .:/app
      - ./docker/php.ini:/usr/local/etc/php/php.ini
  php-web:
    build: ./docker
    volumes:
      - .:/app
      - ./docker/php.ini:/usr/local/etc/php/php.ini
    ports:
      - "8000:80"

CLIの方はビルドをしておきたいだけで起動しておく必要はないので適当にコマンドを指定しておいた。

### php.ini

んで、php.iniはまだ勉強してなくて全然分からないので、shin1x1さんのをコピーさせていただきました。で、2ヶ所だけ変更。

1) xdebug.remote_autostartOffにした。理由はリモートデバッグのところで書く。

2) xdebug.remote_host172.17.0.1(コンテナから見たホスト側のIPアドレス)にしておいた。今だとMacdocker.for.mac.localhostからhost.docker.internalに変わってるから、Macの場合はそっちにしといたらいいかな。Linux版にもそういう機能入れといてくれたらいいのにね。

; timezone
date.timezone = Asia/Tokyo

; error reporing
log_errors = On
error_log = /dev/stderr

; xdebug
xdebug.remote_enable = On
xdebug.remote_autostart = Off
xdebug.remote_connect_back = Off
xdebug.remote_host = 172.17.0.1
;xdebug.remote_port=9000
;xdebug.idekey=phpstorm

### ビルド

以上でファイルが揃ったのでビルド。いったんupして成功することを確認してからCTRL+Cで停止しておく。

❯ docker-compose build
❯ docker-compose up

CTRL + C

ちなみに、docker-composeを使うときは親ディレクトリの名前がイメージIDに使われる。今回僕はプロジェクトのディレクトリ名をphpstorm-docker-cakephpにしたので、イメージ名はこうなった(ちょっと長かったなって後で思ったけど)。

❯ docker-compose images
           Container                       Repository              Tag       Image Id      Size
------------------------------------------------------------------------------------------------
phpstormdockercakephp_php-cli_1   phpstormdockercakephp_php-cli   latest   92565eef7208   490 MB
phpstormdockercakephp_php-web_1   phpstormdockercakephp_php-cli   latest   92565eef7208   490 MB

ビルドが終わったので、こんな感じでコマンドが使える。

❯ docker-compose run --rm php-cli whoami
gosu user to www-data
www-data

じゃ、CakePHPプロジェクトを生成しよう。

## ComposerでCakePHPのプロジェクトを生成

### プロジェクトを作成

Composerが使えるようになったのでCakePHPのプロジェクトを生成する。

❯ docker-compose run --rm php-cli composer \
create-project --prefer-dist cakephp/app

しばらく待って、最後に「フォルダーのパーミッションセットする?」って聞かれたのでYにしといた。

### 生成したプロジェクトのファイルを移動

appってディレクトリに生成されてるので、その中身をカレントに移動することにする。

❯ mv app/* .
❯ mv app/.* .
❯ rmdir app

❯ tree -L 1
.
├── bin
├── composer.json
├── composer.lock
├── config
├── docker
├── docker-compose.yml
├── index.php
├── logs
├── phpunit.xml.dist
├── plugins
├── README.md
├── src
├── tests
├── tmp
├── vendor
└── webroot

10 directories, 6 files

### モジュールの追加

んで、PHPCS, PHPMD, PHPUnitを入れておく。PHPUnitCakePHP指定のバージョンで。

❯ docker-compose run --rm php-cli composer require --dev \
"squizlabs/php_codesniffer" \
"phpmd/phpmd" \
"phpunit/phpunit:^5.7|^6.0"

### 起動して見とく

これでアプリは動く状態なので、docker-composeでphp-webを立ち上げて見てみる。

❯ docker-compose up -d

でブラウザで localhost:8000

f:id:bufferings:20180826225449p:plain

(∩´∀`)∩ワーイ。準備はこんなとこかな。じゃ、PhpStormを起動しよう。

## PhpStorm

やるのは4つ:

ここでもう一度伝えておくと、僕が実際に使ってるのはIDEAなので、PhpStormとはちょこちょこ場所が違うかも。

## CLI Interpreterの設定

プロジェクトをインポートしたら、Settingsを開いて

Build, Execution, Deployment > Dockerで「+」を押してDockerの設定。Unix socketを選ぶとConnection sucessfulの文字が出る。

f:id:bufferings:20180826230202p:plain

次に、PHPCLI Interpreterを追加する。Language & Frameworks > PHPから、CLI Interpreterの右側の「…」をクリックして

f:id:bufferings:20180828003622p:plain

「+」ボタンを押して出たポップアップから「From Docker, Vagrant, VM, Remote...」を選ぶとこんなダイアログが表示される。

f:id:bufferings:20180828004045p:plain

ここで今回僕が選んだのは、Docker ComposeじゃなくてDocker。理由は、PhpStormがPHPCSとPHPMDを実行するのにDocker Composeだと対応してなくて、Dockerだったら対応してたから。

さっきDocker Composeでビルドしたので、イメージが作られてる。それを選択「phpstormdockercakephp_php-cli:latest」

f:id:bufferings:20180826230608p:plain

んで、OK押したら、こんな感じになるので、OKを押して

f:id:bufferings:20180828004409p:plain

PHPの設定画面で、今度は「Docker Container」という項目の一番右側のフォルダアイコンをクリックして

f:id:bufferings:20180828004714p:plain

ボリュームマウントの設定を行う。docker-compose.ymlで設定してたのと同じにしてOKを押す。

f:id:bufferings:20180827001402p:plain

これでCLIの設定終わり。

## PHPUnitの実行やデバッグ実行ができた

Language & Frameworks > PHP > Test Frameworkの設定をこんな感じにして

f:id:bufferings:20180827001549p:plain

実行したら動くー!

f:id:bufferings:20180827001741p:plain

デバッグ実行もできるー!(∩´∀`)∩ワーイ

f:id:bufferings:20180827001842p:plain

## PHPCSとPHPMDのコードインスペクションができた

### Code Snifferの設定

Code Snifferは、Language & Frameworks > PHP > Code Sniffer でConfigurationの一番右側の「…」のとこを押して

f:id:bufferings:20180828005621p:plain

開いた画面の左側の「+」の部分からさっき作成したCLIを選ぶだけ。

f:id:bufferings:20180827002045p:plain

んで、Validateボタンを押してOKとなるのを確認しておく。

f:id:bufferings:20180828005741p:plain

### Mess Detectorの設定

phpmdもLanguage & Frameworks > PHP > Mess Detectorで同じことをやる。

f:id:bufferings:20180828005954p:plain

### Inspectionの設定

は、PHPCSとPHPMDのことをまだよく分かってないので、PHPCSはPSR2を選んで

f:id:bufferings:20180827002639p:plain

PHPMDは全部チェック入れてみた

f:id:bufferings:20180827002740p:plain

### 変なコードを書くと

ちゃんと指摘してくれる!(∩´∀`)∩ワーイ

f:id:bufferings:20180827003139p:plain

だけど、メニューからの一括実行には対応してないみたい。残念。ま、そこはターミナルからやればいっか。

https://youtrack.jetbrains.com/issue/WI-33088

## CakePHPアプリケーションのリモートデバッグができた

最後にリモートデバッグをやったらおしまい!

### PHP Remote Debugの設定

RunメニューのEdit Configurations…で開いたRun/Debug Configurationsから「+」を押して「PHP Remote Debug」を追加して、「Filter debug connection by IDE key」をチェックしたら右側の「…」を押して、こんな風に設定。パスのマッピングが必要なので、Use path mappingのチェックを入れて、プロジェクトルートを/appにマッピングする。

f:id:bufferings:20180827004505p:plain

PHP Remote Debug」の設定に戻って、IDE key(session id)のところを「PHPSTORM」にしておく。

f:id:bufferings:20180828010921p:plain

OKを押す。

### リモートデバッグ用のブックマークレット

JetBrainsの「Xdebug & Zend Debugger bookmarklets generator for PhpStorm」を使う。

www.jetbrains.com

XdebugIDE keyに 初期値の「PHPSTORM」が入力された状態で「Generate」ボタンを押して表示された「Start debugger」と「Stop debugger」をブックマークバーにドラッグ&ドロップ。

f:id:bufferings:20180828012351p:plain

### いよいよデバッグ

Runメニューの下の方にある「Start Listening for PHP Debug Connections」を実行。それと、分かりやすさのために今回は「Break at first line in PHP scripts」を有効にしておく。

その後、http://localhost:8000/ をブラウザで開いて、さっきブックマークした「Start debugger」をクリックして、画面をリロードすると

f:id:bufferings:20180828012822p:plain

index.phpに入ってきたところで止まって、デバッグ情報も見れたー(∩´∀`)∩ワーイ

### remote_autostart

最初はXdebugのremote_autostartをOnにしてたんだけど、そうするとCLI系のツールが全部動かなくなってしまったので、OffにしてちゃんとIDE keyを指定するようにしたのだ。

## まとめ

ホストマシンにはPHPをインストールせずに、PhpStormでApache+PHPのDockerイメージを使って、

現在のPhpStormでは対応されていないこと

  • Docker ComposerインタープリターによるPHPCS、PHPMDの実行はできない
  • メニューからInspectionを一括実行した場合には、PHPCS、PHPMDは動作しない
  • それと、PhpStormのComposer連携はRemote Interpreter非対応みたい

感想

  • 満足したので、CakePHPの勉強をやっと始めそうな気がする。