PHP7,Laravel(Lumen),MySQL,Redisを利用したAPI開発環境をDockerで構築する


以前構築したサービスのフレームワークはPhalconだったのだが、
PHP7への対応が遅かったり、今後のメンテナンスでどうなるのか不安なので、他のフレームワークも試そうと思った。
[一からマイクロサービスの開発フローを作った話](http://qiita.com/tomoyamachi/items/0f049c87bfdef4aa6ad9)

ためしにLaravelと、[Laravel](https://laravel.com/)製の軽量フレームワーク[Lumen](https://lumen.laravel.com/)を試した。
今回、LaravelとLumenを試したが、Laravelのインストールするだけでほぼ動作したのに対し、Lumen側では一部ハマりどころがあったので、Lumenのインストール方法と連携からメモしていく。

コードは以下に公開してある。
https://github.com/tomoyamachi/lumen-docker-sample

# versions
- PHP : v7.1.*
- MySQL : v5.6.*
- Redis : v3.0.*
- Lumen : v5.2.* : プロジェクトは独立しているので置き換え可能

# 最終フォルダ構成

```
.
├── docker-compose.yml       # docker-compose用
├── deploy                   # docker各コンテナ設定用
├── features                 # APIテスト用(cucumber)
└── sample                   # サンプルアプリ用ファイル一式
    ├── app
    │   ├── Console
    │   ├── Events
    │   ├── Exceptions
    │   ├── Http
    │   │   ├── Controllers  # コントローラを保存
    │   │   ├── Middleware
    │   │   └── routes.php   # ルーター
    │   ├── Jobs
    │   ├── Listeners
    │   ├── Providers
    │   └── User.php         # モデル
    ├── artisan
    ├── bootstrap            # 起動時に読み込むパッケージなどを指定
    ├── composer.json
    ├── composer.lock
    ├── database
    │   ├── factories
    │   ├── migrations       # マイグレーションファイル
    │   └── seeds
    ├── phpunit.xml
    ├── public
    ├── resources
    ├── storage
    │   └── logs
    │       └── lumen.log    # log出力先
    ├── tests                # テストファイル
    └── vendor

```

# Lumenの導入

[公式サイト](https://lumen.laravel.com/docs/5.2/installation)の通りにインストール。

```
$ composer global require "laravel/lumen-installer"
```

## フォルダ内でプロジェクト作成

```
$ mkdir lumen-docker-sample && cd lumen-docker-sample
$ composer create-project --prefer-dist laravel/lumen sample
...lumenプロジェクト作成...
```

# docker-composeの設定
## docker-compose.ymlの作成

プロジェクトのルートディレクトリにdocker-compose.ymlとコンテナ設定用のファイルを作成する。今回はdeployフォルダ内に各種dockerfileを置いた。

```docker-compose.yml
version: '2'
services:
    web:
        build:
            context: ./
            dockerfile: deploy/web.docker
        volumes:
            - ./:/var/www
        ports:
            - "8080:80"
        links:
            - app
    app:
        build:
            context: ./
            dockerfile: deploy/app.docker
        volumes:
            - ./:/var/www
        links:
            - database
            - cache
        environment:
            - "DB_PORT=3306"
            - "DB_HOST=database"
            - "REDIS_PORT=6379"
            - "REDIS_HOST=cache"
    database:
        image: mysql:5.6
        environment:
            - "MYSQL_ROOT_PASSWORD=sample_pass"
            - "MYSQL_DATABASE=sample_db"
        ports:
            - "33061:3306"
    cache:
        image: redis:3.0
        ports:
            - "63791:6379"
    queue:
        build:
            context: ./
            dockerfile: deploy/queue.docker
        volumes:
            - ./:/var/www
        links:
            - database
        environment:
            - "DB_PORT=3306"
            - "DB_HOST=database"

```

## コンテナ起動

```
$ docker-compose up -d # docker-composeした環境をバックグラウンドで走らせる
Creating network "lumendocker_default" with the default driver
Creating lumendocker_cache_1
Creating lumendocker_database_1
Creating lumendocker_app_1
Creating lumendocker_web_1
Creating lumendocker_queue_1
```

## 動作確認
docker-machineを利用しており、defaultという名前で登録している前提で記載する。
docker-composeファイルで設定した8080ポートに通信ができるかを確認。

```
$ curl $(docker-machine ip default):8080
Lumen (5.2.8) (Laravel Components 5.2.*) # OK
```

# アプリケーションの開発
## MySQLとの紐付け
LumenはLaravelのライブラリを最低限しか読まないことで軽量化しているが、ORMやRedisとの連携を取ろうとすると、読み込まないように設定してあるライブラリを読み込む必要がある。

LaravelのModelを利用したい場合、bootstrap/app.phpで以下のコメントアウトを外す。

```lumen-docker-sample/bootstrap/app.php
$app->withEloquent();
```

また、.envファイルに、docker-compose.ymlファイルで指定したMySQLの設定を記載する。

```lumen-docker-sample/.env
DB_CONNECTION=mysql
DB_HOST=192.168.99.100
DB_PORT=33061
DB_DATABASE=sample_db
DB_USERNAME=root
DB_PASSWORD=sample_pass
```

## Redisとの紐付け

同じくライブラリを読み込む設定をする。また、必要なパッケージをcomposerから取得する

```lumen-docker-sample/composer.json

    "require": {
        "php": ">=7.0.10",
        "laravel/lumen-framework": "5.2.*",
        "vlucas/phpdotenv": "~2.2",
        
        // ここを追加
        "illuminate/redis": "5.2.*",
        "predis/predis": "~1.0"
    },
```

bootstrap.appで読み込むライブラリなどを設定。

```lumen-docker-sample/bootstrap/app.php
$app->withFacades(); // コメントアウトする
$app->register(Illuminate\Redis\RedisServiceProvider::class); //プロバイダにRedisのクラスを追加
```

同じく.envファイルにredisの接続先情報を追加。

```lumen-docker-sample/.env
CACHE_DRIVER=redis
SESSION_DRIVER=redis
QUEUE_DRIVER=sync

REDIS_HOST=192.168.99.100
REDIS_PASSWORD=null
REDIS_PORT=63791
```

## ユーザの名前を保存と取得をするAPIを作る

### テーブルの作成

まず`php artisan make:migration create_user`で、migrationファイルを作成し、テーブル構造を記入。

```database/migrations/xxxxx_create_user.php
<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class User extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::drop('users');
    }
}

```

テーブルを作成する。

```
$ php artisan migrate
Migration table created successfully.
Migrated: xxxxx_create_user
```

### routesの設定

インターフェースを決める。

```sample/app/Http/routes.php
$app->post('users', 'UserController@create'); // ユーザの作成
$app->get('users/{id}', 'UserController@get'); // ユーザの取得
```

### Controllerの作成

むりやりMySQLとRedisを併用する。

```sample/app/Http/Controllers/UserController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Redis;

class UserController extends Controller
{
    public function create(Request $request)
    {
        $name = $request->input('name');
        if (empty($name)) {
            return 'invalid name';
        }
        $user = new \App\User();
        $user->name = $name;
        if ($user->save()) {
            // redisにユーザ情報を保存
            Redis::set('user:profile:'.$user->id, $user);
            return 'success';
        }
        return 'failed';
    }

    public function get(Request $request, $id)
    {
        $cacheResult = Redis::get('user:profile:'.$id);

        // redisキャッシュがあればキャッシュから
        if ($cacheResult) {
            return $cacheResult;
        }

        // なければDBから取得
        return \App\User::findOrFail($id);
    }
}
```

### モデルを作成

特にこったことはしないので、Eloquent\Modelを継承するだけでいい。

```
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
}
```

### 動作確認

```
$ curl -X POST -F "name=test" http://$(docker-machine ip default):8080/users
success

$ curl $(docker-machine ip default):8080/users/1 | jq
{
  "name": "test",
  "updated_at": "2016-08-26 09:36:37",
  "created_at": "2016-08-26 09:36:37",
  "id": 1
}
```

# その他
## 単体テスト
phpunitがデフォルトでcomposerの中に含まれているので、phpunitコマンドでテストできる。

```
vendor/bin/phpunit
PHPUnit 4.8.27 by Sebastian Bergmann and contributors.

.

Time: 231 ms, Memory: 7.00MB

OK (1 test, 1 assertion)
```

## 結合テスト
同じくphpunitでテスト可能らしいが、まだ試していない。
https://lumen.laravel.com/docs/5.2/testing#application-testing

# 今後のこと

Lumenを今後の開発で利用するかは分からないが、確かに最低限の機能という感じで印象がいい。あとはWerckerCIと連携して色々やりたい。