📌

この前作ったカレンダーの連携が雑すぎたので分離してパッケージ化しておいた件

2021/03/14に公開

こんにちはみなさん。

前回の記事で、とりあえずGoogle CalendarとOAuth連携する方法を記事に出しました。
https://zenn.dev/niisan/articles/dc7339b50585b7

このカレンダーと連携する部分について、検証のためにappの中に入れてました。しかし、実際にプロダクトの中に入れようとした時、カレンダーのイベントの使い方自体はプロダクトが持ちますが、どうやって取得するかやどうやって操作するかは、別にプロダクト固有のやり方というわけではありません。

というわけで、今回は、プロダクトの文脈的には直接関係ないカレンダーの操作部分を外部パッケージとして切り出してみましょう。

成果物

こんなのができました。
https://github.com/niisan-tokyo/oauth-google-calendar

composerでインストールできます。

composer require niisan/laravel-oauth-google-calendar

やり方

状況確認

前回のおさらいとして、まず、コントローラ側を見てみます。
https://github.com/niisan-tokyo/ex-oauth-laravel-calendar/blob/v1.0.0/oauth/app/Http/Controllers/TodoController.php

流石にこちらにはカレンダー関連のロジックは入れていません。

    public function __construct(CalendarService $service)
    {
        $this->service = $service;
    }
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        $user = Auth::user();
        return view('todo.index', [
            'events' => $this->service->getEventList($user->googleUser),
            'todo' => $user->todo
        ]);
    }

一方でこのサービスの中身はこんな感じです。
https://github.com/niisan-tokyo/ex-oauth-laravel-calendar/blob/v1.0.0/oauth/app/Services/CalendarService.php

    public function getEventList(GoogleUser $user)
    {
        $this->setAccessToken($user);
        $service = new Google_Service_Calendar($this->client);
        $events = $service->events->listEvents('primary', [
            'timeMin' => Carbon::now()->subDays(7)->format(DATE_RFC3339),
            'timeMax' => Carbon::now()->addDays(7)->format(DATE_RFC3339)
        ]);
        $ret = [];

        while(true) {
            foreach ($events->getItems() as $event) {
                if ($event->start and $event->end) {
                    $ret[] = [
                        'id' => $event->id,
                        'summary' => $event->getSummary(),
                        'start' => $event->start->dateTime,
                        'end' => $event->end->dateTime
                    ];
                }
            }
            $pageToken = $events->getNextPageToken();
            if ($pageToken) {
                $optParams = array('pageToken' => $pageToken);
                $events = $service->events->listEvents('primary', $optParams);
            } else {
                break;
            }
        }
        return $ret;
    }

カレンダーのロジックが丸々出ています。
別にこれでもいいのですが、プロダクトの中に突っ込んでいる限りは、このプロダクトの中でしか使えません。

まあ、他のプロダクトで使うときには、コピペするって方法もありますが、使いたくないです。

方針の決定

カレンダーのイベントをどうやって使ったかとか、何を入力しているかなどはプロダクトにかかわる部分ですが、カレンダーの操作自体はプロダクトにとってはあまり興味のない部分です。
そこで、カレンダーの操作部分はパッケージ化して外に出してしまいましょう。

外部パッケージにする利点は以下の通りです。

  • プロダクトの中から「カレンダーを操作する」という興味のない部分を外に追い出せる
  • 他のプロダクトでも使いまわせる
  • OSS公開することで、実績作りもできる

外部パッケージを作る

パッケージのひな型の作成

外部パッケージを作ると決めたら、名前を決めましょう。
https://github.com/niisan-tokyo/oauth-google-calendar

今時のPHPであれば、GitHub + Packagist で簡単に外部パッケージを作ることができます。
https://qiita.com/niisan-tokyo/items/a6a3ee984f0ccb6c734d

GitHubで空のリポジトリを作って、同時にライセンスとREADMEを作っておきます。
これらをローカルにクローンします。

リポジトリをクローンしたら、開発環境を整えます。これは人それぞれなのですが、私のようにVS Code使っている場合は、開発用のコンテナを立ててVS Code Remoteを使ってそこで開発するのがおすすめです。
設定についての詳細はリポジトリの.devcontainerを参照してください。
https://github.com/niisan-tokyo/oauth-google-calendar/tree/main/.devcontainer

開発環境が整ったら、まずcomposer initで開発を始めましょう。
必要なパッケージとしてはこの辺を入れています。

   "require": {
        "google/apiclient": "^2.9",
        "nesbot/carbon": "^2.0",
        "illuminate/support": "^5.8|^6.0|^7.0|^8.0"
    },
    "require-dev": {
        "phpunit/phpunit": "^9.5",
        "mockery/mockery": "^1.4"
    },

準備が整ったら実装を開始します。

インターフェースの決定

実装をするにあたり、どんなインターフェースにするか考えます。
大体、今の時点でこれくらいのメソッドが欲しくなります。

  • カレンダーの予定一覧取得( getEventList )
  • カレンダーの予定の作成( createEvent )
  • カレンダーの予定の削除( deleteEvent )

これは、元のCalendarServiceにあったメソッド群です。

加えて、OAuthの仕組みも一緒に外に出したり、ついでに休日も欲しくなりましたので、以下のメソッドも追加しています。

  • カレンダーの予定の更新( updateEvent )
  • OAuth認可URL取得( getAuthUri )
  • OAuth認可時のトークン取得( getTokenByCode )
  • 休日一覧取得( getHolidays )

とりあえず、この程度のメソッドがあれば十分そうです。

実装

本当はインターフェースを作って実装を作るべきなんですが、パッケージ名にGoogleとか入れちゃったので、今回はそのまま実装を作ります。
https://github.com/niisan-tokyo/oauth-google-calendar/blob/main/src/OauthCalendarService.php

外部パッケージとして公開する場合は必ずテストを入れましょう
https://github.com/niisan-tokyo/oauth-google-calendar/blob/main/tests/OauthCalendarServiceTest.php

プロダクトの外にコードを追い出しているため、プロダクト上でのテストは困難になります。また、他のプロダクトでも使ったり、別の第三者が安心して使ってもらえるようにするためにも、テストコードを書いておくことは必須です。

接続部分の実装

本体コードの実装が終わったら、これをプロダクトにつなぎこむために、接続部分を実装します。

https://github.com/niisan-tokyo/oauth-google-calendar/blob/main/src/OauthCalendarServiceProvider.php

    public function register()
    {
        $this->app->singleton(OauthCalendarService::class, function () {
            $client = new Client([
                'client_id' => config('google-calendar.client_id'),
                'client_secret' => config('google-calendar.client_secret'),
                'redirect_uri' => config('google-calendar.redirect')
            ]);
            $oauth_service = new Google_Service_Oauth2($client);
            $calendar_service = new Google_Service_Calendar($client);
            return new OauthCalendarService($client, $oauth_service, $calendar_service);
        });
    }

    public function boot()
    {
        $this->publishes([
            __DIR__.'/config/google-calendar.php' => config_path('google-calendar.php'),
        ]);
    }

さらに、composer.jsonを編集し、

    "extra": {
        "laravel": {
            "providers": [
                "Niisan\\Laravel\\GoogleCalendar\\OauthCalendarServiceProvider"
            ]
        }
    },

こうしておくことで、laravelは自動でパッケージを登録し、laravelのコンテナから上記の実装を供給することができるようになります。

公開する

大体作り終えたら、パッケージを公開しましょう。composer を使ったパッケージ公開は以下の手順で行います。

  • GitHubにコードを公開( ほかのリポジトリでもいい )
  • Packagistにてリポジトリを登録
  • バージョンタグをつける

GitHub使っているのなら、GitHub上でタグ付けるのがおすすめです。
バージョンはセマンティック バージョニングを使うと、やりやすいです。

使う

公開したら、分離元で使えるようにしましょう。
https://github.com/niisan-tokyo/ex-oauth-laravel-calendar/tree/v2.0.0/oauth

composer require niisan/laravel-oauth-google-calendar

これでパッケージをとってきます。
パッケージが入ったら、laravelのほうでサービスディスカバリが走ってパッケージがプロダクトの中で使えるようになります。
次いで、パッケージ専用の設定ファイルを持ってきます。

php artisan vendor:publish

これで、niisan/laravel-oauth-google-calendarを選べば、パッケージの中で使っている設定ファイルをconfig配下にコピーして使えるようになります。

元のリポジトリではCalendarServiceというクラスでカレンダーを扱っていましたが、この中で、カレンダーに接続している部分を外に出すと考えましょう。

class CalendarService
{
    private OauthCalendarService $oauthCalendarService;

    public function __construct(OauthCalendarService $oauthCalendarService)
    {
        $this->oauthCalendarService = $oauthCalendarService;
    }

    /**
     * イベントのリストの取得
     * 
     * @param GoogleUser $user
     * 
     * @return array
     */
    public function getEventList(GoogleUser $user)
    {
        $events = $this->oauthCalendarService->getEventList($user, [
            'timeMin' => Carbon::now()->subDays(7),
            'timeMax' => Carbon::now()->addDays(7),
        ]);
        $ret = [];
        foreach ($events as $event) {
            if ($event->start and $event->end) {
                $ret[] = [
                    'id' => $event->id,
                    'summary' => $event->getSummary(),
                    'start' => $event->start->dateTime,
                    'end' => $event->end->dateTime
                ];
            }
        }
        
        return $ret;
    }
}

こうして、元のリポジトリの中からカレンダーを取得する部分を外部に切り出し、サービスのほうでは得られたデータをどうやって加工するかに力を注げるようになります。

まとめ

というわけで、カレンダーとの連携アプリの中で、カレンダーに対する操作部分を外部に切り出し、元のリポジトリをすっきりさせる小細工を弄してみました。
まあ、こういうのはもっとカジュアルにやればいいのだと思います。もちろん、外部に切り出すことで、容易に変更できなくなりますが、変更が必要ない程度までちゃんと作っておけばいいということでもあります。

今回はこんなところです。

Discussion