Dockerで構築したLaravelにgRPCを導入してfirestoreにデータを書き込む方法

LaravelをAPIサーバーとして使い、Laravelからwebsocketなどではなく、firestoreでチャットやプッシュ通知などリアルタイム通信させたいときがあります。
公式（ https://firebase.google.com/docs/firestore/quickstart?hl=ja ）によれば、
PHPの場合、gRPCのインストールが必要です。
dockerでのインストールの情報が皆無なので書いておきます。

# gRPCのインストール

## dockerfileの修正（docker-compose で制御する場合の参考）

```docker:docker/php/Dockerfile

FROM php:7-fpm

ENV DEBIAN_FRONTEND noninteractive

COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
RUN apt-get update && apt-get -y install git autoconf zlib1g-dev libicu-dev libonig-dev libzip-dev unzip &&\
    apt-get clean && \
    pecl install grpc && \
    pecl install protobuf && \
    rm -rf /var/lib/apt/lists/* &&\
    docker-php-ext-install pdo_mysql &&\
    composer config -g process-timeout 3600 && \
    mkdir -p /app
COPY ./docker/php/php.ini /usr/local/etc/php/php.ini

WORKDIR /app

```

この中で、gRPCのインストールに関係する部分は、

```docker:docker/php/Dockerfile
autoconf zlib1g-dev

```

でpeclをインストールし、

```docker:docker/php/Dockerfile

pecl install grpc && \
pecl install protobuf && \

```

でgrpc本体をインストールという流れです。
protobufはgrpcのパフォーマンスを向上させるライブラリのようです。


## php.iniの修正

dockerfile書くときにphp.iniも書いていると思いますので、
そこに以下を追記

```php.ini:docker/php/php.ini
extension=grpc.so
extension=protobuf.so

```

そして、composer.jsonファイルの
"require"に

```json:composer.json
"grpc/grpc": "^1.27.0",
```


を追記してください。
バージョンは公式（https://cloud.google.com/php/grpc?hl=ja）で最新版を確認してください。

そのうえで、再ビルドすればgRPCの導入完了です。
（ただし、gRPCのインストールはめちゃくちゃ時間がかかります。。。）

# laravelからfirestoreへのsetができるようにする
## ライブラリの導入

コマンドでライブラリのインストール

``` command:command
composer require google/cloud-firestore
composer require kreait/laravel-firebase

```
config/app.phpに追記

```php:config/app.php
return [
    // ...
    'providers' => [
        // ... これを追記
        Kreait\Laravel\Firebase\ServiceProvider::class
    ]
    // ...
];

```

次に、firebaseのコンソールで、歯車 > サービスアカウント > FIrebase Admin SDK > 新しい秘密鍵の生成
をクリックしてください。


![2021-01-25_14h31_21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520231/b4b42c02-175e-a6d9-abc8-d14914e77705.png)


ダウンロードされたファイル名を
firebase_credentials.json
に変えて、プロジェクト直下に設置し、
秘匿情報なので、
gitignoreに
firebase_credentials.json
を追記しておきましょう。

__もし本番環境がECS fargateの場合、本番環境ではsshログインできませんので、codebuildのときにs3からコピーするように処理を追記しておきましょう__

```text:.env
FIREBASE_CREDENTIALS=firebase_credentials.json
```

そして、コマンドでconfigファイルを作っておきましょう。

```command:command
php artisan vendor:publish --provider="Kreait\Laravel\Firebase\ServiceProvider" --tag=config
```

firestoreへのデータのセット等はいろんな箇所で使うことが多いと思うので、サービス化しておきましょう。

以下、サービス化（知っている方、必要ない方は読み飛ばしてください。）

```php:app/Providers/FirebaseApiServiceProvider.php

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Foundation\Application;
use App\Services\FirestoreApiService;

class FirestoreApiServiceProvider extends ServiceProvider
{
    /**
     * Register services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->bind('FirestoreApi', function(Application $app){
            return new FirestoreApiService();
        });
    }

    /**
     * Bootstrap services.
     *
     * @return void
     */
    public function boot()
    {
        //
    }
}


```

```php:app/Facades/FirestoreApiFacade.php
<?php

namespace App\Facades;
use Illuminate\Support\Facades\Facade;

class FirestoreApiFacade extends Facade
{
  protected static function getFacadeAccessor()
  {
    return 'FirestoreApi';
  }
}

```

```php:config/app.php

// providersに追記
        App\Providers\FirestoreApiServiceProvider::class,

// aliasesに追記
        'FirestoreApi' => App\Facades\FirestoreApiFacade::class,

```

これでサービス化は完了です。

そしていよいよfirestoreへのデータset,update,deleteです。


```php:app/Services/FirestoreApiService.php
<?php
namespace App\Services;

use Google\Cloud\Firestore\FieldValue;

class FirestoreApiService
{
    protected $db;

    public function __construct()
    {
        $this->db = app('firebase.firestore')->database();
    }

    /**
     * firestoreへのデータset
     * @param sender_id int
     * @param room_id int
     * @param comment string
     * @return void
     */
    public function storeComment($sender_id, $room_id, $comment_id ,$comment)
    {
        $docRef = $this->db->collection('rooms')->document($room_id)->collection('comment');

        $docRef->document($comment_id)->set([
            'comment' => $comment,
            'sender_id' => $sender_id,
            'created_at' => FieldValue::serverTimestamp(),
            'updated_at' => FieldValue::serverTimestamp(),
        ]);
    }

    /**
     * firestoreのデータをupdate
     * @param sender_id int
     * @param room_id int
     * @param comment_id int
     * @param comment string
     * @return void
     */
    public function updateComment($sender_id, $room_id, $comment_id, $comment)
    {
        $docRef = $this->db->collection('rooms')->document($room_id)->collection('comment');

        $docRef->document($comment_id)->update([
            [ 'path' => 'comment', 'value' => $comment],
            ['path' => 'updated_at', 'value' => FieldValue::serverTimestamp()]
        ]);
    }

    /**
     * firestoreのデータをdelete
     * @param room_id int
     * @param comment_id int
     * @return void
     */
    public function deleteComment($room_id, $comment_id)
    {
        $docRef = $this->db->collection('rooms')->document($room_id)->collection('comment');

        $docRef->document($comment_id)->delete();
    }

}

```

これでuseなどを使わなくても、どこでも

```php
\FirestoreApi::storeComment($sender_id, $room_id, $comment);
```

で呼び出して、Laravelからfirestoreにsetしたりできます。