ファイルアップロード時の公開(パーミッション)設定

ファイルをローカルディスクにアップロードする際の公開設定について調べてみました。
アップロードしたファイルだけでなく、ディレクトリのパーミッションが意図した値になっているか注意が必要です。

調べた時のバージョンは下記のとおりです。
・PHP:8.3.7
・laravel/framework:v11.7.0

困ったこと

下記の公式のドキュメントにあるstorePublicly を利用して保存をしたところ、ディレクトリのパーミッションが意図しない値(private相当の0600)になってしまいました。

何が起きた?

設定の状態は下記の状態。
config/filesystems.php:手を加えずデフォルトのまま
.envFILESYSTEM_DISK=localの状態です

storePublicly関数でファイルを保存したところ、アップロードされたファイルのパーミッションはちゃんと0644になっていたのですが、filesディレクトリのパーミッションが0600になってしまいました。
ディレクトリのパーミッションは0755を期待していました。

Controller
$request->file("file")->storePublicly('public/files');

どうしたらよかった?

storePublicly関数ではなく、store関数を利用して第2引数にdiskの設定(public)を渡すことで、ディレクトリのパーミッションは0755になります。

Controller
// 同じ結果になります
$request->file("file")->store('files', 'public');
$request->file("file")->store('files', ['disk' => 'public']);

処理を追ったところ、Illuminate\Filesystem\FilesystemManager:resolve()の時にconfig('filesystems.disks.public')が取得されます。
この設定からファイル操作のドライバーに'visibility' => 'public'が渡されることで、ディレクトリのパーミッションがpublic相当の0755となります。

storePublicly は何をしている?

storePublicly関数はアップロードしたファイル自身のみのパーミッションをpublicにするだけであり、ディレクトリのパーミッションはprivate相当の0600で生成されます。

storePublicly関数の実装を見ると$options['visibility'] = 'public';しています。
なんだか、storePublicly関数でもイケそうに見えますね。(でもダメなんですけどね)

UploadedFile.php
public function storePublicly($path = '', $options = [])
{
    $options = $this->parseOptions($options);

    $options['visibility'] = 'public';

    return $this->storeAs($path, $this->hashName(), $options);
}

参考:https://github.com/laravel/framework/blob/v11.7.0/src/Illuminate/Http/UploadedFile.php#L46-L53

処理を追ってみたところstorePublicly関数の$options['visibility'] = 'public';diskの値には関係せず、config('filesystems.disks.local')の設定が取得されます。

最終的に、FlysystemライブラリのLeague\Flysystem\Local\LocalFilesystemAdapter:writeToFile()に渡ります。
参考:https://github.com/thephpleague/flysystem-local/blob/3.25.1/LocalFilesystemAdapter.php#L118-L135
$config->get(Config::OPTION_VISIBILITY)のところで$options['visibility'] = 'public';が利用され、ファイルのパーミッションがpublic相当の0755になります。

ディレクトリのパーミッションはどうするかというと、$config->get(Config::OPTION_DIRECTORY_VISIBILITY)の部分です。
Config::OPTION_DIRECTORY_VISIBILITY'directory_visibility'なので、$optionsに設定したら行けそうですね。

まとめると、disklocalの状態でファイルアップロードをするが、ファイルとディレクトリのパーミッションをpublicにしたい場合(そんなことある?)は下記のようにできます。

Controller
$request->file("file")->store('public/files', ['visibility' => 'public', 'directory_visibility' => 'public']);