Laravel + GCS でstorage管理していたファイルをCGSで管理するようにした時の備忘録

# はじめに
この記事ではLaravelでファイルアップロードしたファイルをGCSで管理した時の備忘録です

# やり方

## 事前準備:GCPのプロジェクト・バケットの作成

以下の記事を参考にしました🙇‍♂️

https://blog.capilano-fw.com/?p=3359

## 作成した`Storage`に以下の権限を付与しておく

![スクリーンショット 2022-01-13 16.31.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555632/73b6b849-f3d6-18be-9312-39705b6fe402.png)


## `.env`に登録したバケット名を入力する

```text:.env
GCS_BUCKET_NAME="hoge-bucket"
```

## `.env`の値を`app.php`から参照する

```php:src/laravel/config/app.php
'gcp_bucket_name' => env('GCS_BUCKET_NAME', 'hoge-bucket'),
```

## GCSを操作するパッケージをインストール

```bash
composer require google/cloud-storage
```

https://packagist.org/packages/google/cloud-storage

https://googleapis.github.io/google-cloud-php/#/

## google/cloud-storageをインスタンス化する

- 事前準備で作成したバケットの認証キーを`storage`直下のディレクトリに移動します


`Config('app.gcp_bucket_name');`:`app.php`からバケット名を参照します

`$url = file_get_contents()`:事前準備で作成したキーファイルを参照します

`new StorageClient([バケット名,サービスアカウントのキーファイル])`:

`$client->bucket();`:バケットを生成します

```php:src/laravel/app/Services/Utilities/UtilityService.php
class UtilityService
{

    public $bucket;
    public $bucketOptions;

    public function __construct()
    {
        $projectId = Config('app.gcp_bucket_name');
        $url = file_get_contents(storage_path('app/local-service-account.json', true));

        $client = new StorageClient([
            'projectId' => $projectId,
            'keyFile' => json_decode($url, true)
        ]);
        $this->bucket = $client->bucket($projectId);
        $this->bucketOptions = [
            'resumable' => true, // アップロードを再開可能に行うか
            'name' => '',
            'metadata' => [
                // 日本語化
                'contentLanguage' => 'ja'
            ]
        ];
    }

    /**
     * 仮登録GCS保存名を作成
     *
     * @param UploadedFile $file
     * @param string $dirName
     * @param string $uniqueKey
     * @param array $options
     * @return array
     */
    public function setBucketOptions(UploadedFile $file, string $dirName, string $uniqueKey, array $options): array {
        $extension = $file->extension();
        $fileName = "{$uniqueKey}_{$dirName}.{$extension}";

        // 保存ディレクトリ生成
        $options['name'] = "tmp/images/{$uniqueKey}/{$dirName}/{$fileName}";
        return $options;
    }
}
```

## 送信時にGCSに一時保存する

`tmp/images/一意な値`に保存するようにします

```php:src/laravel/app/Services/SkillSheet/SkillSheetService.php
protected UtilityService $utilityService;

public function __construct()
{
    $this->utilityService = new UtilityService();
}
/**
 * GCSへの仮保存
 *
 * @param StoreSkillSheetRequest $request
 * @param string $uniqueKey
 * @return array
 */
public function tmpStorageToGcsBucket(StoreSkillSheetRequest $request, string $uniqueKey): array
{
    // バケットを生成
    $bucket = $this->utilityService->bucket;

    // 登録した保存名を保持していく
    $saveNames = [];

    // 写真・動画・その他画像を１件ずつ振り分けする
    foreach ($request->file() as $key => $files) {
        foreach ($files as $file) {
            $options = $this->utilityService->setBucketOptions(
                $file,
                $key,
                $uniqueKey,
                $this->utilityService->bucketOptions
            );

            $saveNames[$key] = $options['name'];

            $bucket->upload(
                fopen($file, 'rb'),
                $options
            );
        }
    }

    return $saveNames;
}
```

## GCSに一時保存した画像を正式に保存する場所に移動します

`images/一意な値・ユーザーID`に保存するようにします
セッションに一時保存したファイル名を保持している状態と仮定します

```php:
/**
 * GCS保存
 *
 * @param integer $id
 * @param SkillSheet $target
 * @param array $updSkillSheet
 * @return array
 */
public
function storageToGcsBucket(int $id, SkillSheet $target, array $updSkillSheet): array
{
    // セッションを破棄する
    $saveNames = Session('saveNames');
    session()->forget('saveNames');

    // バケットイニシャライズ
    $bucket = $this->utilityService->bucket;

    // 仮登録したデータをコピーして移動させてから削除する
    foreach ($saveNames as $key => $v) {
        // 仮登録したオブジェクトがバケットに存在する場合は移管する
        $targetObject = $bucket->object($v);
        $extension = pathinfo($v)['extension'];
        $name = "skill_sheet/{$id}/{$key}/{$id}_{$key}.{$extension}";

        // オブジェクトが存在する場合は仮置きからコピーして移動する・仮置きは削除する
        if ($targetObject->exists()) {
            $targetObject->copy($bucket, ['name' => $name]);
            $targetObject->delete();
        }

        // 登録データが既にある場合は差し替える(更新)
        if ($target->pr_image_path) {
            // 差し替え前のオブジェクトを取得
            $beforeObject = $bucket->object($target->pr_image_path);

            // 差し替え前のオブジェクトがあれば削除
            if ($beforeObject->exists()) {
                $beforeObject->delete();
            } else {
                // 差し替え前のオブジェクトがないのにDBに登録されている場合はリセットする
                $updSkillSheet['pr_image_path'] = null;
            }
        }
        $updSkillSheet['pr_image_path'] = $name;
    }

    return $updSkillSheet;
}
```

## 移動先にファイルの実体が移動されます

![スクリーンショット 2022-01-02 21.31.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555632/bd655655-e750-72c4-5327-0fe7d03535b8.png)