Google Photos APIをPHPで使う

Google Photos APIを使用して、Google Photoにアクセスするアプリを作成しています。
その過程でのAPIを利用するまでを記載します。

基本的にオフィシャルのリファレンスを参照していますが、そこから読み取りにくい部分などを具体的に紹介できたらと思っております。

[Google Photos API](https://developers.google.com/photos/library/guides/get-started-php) 

Laravelを使用したコードを記載していますが、Laravelに特化した部分は少ないと思いますので、
Laravel以外のフレームワークを使っている場合もご参考になればと思います。

## 準備

### Laravelプロジェクト作成
```bash
$ composer create-project laravel/laravel googlephoto_sample --prefer-dist
```

### Google Photos APIを有効にする
- [Google APIs](https://console.developers.google.com/apis/dashboard)のダッシュボードからプロジェクト作成 → APIとサービスを有効化 → Google Photos APIを選択
- Google APIsのプロジェクトが作成済みなら [Google Photos API](https://developers.google.com/photos/library/guides/get-started-php)  このページから直接もできます

### クライアントID作成
- Google APIsのダッシュボードから「認証情報」を選択 → 認証情報を作成 → OAuthクライアントID
- アプリケーションの種類は、ウェブアプリケーション
- 承認済みのリダイレクトURIは、今回は取り合えず http://localhost/photo/redirect
- 作成したクライアントIDの画面からJSONをダウンロード → credential.jsonにリネームしておく
- ダウンロードしたcredential.jsonをLaravelプロジェクトのフォルダに入れておきます。

### Google APIs Client,  Google Photos API ライブラリのインストール
```json:composer.json
    "require": {
        "google/apiclient": "^2.0",
        "google/photos-library": "^1.5"
    },
```

```bash
$ composer update
```

composer.jsonを編集せずに、直接コマンドラインかｒ`composer require google/apiclient:"^2.0"`みたいにやるとなぜか失敗します。

## Google Photos APIを使ってみる

PhotoControllerという名前のコントローラーと、コントローラーから呼び出されるGooglePhotoServiceという名前のクラスを作成していきます。

### 認証URLの生成、表示

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

use Google\Auth\Credentials\UserRefreshCredentials;
use Google\Photos\Library\V1\PhotosLibraryClient;

class GooglePhotoService
{
    private function createGoogleClient()
    {
        $client = new \Google_Client();
        $client->setApplicationName('GooglePhotoSample');
        $client->setScopes(\Google_Service_PhotosLibrary::PHOTOSLIBRARY_READONLY); 
        $client->setAuthConfig(base_path("credentials.json"));
        $client->setAccessType('offline');
        return $client;
    }

    public function createAuthUrl($redirectUri)
    {
        $client = $this->createGoogleClient();
        $client->setRedirectUri($redirectUri);
        return $client->createAuthUrl();
    }
}
```

```php:app\Http\Controllers\PhotoController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Services\GooglePhotoService;

class PhotoController extends Controller
{
    public function start()
    {
        $service = new GooglePhotoService();
        $url = $service->createAuthUrl("http://localhost/photo/redirect");
        return view(start, [
            'url' => $url
        ]);
    }
}

```

```php:resources\views\start.blade.php
<html>
<body>
  <div class="flex-center position-ref full-height">
    <div class="content">
      Step.1<br/>
      <a href="{{$url}}">{{$url}}</a>
    </div>
  </div>
</body>
</html>
```

```php:routes\web.php
Route::get('/photo/start, 'PhotoController@start);
```

### OAuthのリダイレクト先を作成
リダイレクト時に受け取ったauthCodeをもとにアクセストークンを取得する。
取得したアクセストークンを保存する処理は`$saveTokenFunction`という名前のコールバック関数として定義する。

```php:app\Services\GooglePhotoService.php
    public $saveTokenFunction = null;

    public function getAccessToken($authCode)
    {
        $client = $this->createGoogleClient();
        $accessToken = $client->fetchAccessTokenWithAuthCode($authCode);
        if ($this->saveTokenFunction != null) {
            call_user_func($this->saveTokenFunction, $accessToken);
        }
        return $accessToken;
    }
```

```php:app\Http\Controllers\PhotoController.php
    public function saveToken($accessToken)
    {
        // 保存の処理は省略。データベースなどに保存
    }

    public function redirect(Request $request)
    {
        $authCode = $request->input('code');
        $service = new GooglePhotoService();
        $service->saveTokenFunction = [$this, 'saveToken'];
        $service->getAccessToken($authCode);
        return view('redirect', [
        ]);
    }
```

```php:resources\views\redirect.blade.php
// 省略
```

```php:routes\web.php
Route::get('/photo/redirect, 'PhotoController@redirect);
```

### PhotosLibraryClientを生成
取得したアクセストークンで、PhotosLibraryClientを生成
アクセストークンの期限が切れたときのリフレッシュの処理も一緒に実装

```php:app\Services\GooglePhotoService.php
    public function getPhotosLibraryClient($accessToken)
    {
        $client = $this->createGoogleClient();
        $client->setAccessToken(json_decode($accessToken, true));
        if ($client->isAccessTokenExpired())
        {
            $client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
            if ($this->saveTokenFunction != null) {
                call_user_func($this->saveTokenFunction, $client->getAccessToken());
            }
        }

        $authCredentials = new UserRefreshCredentials(\Google_Service_PhotosLibrary::PHOTOSLIBRARY_READONLY, [
            "client_id" => $client->getClientId(),
            "client_secret" => $client->getClientSecret(),
            "refresh_token" => $client->getRefreshToken()
        ]);
        return new PhotosLibraryClient(['credentials' => $authCredentials]);
    }
```

### PhotosLibraryClientを使用してGoogle Photoからアルバム情報を取得してみる

```php:app\Http\Controllers\PhotoController.php
    public function listAlbums()
    {
        $accessToken = [] // 保存してあるアクセストークンを取得する処理
        // 例) $accessToken = UserToken::where('user_id', auth()->user()->id)->value('token');
        $service = new GooglePhotoService();
        $service->saveTokenFunction = [$this, 'saveToken'];
        $client = $service->getPhotosLibraryClient($accessToken);
        $pagedResponse = $client->listAlbums();
        $data = [];
        foreach ($pagedResponse->iterateAllElements() as $album) {
            $data[] = $album->getTitle();
        }
        return response()->json([
            "title" => $data
        ]);
    }
```

```php:routes\web.php
Route::get('/photo/albums', 'PhotoController@listAlbums');
```

### 以上です
authCodeからAccessTokenを取得する処理を、なんとかAPIだけで完結させたかったのですが
できませんでした。今回は、このような形でウェブページを一度経由してその後APIで利用するという形になりました。