Google Sheets API を使ったスプレッドシートとの連携（Laravelを用いて）

# Google Sheets API を使ったスプレッドシートとの連携（Laravelを用いて）
Laravelを用いてデータ入力・保存を行うWebアプリを開発した際に、APIを叩いてデータがGoogleスプレッドシートにも反映されるように実装した際の備忘録を残しておく。
Google APIとの認証には、OAuth 2.0ではなく、サービスアカウントを用いる。
理由としては、
- 人間ユーザーが介入する必要がない（秘密鍵を管理しておけばバックエンドで自動的にデータ保存が可能になる）

通常、OAuthでのアクセスだと、GmailログインしてアクセスしてAPIを使うためにOAuthでアクセス許可を求めるという面倒な作業が必要だが、サービスアカウントだとそれらをバックエンドで自動的に行なってくれる。（ただし、OAuth 2.0にもメリットはあるので調べること）

## 1. Google API の設定

### 1.1 Google Cloud Platform (GCP) プロジェクト作成
Google Cloud Console で新しいプロジェクトを作成します。

### 1.2 Google Sheets API の有効化
GCP のプロジェクトで **Google Sheets API** を有効化します。

### 1.3 サービスアカウントの作成
スプレッドシートへのアクセスを許可するため、サービスアカウントを作成し、必要な JSON 形式の認証キーを取得します。

### 1.4 スプレッドシート共有設定
サービスアカウントのメールアドレスをスプレッドシートの共有設定に追加します。

---

## 2. Google Sheets API の有効化手順

1. GCP のメインダッシュボードから、**「API とサービス」 > 「ライブラリ」** に移動します。  
2. **Google Sheets API** を検索し、クリックします。  
3. **「有効にする」** をクリックします。  

---

## 3. サービスアカウントの作成

1. **「API とサービス」 > 「認証情報」** に移動します。  
2. **「+ 認証情報を作成」 > 「サービスアカウント」** を選択します。  
3. サービスアカウントに名前を付け（例: Sheets Integration Service Account）、説明を追加して **「作成して続行」** をクリックします。  
4. サービスアカウントに以下のロールを割り当てます：
   - **プロジェクト > 編集者**  
5. 必要に応じて、**「完了」** をクリックします。  

---

## 4. サービスアカウントの認証キーを作成

1. 作成したサービスアカウントをリストから選択します。  
2. **「鍵」** タブに移動し、**「鍵を追加」 > 「新しい鍵を作成」** を選択します。  
3. **JSON 形式** を選択して鍵を作成します。  
4. 鍵ファイルが自動的にダウンロードされるので、安全な場所に保存します（アプリケーションで使用するため重要）。  

---

## 5. スプレッドシートの共有設定

1. Google スプレッドシートを開く（または新しいスプレッドシートを作成）。  
2. サービスアカウントのメールアドレス（JSON ファイルに記載）を取得します。  
3. スプレッドシートの **「共有」** ボタンをクリックし、このメールアドレスを招待します。  
4. **編集権限** を付与して共有します。  

---

## 6. 必要なライブラリのインストール

### 6.1 ライブラリのインストール
Laravel で Google Sheets API を使用するために、`google/apiclient` ライブラリを Composer でインストールします。

```bash
composer require google/apiclient
```

### 6.2 サービスアカウントの認証設定

1. サービスアカウントの JSON 認証ファイルを Laravel プロジェクトに配置します。  
   - 例: `storage/app/google-service-account.json`
   
2. `.env` ファイルにサービスアカウントのパスを設定します：
    ```bash
     GOOGLE_APPLICATION_CREDENTIALS=/var/www/html/storage/app/google-service-account.json
    ```

3. Laravel が環境変数を読み取るように、キャッシュをクリアします：

    ```bash
    sail artisan config:clear
    ```

### 6.3 必要な環境変数を追加
.env ファイルに以下を追加します：
```bash
GOOGLE_SPREADSHEET_ID=あなたのスプレッドシートID
GOOGLE_APPLICATION_CREDENTIALS=/var/www/html/storage/app/google-service-account.json
```

設定は以上。
# 以下、Gogle Sheets API操作のための実装
APIが完成しても、スプレッドシートに反映されないことは多くある。その際に、APIの連携がうまくいっていないのか、あるいは入力が間違えているのかはっきりするように、Googleスプレッドシートにデータがしっかり反映される環境が整っていることを確認するためだけのエンドポイントを作るのもいいかもしれない。

## 7. 連携確認用のエンドポイント作成
# 7.1 Serviceクラス
```php
<?php

namespace App\Services;

use Google\Client;
use Google\Service\Sheets;

class GoogleSheetsService
{
    private $client;
    private $service;

    public function __construct()
    {
        if (!file_exists(env('GOOGLE_APPLICATION_CREDENTIALS'))) {
            throw new \Exception('Google Application Credentials file not found: ' . env('GOOGLE_APPLICATION_CREDENTIALS'));
        }

        $this->client = new Client();
        $this->client->setApplicationName('Laravel Google Sheets Integration');
        $this->client->setScopes([Sheets::SPREADSHEETS]);
        $this->client->setAuthConfig(env('GOOGLE_APPLICATION_CREDENTIALS'));

        $this->service = new Sheets($this->client);
    }

    public function appendDataToSheet($spreadsheetId, $range, $values)
    {
        try {
            $body = new Sheets\ValueRange(['values' => $values]);
            $params = ['valueInputOption' => 'RAW'];

            \Log::info('Appending data to Google Sheets.', ['spreadsheetId' => $spreadsheetId, 'range' => $range, 'values' => $values]);
            return $this->service->spreadsheets_values->append($spreadsheetId, $range, $body, $params);
        } catch (\Exception $e) {
            \Log::error('Google Sheets API Append Error: ' . $e->getMessage());
            throw $e;
        }
    }

    public function getSheetData($spreadsheetId, $range)
    {
        try {
            \Log::info('Fetching data from Google Sheets.', ['spreadsheetId' => $spreadsheetId, 'range' => $range]);
            return $this->service->spreadsheets_values->get($spreadsheetId, $range);
        } catch (\Exception $e) {
            \Log::error('Google Sheets API Get Error: ' . $e->getMessage());
            throw $e;
        }
    }

    public function updateRowInSheet($spreadsheetId, $range, $values)
    {
        try {
            $body = new Sheets\ValueRange(['values' => $values]);
            $params = ['valueInputOption' => 'RAW'];

            \Log::info('Updating row in Google Sheets.', ['spreadsheetId' => $spreadsheetId, 'range' => $range, 'values' => $values]);
            return $this->service->spreadsheets_values->update($spreadsheetId, $range, $body, $params);
        } catch (\Exception $e) {
            \Log::error('Google Sheets API Update Error: ' . $e->getMessage());
            throw $e;
        }
    }

    public function clearRowInSheet($spreadsheetId, $range)
    {
        try {
            \Log::info('Clearing row in Google Sheets.', ['spreadsheetId' => $spreadsheetId, 'range' => $range]);
            $this->service->spreadsheets_values->clear($spreadsheetId, $range, new Sheets\ClearValuesRequest());
        } catch (\Exception $e) {
            \Log::error('Google Sheets API Clear Error: ' . $e->getMessage());
            throw $e;
        }
    }
}

```
# 7.2 コントローラー
```php
<?php

namespace App\Http\Controllers;

use Google\Client;
use Google\Service\Sheets;

class GoogleSheetsTestController extends Controller
{
    public function test()
    {
        $client = new Client();
        $client->setApplicationName('Laravel Google Sheets Integration Test');
        $client->setScopes([Sheets::SPREADSHEETS]);
        $client->setAuthConfig(env('GOOGLE_APPLICATION_CREDENTIALS'));

        $service = new Sheets($client);

        // スプレッドシートIDと範囲
        $spreadsheetId = env('GOOGLE_SPREADSHEET_ID'); // .envに設定
        $range = 'シート1!A1';

        // テストデータの書き込み
        $values = [
            ["Column 1", "Column 2", "Column 3"],
            ["Data 1", "Data 2", "Data 3"],
        ];
        $body = new Sheets\ValueRange(['values' => $values]);
        $params = ['valueInputOption' => 'RAW'];

        try {
            $result = $service->spreadsheets_values->append($spreadsheetId, $range, $body, $params);
            return response()->json(['status' => 'success', 'result' => $result]);
        } catch (\Exception $e) {
            return response()->json(['status' => 'error', 'message' => $e->getMessage()]);
        }
    }
}

```

# 7.3 ルーティング
```php
<?php

use App\Http\Controllers\ProfileController;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\DataController;
use App\Http\Controllers\AquariumController;
use Illuminate\Support\Facades\DB;
use App\Http\Controllers\GoogleSheetsTestController;
// GoogleSheetsAPI接続確認ルート
Route::get('/test-google-sheets', function () {
    $googleSheetsService = app(App\Services\GoogleSheetsService::class);
    $spreadsheetId = env('GOOGLE_SPREADSHEET_ID');
    $range = 'シート1!A1'; // 書き込むセル範囲を指定
    $values = [
        ['Test ID', 'Test Data Validation ID', 'Aquarium ID', 'PH', 'Salt', 'DO', 'Water Temp', 'Outside Temp', 'Inside Temp', 'Detail', 'Created At', 'Updated At', 'Deleted At']
    ];

    try {
        $googleSheetsService->appendDataToSheet($spreadsheetId, $range, $values);
        return 'Data appended successfully.';
    } catch (\Exception $e) {
        return 'Error: ' . $e->getMessage();
    }
});
```

このように実装しておけば、'/test-google-sheets'というエンドポイントにアクセスすることで、データがシートに書き込まれるか（API連携できてるか）確認可能