Add Metadata, headers (Expires, CacheControl) to a file uploaded to Amazon S3 using the Laravel 5.0 Storage facade
Asked Answered
U

10

29

I am trying to find out how to add in Metadata or headers (Expires, CacheControl etc.) to a file uploaded using the Laravel 5.0 Storage facade. I have use the page here as reference.

http://laravel.com/docs/5.0/filesystem

The following code works correctly:

Storage::disk('s3')->put('/test.txt', 'test');

After digging I also found that there is a 'visibility' parameter which sets the ACL to 'public-read' so the following also works correctly.

Storage::disk('s3')->put('/test.txt', 'test', 'public');

But I would like to be able to set some other values to the header of the file. I have tried the following:

Storage::disk('s3')->put('/index4.txt', 'test', 'public', array('Expires'=>'Expires, Fri, 30 Oct 1998 14:19:41 GMT'));

Which doesn't work, I have also tried:

Storage::disk('s3')->put('/index4.txt', 'test', array('ACL'=>'public-read'));

But that creates an error where the 'visibility' parameter can not be converted from a string to an array. I have checked the source of AwsS3Adapter and it seems there is code for options but I can not seem to see how to pass them correctly. I think it takes the following:

protected static $metaOptions = [
    'CacheControl',
    'Expires',
    'StorageClass',
    'ServerSideEncryption',
    'Metadata',
    'ACL',
    'ContentType',
    'ContentDisposition',
    'ContentLanguage',
    'ContentEncoding',
];

Any help on how to accomplish this would be appreciated.

Unlovely answered 11/3, 2015 at 14:41 Comment(0)
S
38

First, you need to call getDriver so you can send over an array of options. And then you need to send the options as an array.

So for your example:

Storage::disk('s3')->getDriver()->put('/index4.txt', 'test', [ 'visibility' => 'public', 'Expires' => 'Expires, Fri, 30 Oct 1998 14:19:41 GMT']);

Be aware that if you're setting Cache-Control it has to be passed as CacheControl. This may well be true for other keys with non-alphanumierc characters.

Senate answered 20/5, 2015 at 16:36 Comment(3)
Check out my answer below (https://mcmap.net/q/478523/-add-metadata-headers-expires-cachecontrol-to-a-file-uploaded-to-amazon-s3-using-the-laravel-5-0-storage-facade) if you wish to set global defaults in configHolifield
Thank you for this, everything worked without the getDriver() until I needed to set the 'ContentType' metadata.Lucchesi
Thanks for pointing out the Cache-Control => CacheControl issue. Saved me a bunch of timeBirdseed
H
31

If you want to have global defaults with headers, this works in Laravel 5.4. Change your config/filesystems.php file like so:

s3' => [
    'driver' => 's3',
    'key' => env('AWS_KEY'),
    'secret' => env('AWS_SECRET'),
    'region' => env('AWS_REGION'),
    'bucket' => env('AWS_BUCKET'),
    'options' => ['CacheControl' => 'max-age=315360000, no-transform, public', 
                  'ContentEncoding' => 'gzip']
],
Holifield answered 10/9, 2017 at 21:45 Comment(6)
Why the downvote? This solution works and if you don't believe it works, would like to know why you think so?Holifield
Wow, this should be pretty much the best answer but someone downvoted it for no reasonHolifield
I'm not sure why this was downvoted either, it has worked perfectly for me in Laravel 5.4.Vile
This is a working solution even in Laravel 5.8. Nice, thanks!Cinda
Thanks for this, I'd assume it may have been downvoted because this is a global solution, rather than a per file/upload solution. You may not want to set this at a global level. I didn't when I asked the question.Unlovely
this is a perfect solution for my use case. I chose to organize every upload use case into a separate Laravel Storage Disk, specifying the bucket and root per disk. Not global. But also not per individual file.Senecal
N
10

After attempting the above answers and failing to be able to add customer user-metadata it turns out that after digging through the SDK code it is a bit easier than I thought (Assume $path is a path to an image file). I didn't appear to need to call the getDriver() method either, not too sure if that makes any difference with the current version of the AWS SDK.

Storage::put(
    'image.jpg',
    file_get_contents($path),
    [
        'visibility' => 'public',
        'Metadata' => [
            'thumb' => '320-180',
        ],
    ]
);

So now if you view the newly uploaded file in S3 you will see the custom metadata:

enter image description here

Hope this helps someone.

Nitro answered 22/1, 2018 at 6:42 Comment(0)
A
5

For Laravel 9 users this has became more easy. You do not need to call ->getDriver() anymore. You can directly pass options to the put command.

Storage::disk('s3')->put('/index.txt', 'file content', [
    // S3 Object ACL
    'visibility' => 'public', // or 'private',

    // HTTP Headers
    'CacheControl' => 'public,max-age=315360000',
    'ContentDisposition' => 'attachment; filename="index.txt"',
    'Expires' => 'Thu, 12 Feb 2032 08:24:43 GMT',

    // Metadata or other S3 options
    'MetadataDirective' => 'REPLACE'
    'Metadata' => [
        'Custom-Key' => 'test',
    ],
])

In case you need other headers or options, please checkout the flysystem source code for all available headers and options.

https://github.com/thephpleague/flysystem-aws-s3-v3/blob/master/src/AwsS3Adapter.php#L38

public const AVAILABLE_OPTIONS = [
    'ACL',
    'CacheControl',
    'ContentDisposition',
    'ContentEncoding',
    'ContentLength',
    'ContentType',
    'Expires',
    'GrantFullControl',
    'GrantRead',
    'GrantReadACP',
    'GrantWriteACP',
    'Metadata',
    'MetadataDirective',
    'RequestPayer',
    'SSECustomerAlgorithm',
    'SSECustomerKey',
    'SSECustomerKeyMD5',
    'SSEKMSKeyId',
    'ServerSideEncryption',
    'StorageClass',
    'Tagging',
    'WebsiteRedirectLocation',
];
Ainsley answered 14/2, 2022 at 8:46 Comment(0)
L
3

The answer from @Paras is good. But there is one thing that can confuse newcommers:

'options'     => [
    'Expires' => gmdate('D, d M Y H:i:s GMT', strtotime('+1 month')),
    >>> WRONG visibility' => 'public', WRONG <<<
]

If you want to define global options for the HEADERS, the options array is the right way to go. But if you also want to define the visibility, you can not mix it up. Visibility has to be defined outside of options array.

👍

'visibility'  => 'public',
'options'     => ['Expires' => gmdate('D, d M Y H:i:s GMT', strtotime('+1 month'))]
Lahey answered 22/1, 2018 at 6:9 Comment(0)
D
2

This is an example of how to upload a file to S3 as of Laravel 5.8 with expiry and cache control headers, for example:

Storage::put($directory . '/' . $imageName, 
            $image, [
              'visibility' => 'public',
              'Expires' => gmdate('D, d M Y H:i:s \G\M\T', time() + (60 * 60 * 24 * 7)),
              'CacheControl' => 'max-age=315360000, no-transform, public',
        ]);

Also don't forget to uncheck the 'Disable cache' checkbox in Chrome if you're testing and it never seems to work, that got me bad for an hour when my browser wouldn't cache things even though I finally got the headers right in S3.

Duester answered 1/7, 2019 at 22:40 Comment(0)
P
1

Hey I solved this problem, you need to create a custom S3 filesystem

First, create a new file CustomS3Filesystem.php and save into app/providers, this custom S3 filesystem uses the S3 Adapter, but you can add metadata and headers.

<?php namespace App\Providers;
use Storage;
use League\Flysystem\Filesystem;
use Aws\S3\S3Client;
use League\Flysystem\AwsS3v2\AwsS3Adapter as S3Adapter;
use Illuminate\Support\ServiceProvider;

class CustomS3Filesystem extends ServiceProvider {

public function boot()
{
    Storage::extend('s3_custom', function($app, $config)
    {
        $s3Config = array_only($config, ['key', 'region', 'secret', 'signature', 'base_url']);
        $flysystemConfig = ['mimetype' => 'text/xml'];
        $metadata['cache_control']='max-age=0, no-cache, no-store, must-revalidate';
        return new Filesystem(new S3Adapter(S3Client::factory($s3Config), $config['bucket'], null, ['mimetype' => 'text/xml', 'Metadata' => $metadata]), $flysystemConfig);
    });
}
public function register()
{
    //
}
}

Add provider into providers list at config/app.php

'App\Providers\CustomS3Filesystem',

create new filesistem name in config/filesystems

's3-new' => [
            'driver' => 's3_custom',
            'key'    => 'XXX',
            'secret' => 'XXX',
            'bucket' => 'XXX',
        ],

Use the new created custom s3 adapter

Storage::disk('s3-new')->put(filename, file_get_contents($file), public);

I used laravel documentation to customize the s3 adapter http://laravel.com/docs/5.0/filesystem#custom-filesystems

I hope this may help you.

Permissible answered 15/5, 2015 at 22:48 Comment(0)
P
1

I am using Laravel 4.2, but I think my solution might also help on Laravel 5.0 (cannot say for sure, as I have not tried to upgrade yet). You need to update the meta options in the config for the Flysystem driver that you are using. In my case, I created a connection called s3static to access the bucket where I am storing images that will not be changing.

My config file:

's3static' => [
            'driver'     => 'awss3',
            'key'        => 'my-key',
            'secret'     => 'my-secret',
            'bucket'     => 'my-bucket',
            // 'region'     => 'your-region',
            // 'base_url'   => 'your-url',
            'options'    => array(
                'CacheControl' => 'max_age=2592000'
            ),
            // 'prefix'     => 'your-prefix',
            // 'visibility' => 'public',
            // 'eventable'  => true,
            // 'cache'      => 'foo'
        ],

Now when I put any files on to S3 using this connection, they have the Cache-Control meta data set.

Playbook answered 17/11, 2015 at 4:31 Comment(1)
This isn't documented at all.Wallachia
V
1

To expand on @sergiodebcn 's answer, here is the same CustomS3Filesystem class working for S3 v3 and the latest Laravel. Note I have removed the XML mimetype and set up a 5 day cache time:

namespace App\Providers;

use Illuminate\Support\Arr;
use Storage;
use League\Flysystem\Filesystem;
use Aws\S3\S3Client;
use League\Flysystem\AwsS3v3\AwsS3Adapter as S3Adapter;
use Illuminate\Support\ServiceProvider;

class CustomS3Filesystem extends ServiceProvider
{

    /**
     * Format the given S3 configuration with the default options.
     *
     * @param  array  $config
     * @return array
     */
    protected function formatS3Config(array $config)
    {
        $config += ['version' => 'latest'];

        if ($config['key'] && $config['secret']) {
            $config['credentials'] = Arr::only($config, ['key', 'secret']);
        }

        return $config;
    }

    /**
     * Bootstrap a custom filesystem
     *
     * @return void
     */
    public function boot()
    {
        Storage::extend('s3_custom', function($app, $config)
        {
            $s3Config = $this->formatS3Config($config);
            return new Filesystem(
                new S3Adapter(
                    new S3Client($s3Config),
                    $config['bucket'],
                    null,
                    [
                        'CacheControl'  => 'max-age=432000'
                    ]
                )
            );
        });
    }

    public function register()
    {
        //
    }
}
Venice answered 5/9, 2017 at 1:52 Comment(0)
A
0

Using Laravel 8 here:

I didn't see this mentioned elsewhere, but the metadata option key => values listed by Christoph Kluge

appear to only accept string values, and fail silently if passed an integer, bool, etc... So if you're passing in a variable you'll need to convert to a string value:

            $fileID = $fileData['FileId'];
            $fileExt = $fileData['FileExtension'];
            $fileUnique = $fileData['UniqueFileId'];
            $isImage = $fileData['IsImage'];
            $isDefault = $fileData['IsDefaultImage'];
            $filePath = $fileUnique . "." . $fileExt;
            $file = $mp->fileID($fileID)->get();
            
            if (Storage::disk('s3')->missing('img/' . $filePath)) {
                Storage::disk('s3')->put(
                    'img/' . $filePath, 
                    $file,
                    [
                        // Metadata or other S3 options
                        'MetadataDirective' => 'REPLACE',
                        'Metadata' => [
                            'is-image' => strval($isImage),
                            'is-default' => strval($isDefault),
                            'unique-file-id' => strval($fileUnique),
                            'file-extension' => strval($fileExt),
                        ]
                    ]
                );
                    echo nl2br('uploading file: ' . $filePath . "\n");
            } else {
                echo nl2br('file already exists:' . $filePath . "\n");
            }
Agog answered 17/4, 2022 at 21:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.