Laravelには色んな種類のファイルシステムを操作する便利機能が用意されていて当然その対象にはS3も含まれている。
ちょっとS3バケット間でファイルをコピーしたいと思った。
こんなメソッドシグネチャで考える。
<?php function copyOverBucket($fromDiskName, $fromPath, $toDickName, $toPath);
LaravelのFileSystem(実体は File Storage Abstraction for PHP - Flysystem )には汎用的なファイル操作APIが提供されている。
そこに別のFileSystemインスタンスとの間のファイルコピーはない。
したがって、素直にコピーしようとするとこのようになる。
<?php function copyOverBucket($fromDiskName, $fromPath, $toDickName, $toPath) { Storage::disk($toDiskName)->put($toPath, Storage::disk($fromDiskName)->get($fromPath)); }
そうするとコピー元のS3バケットから一度オブジェクトをダウンロードして、それをコピー先のS3バケットにアップロードする動きになる。
(実際には大して問題にならないレイテンシや転送量のコストでも気分的に) 嫌だ。
バケット間で直接コピーしたい。
Aws S3 (v3) Adapter - Flysystem の実装も参考になりそうだ。
とりあえず動きはした。
Aws S3 (v3) Adapterはしっかり実装が隠蔽されていて無理やりな感じになってしまった。
汎用的なファイル操作APIとS3固有のAPIでの操作を混在させるのは筋が悪いかもしれない。参考程度に見てください。
<?php function copyOverBucket($fromDiskName, $fromPath, $toDickName, $toPath) { // AwsS3V3Adapter がプレフィクサを公開していないので実装に合わせて処理する $fromDisk = Storage::disk($fromDiskName); $fromPrefixer = new PathPrefixer($fromDisk->getConfig()['path'] ?? ''); $toDisk = Storage::disk($toDiskName); $toPrefixer = new PathPrefixer($toDisk ->getConfig()['path'] ?? ''); // S3ディスクの実体はちょっと拡張されたラッパーなのでS3のクライアントを取得できる $toDisk->getClient()->copyObject([ 'Bucket' => $toDisk->getConfig()['bucket'], 'Key' => $toPrefixer->prefixPath($toPath), 'CopySource' => "{$fromDisk->getConfig()['bucket']}/{$fromPrefixer->prefixPath($fromPath)}", ]); }
S3ディスクの実体: AwsS3V3Adapterでラップされている。
他方、S3のバケット名などを取得するインタフェースはない*1。そのため各ディスクの設定を参照することになる。設定なのでそうそう変わるものではないだろうが、内部構造や実装が変化したら容易に破損するだろうからあまりいい作りではないように思える*2。
とは言え、目的は達成できたのでまぁ良かった。
そんな感じ。
*1:v1の頃はできた模様 https://github.com/thephpleague/flysystem-aws-s3-v3/blob/4e25cc0582a36a786c31115e419c6e40498f6972/src/AwsS3Adapter.php#L91-L99
*2:抽象化されたファイル操作を行えるのが利点の機能を完全に否定しているし、ローカルとクラウドでファイルの保存先を変えるようなこともできなくなる。