PHP7.2 × DockerでgRPCクライアントを使うための環境構築

Goでクライアントを作るときには、`brew`や`go get`でパッケージをいくつか入れるだけで済むのですが、PHPの方ではいくつかハマりどころがあったのでまとめました。

# 前提
* protoコマンドがインストールされている
* 使用するRPC方式はUnaryRPCである
* コード生成はローカル環境で行う

# 扱うprotoファイル
下記のprotoに少し変更をかけたものを扱います。
https://grpc.io/docs/languages/php/quickstart/

```hello.proto
package hello;

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}
```

# コード生成
下記ページを参考にしました。
https://grpc.io/docs/languages/php/quickstart/

## コード生成用のPHPプラグインを追加
protoファイルからPHP用の各種クラスを生成するために、grpc_php_plugin を入れる必要があります。

```
$ git clone -b v1.30.0 https://github.com/grpc/grpc
$ cd grpc && git submodule update --init && make grpc_php_plugin
```

すると、ソースを展開したディレクトリに `bins/opt/grpc_php_plugin` が生成されます。

## コード生成実行
下記コマンドを入力します。

```
$ protoc --proto_path=proto \
  --php_out=src/gen \
  --grpc_out=src/gen \
  --plugin=protoc-gen-grpc=./grpc/bins/opt/grpc_php_plugin \
  ./proto/hello.proto
```

この時のオプションとして、

* `--proto_path` にはprotoファイルが配置されているディレクトリを指定する
* `--php_out` と `--grpc_out` は同じディレクトリを指定する
* `--plugin=protoc-gen-grpc=` に、先ほどプラグインをインストールしたディレクトリの `bins/opt/grpc_php_plugin` を指定する

を行います。
これでコード生成はOKです。

# クライアント環境準備
インストールするパッケージは下記ページを参考にしました。
https://cloud.google.com/php/grpc?hl=ja#php-implementation

Dockerの上で動かしたPHPで、gRPCのリクエストを送るための環境を構築します。

## php.ini
`grpc.so` 拡張パッケージの記述がポイントです。

```php.ini
[Date]
date.timezone = "Asia/Tokyo"

[mbstring]
mbstring.internal_encoding = "UTF-8"
mbstring.language = "Japanese"

extension = grpc.so
```

## Dockerfile
peclでgrpcパッケージを入れることがポイントです。

```Dockerfile
FROM php:7.2-stretch

RUN apt-get update -y \
    && apt-get install -y zip autoconf zlib1g-dev \
    && apt-get clean

RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
COPY php.ini /usr/local/etc/php/php.ini
RUN pecl install grpc

WORKDIR /var/local
COPY . .
RUN composer install
```

## composer.json
ここでは先ほどのコード生成によってファイルが格納された先を `src/gen` として扱っています。

```composer.json
{
    "autoload": {
        "psr-4": {
            "Hello\\": "src/gen/Hello",
            "GPBMetadata\\": "src/gen/GPBMetadata"
        }
    },
    "require": {
        "php": ">=7.2",
        "google/protobuf": ">=3.0",
        "grpc/grpc": ">=v1.27.0"
    },
    "repositories": [
        {
          "type": "vcs",
          "url": "https://github.com/stanley-cheung/Protobuf-PHP"
        }
    ]
}
```

ポイントは、 

* `google/protobuf` パッケージのインストール
* `grpc/grpc` パッケージのインストール
* コード生成されたクラスをautoloadで読み込み指定する

ことです。

## docker-compose.yaml
先ほどのphp.iniを、ボリュームマウントでコンテナに配置することがポイントです。

```docker-compose.yaml
version: "3"
services:
  php:
    build: .
    volumes:
      - ./php.ini:/usr/local/etc/php/php.ini
      - .:/var/local/
    ports:
      - 80:80
```

# クライアント実装
実装例です。

```srcipts/RunHello.php
<?php

use Hello\GreeterClient;
use Hello\HelloRequest;

$hostname = 'localhost:8000';
$client = GreeterClient($hostname, [
    'credentials' => \Grpc\ChannelCredentials::createInsecure()
]);

$request = new HelloRequest();
$request->setName("DummyName");
list($reply, $status) = $client->SayHello($request)->wait();
$message = $reply->getMessage();
```

この時vendor配下に `\Grpc\ChannelCredentials` クラスが存在せず、エディタでは警告等が出ます。
これはcomposerで入れるものではなく、Dockerfileとphp.iniで指定したgrpc拡張パッケージに含まれています。

この辺↓
https://github.com/grpc/grpc/blob/f3e748f646f482b9de2801c1542094bd5b6fdf3a/src/php/ext/grpc/channel_credentials.c#L133

よって、必要なライブラリ自体はDocker側にインストールされているため、実行時には正常に通ります。
（個人的にはここがハマりポイントでした）

# 実行
オートローダーを読み込んで、クライアント実装のスクリプトを読み込めば実行されます。

```
$ docker-compose run --rm php -a
php> require_once('vendor/autoload.php');
php> require_once('scripts/RunHello.php');
```

あとはこのクライアントをクラスに閉じ込めたり、composerのパッケージ化などを行えばWebサーバー等にも組み込むことができます。