Laravelのファイルストレージを利用してCloudStorageを扱えるようにしてみます。Laravelに統合するにあたってゼロからつくるのではなくすでにパッケージが有志の方によって作られているのでこれを利用したいと思います。
導入
READMEに書かれている通りcomposeをつかってインストールします。インストールした後は、.envとfilesystem.phpに必要な情報を追加してください。
エラーを回避する
バケットのアクセスコントロールが「均一」になっているとlaravel-google-cloud-storageを使ってオブジェクトをアップロードすると失敗します。
Cannot insert legacy ACL for an object when uniform bucket-level access is enabled
おそらく原因はgithubのissueのコメントに書かれていることなのではないかと思います。
https://github.com/Superbalist/laravel-google-cloud-storage/issues/80#issuecomment-616557477
なので、このままではバケットのアクセスコントロールが「均一」になっているとこのファイルシステムでのアプロード機能が使えなくなるので、少し手を加えます。
ServiceProviderを作成する
エラーが起きる原因は、laravel-google-cloud-storageがアダプターとしてflysystem-google-cloud-storageを利用していることにあります。エラーを回避する方法がプルリクエストで提案されています。要は、GoogleStorageAdapterクラスのgetOptionsFromConfigメソッドをいじりたいので、GoogleStorageAdapterクラスを拡張したクラスを作成して、getOptionsFromConfigメソッドをオーバーライドしてアクセスコントロールが「均一」のバケットにも対応できるように処理を修正して、このクラス自身を返すというものです。これをLaravelのカスタムファイルシステムで実現させるために、独自のサービスクラスを作ります。作成したサービスプロバイダーの中身は下記になります。
<?php
namespace App\Providers;
use Google\Cloud\Storage\StorageClient;
use Illuminate\Support\Arr;
use Superbalist\LaravelGoogleCloudStorage\GoogleCloudStorageServiceProvider as GCSProvider;
use Superbalist\Flysystem\GoogleStorage\GoogleStorageAdapter;
class GoogleCloudStorageServiceProvider extends GCSProvider
{
/**
* Create a new StorageClient
*
* @param mixed $config
* @return \Google\Cloud\Storage\StorageClient
*/
private function createClient($config)
{
$keyFile = Arr::get($config, 'key_file');
if (is_string($keyFile)) {
return new StorageClient([
'projectId' => $config['project_id'],
'keyFilePath' => $keyFile,
]);
}
if (! is_array($keyFile)) {
$keyFile = [];
}
return new StorageClient([
'projectId' => $config['project_id'],
'keyFile' => array_merge(["project_id" => $config['project_id']], $keyFile)
]);
}
public function boot()
{
$factory = $this->app->make('filesystem');
/* @var FilesystemManager $factory */
$factory->extend('gcs', function ($app, $config) {
$storageClient = $this->createClient($config);
$bucket = $storageClient->bucket($config['bucket']);
$pathPrefix = Arr::get($config, 'path_prefix');
$storageApiUri = Arr::get($config, 'storage_api_uri');
$adapter = $this->resolveAdapter($storageClient, $bucket, $pathPrefix, $storageApiUri);
return $this->createFilesystem($adapter, $config);
});
}
public function resolveAdapter($storageClient, $bucket, $pathPrefix = null, $storageApiUri = null)
{
return new class ($storageClient, $bucket, $pathPrefix, $storageApiUri) extends GoogleStorageAdapter {
protected function getOptionsFromConfig(\League\Flysystem\Config $config)
{
$options = [];
if (empty($this->bucket->info()['iamConfiguration']['uniformBucketLevelAccess']['enabled'])) {
if ($visibility = $config->get('visibility')) {
$options['predefinedAcl'] = $this->getPredefinedAclForVisibility($visibility);
} else {
$options['predefinedAcl'] = $this->getPredefinedAclForVisibility(AdapterInterface::VISIBILITY_PRIVATE);
}
}
if ($metadata = $config->get('metadata')) {
$options['metadata'] = $metadata;
}
return $options;
}
};
}
}
createFilesystemに渡す$adapterに、GoogleStorageAdapterを拡張したクラスを渡しています。またこのサービスクラスもlaravel-google-cloud-storageのGoogleCloudStorageServiceProviderを拡張しています。
作成したサービスプロバイダーをapp.phpに登録します。
...
'providers' => [
...
App\Providers\GoogleCloudStorageServiceProvider::class,
],
...
必要な作業は終わりました。これでアクセスコントロールが「均一」のバケットに対してもオブジェクトがアップロードできるようになります。
public function upload(Request $request)
{
$file = $request->file('test');
$disk = Storage::disk('gcs');
$disk->put('test.txt', file_get_contents($file));
}
Laravelでカスタムファイルシステムを作成する方法については、下記の記事で解説しているのでぜひ参考にしてみてください。