13
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Laravel で S3 にファイルを上げるタイミングで公開権限を設定する

Last updated at Posted at 2020-01-16

はじめに

Laravel のファイルストレージでは AWS の S3 を指定して保存することができます。
S3を指定する方法は公式ドキュメントを参照してもらうとして、保存は下記のように store メソッドを利用します。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

class UserAvatarController extends Controller
{
    public function update(Request $request)
    {
        $path = $request->file('avatar')->store('avatars');

        return $path;
    }
}

重要なポイント

保存をするとき、上記の例では Storage ファサードを使っていません。
もちろんファサードを使って保存することも可能で、

$path = Storage::putFile('avatars', $request->file('avatar'));

このように公式ドキュメントには書いてあります。
で、やりたいのはドキュメントに ファイル視認性 と書かれていますが、

$visibility = Storage::getVisibility('file.jpg');
Storage::setVisibility('file.jpg', 'public')

ファイルが既に保存されている場合、上記の設定でズバッと設定をしてしまいたい。デフォルトで private になっているところを公開の public にしたい場合はどうしたら良いのか、です。
つまり store の段階で最初からできてくれよってことですね。

で、例の如く...

ないんですよね、ドキュメント上に正解が。

ヒントはあって、第二引数で他のディスク(この例だとローカルストレージを利用中)に保存したい場合などは下記のように書かれています。

$path = $request->file('avatar')->store(
    'avatars/'.$request->user()->id, 's3'
);

となると、もしかすると第3や第4の引数でなにかあるかもしれない。
ということでコアを追いかけることになります。

この場合の $requestIlluminate\Http\Requestです。
追跡していくと、

Illuminate\Http\Request
 ↓
Illuminate\Http\Concerns\InteractsWithInput
 ↓
Illuminate\Http\UploadedFile

と、辿れます。どうやって辿るかは正直、勘です。
エスパー並の感度で遡った結果、

store メソッドを見つけました。

Illuminate\Http\UploadedFile
    public function store($path, $options = [])
    {
        return $this->storeAs($path, $this->hashName(), $this->parseOptions($options));
    }

    public function storeAs($path, $name, $options = [])
    {
        $options = $this->parseOptions($options);

        $disk = Arr::pull($options, 'disk');

        return Container::getInstance()->make(FilesystemFactory::class)->disk($disk)->putFileAs(
            $path, $this, $name, $options
        );
    }

    protected function parseOptions($options)
    {
        if (is_string($options)) {
            $options = ['disk' => $options];
        }

        return $options;
    }

parseOptions を見ると、文字列を与えると自動的に配列で disk の key/val 型になるように整形して storeAs に渡しているのがみて取れますね。

で、最終的には putFileAs で保存しているのが分かるわけですが、FilesystemFactory でいよいよ Factory の文字が見えてまいりました。

これの実体は Illuminate\Contracts\Filesystem\Factory です。
こいつは interface なので、基底にもつクラスがいるはずで、それは何かというと Illuminate\Filesystem\FilesystemAdapter になります。
このなかに putFileAs のメソッドも登場してくるわけですが、

Illuminate\Filesystem\FilesystemAdapter
    public function putFileAs($path, $file, $name, $options = [])
    {
        $stream = fopen($file->getRealPath(), 'r');

        $result = $this->put(
            $path = trim($path.'/'.$name, '/'), $stream, $options
        );

        if (is_resource($stream)) {
            fclose($stream);
        }

        return $result ? $path : false;
    }

    public function put($path, $contents, $options = [])
    {
        $options = is_string($options)
                     ? ['visibility' => $options]
                     : (array) $options;

        if ($contents instanceof File ||
            $contents instanceof UploadedFile) {
            return $this->putFile($path, $contents, $options);
        }

        return is_resource($contents)
                ? $this->driver->putStream($path, $contents, $options)
                : $this->driver->put($path, $contents, $options);
    }

putFileAs は put への橋渡しをしていますね。
で、put のところで visibility が登場しており、この辺りがかなりそれっぽい。
で、最終的に $this->driver で保存してそうだということが return からわかります。

ここで使われている drive は当然ながら s3 のはずなので、driver を探します。
このファイルの先頭付近で

use League\Flysystem\AwsS3v3\AwsS3Adapter;

と書かれているので、この辺りがとても怪しい。
本体は Laravel のコアから外れて /vendor/league の下までいくとようやく出てきます。
このクラスを眺めていると、ようやく AWS SDK のオプションが出てきました。

この辺は AWS SDK for PHP のドキュメントを見ると、ファイルアップロード関数の putObject と一致しているのがわかります。

League\Flysystem\AwsS3v3\AwsS3Adapter

    protected static $metaOptions = [
        'ACL',
        'CacheControl',
        'ContentDisposition',
        'ContentEncoding',
        'ContentLength',
        'ContentType',
        'Expires',
        'GrantFullControl',
        'GrantRead',
        'GrantReadACP',
        'GrantWriteACP',
        'Metadata',
        'RequestPayer',
        'SSECustomerAlgorithm',
        'SSECustomerKey',
        'SSECustomerKeyMD5',
        'SSEKMSKeyId',
        'ServerSideEncryption',
        'StorageClass',
        'Tagging',
        'WebsiteRedirectLocation',
    ];

この場合、ACL が今回の対象になるポイントで、Laravelがファイル視認性とか言ってますが、最終的に渡したいのは
private|public-read|public-read-write|authenticated-read|aws-exec-read|bucket-owner-read|bucket-owner-full-control のどれかをこのアダプターの ACL というキーに突っ込みたいわけです。
というわけで、それっぽい関数をみてみると、

League\Flysystem\AwsS3v3\AwsS3Adapter

    protected function upload($path, $body, Config $config)
    {
        $key = $this->applyPathPrefix($path);
        $options = $this->getOptionsFromConfig($config);
        $acl = array_key_exists('ACL', $options) ? $options['ACL'] : 'private';

        if (!$this->isOnlyDir($path)) {
            if ( ! isset($options['ContentType'])) {
                $options['ContentType'] = Util::guessMimeType($path, $body);
            }

            if ( ! isset($options['ContentLength'])) {
                $options['ContentLength'] = is_resource($body) ? Util::getStreamSize($body) : Util::contentSize($body);
            }

            if ($options['ContentLength'] === null) {
                unset($options['ContentLength']);
            }
        }

        try {
            $this->s3Client->upload($this->bucket, $key, $body, $acl, ['params' => $options]);
        } catch (S3MultipartUploadException $multipartUploadException) {
            return false;
        }

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

upload のメソッドに $acl = array_key_exists('ACL', $options) ? $options['ACL'] : 'private';が出てきますね。
ACLという配列キーが出てこなければ private にせよ、と書いてあるので逆に ACL のキーで渡せば良いことがわかります。

というわけで解決方法

長くなりましたが $options に配列で渡していけば良いことがわかりました。

$path = $request->file('avatar')->store('avatars', ['disk' => 's3', 'ACL' => 'public-read']);

ということで、このように第二引数に配列で渡しましょう。その際に disk のキーを渡すのもお忘れなく。

13
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?