20

I am trying to build a file management system in Laravel based on league/flysystem: https://github.com/thephpleague/flysystem

I am using the S3 adapter and I have it working to save the uploaded files using:

$filesystem->write('filename.txt', 'contents');

Now I am stuck on generating the download file URL when using the S3 adapter.

The files are saved correctly in the S3 bucket, I have permissions to access them, I just don't know how to get to the S3 getObjectUrl method through the league/flysystem package.

I have tried:

$contents = $filesystem->read('filename.txt');

but that returns the content of the file.

$contents = $filemanager->listContents();

or

$paths = $filemanager->listPaths();

but they give me the relative paths to my files.

What I need is something like "ht...//[s3-region].amazonaws.com/[bucket]/[dir]/[file]..."

Ioana Cucuruzan
  • 845
  • 1
  • 8
  • 21

7 Answers7

34

I am using Laravel 5.2 and the code below seemed to work fine.

Storage::cloud()->url('filename');
i906
  • 1,677
  • 2
  • 16
  • 20
16

I'm not sure what the correct way of doing this is with Flysystem, but the underlying S3Client object has a method for doing that. You could do $filesystem->getAdapter()->getClient()->getObjectUrl($bucket, $key);. Of course, building the URL is as trivial as you described, so you don't really need a special method to do it.

Jeremy Lindblom
  • 6,437
  • 27
  • 30
  • 1
    Thank you :) can't believe how close I was. Spent hours on this. – Ioana Cucuruzan Aug 15 '14 at 19:00
  • 7
    This only works if your S3 files are publicly accessible – andrewtweber Aug 19 '15 at 05:20
  • 5
    @andrewtweber Incorrect. If I make the assets private on S3 and I grant viewing permissions to a certain user or group on S3, and then I use the API Token for that user/group that was granted permission in my web app, then the web app can access the files, even though they are private to everyone else. Alternatively, I could lock down all of the assets and enable my domain to be able to view the assets via the CORS settings in S3. – Mike McLin Aug 19 '15 at 23:24
13

When updating to Laravel 5.1 this method no longer supported by the adapter. No in your config you must have the S3_REGION set or you will get a invalid hostname error and secondly I had to use the command as input to create the presignedRequest.

    public function getFilePathAttribute($value)
{

    $disk = Storage::disk('s3');
    if ($disk->exists($value)) {
        $command = $disk->getDriver()->getAdapter()->getClient()->getCommand('GetObject', [
            'Bucket'                     => Config::get('filesystems.disks.s3.bucket'),
            'Key'                        => $value,
            'ResponseContentDisposition' => 'attachment;'
        ]);

        $request = $disk->getDriver()->getAdapter()->getClient()->createPresignedRequest($command, '+5 minutes');

        return (string) $request->getUri();
    }

    return $value;
}
Richard McLain
  • 131
  • 1
  • 2
  • 1
    @andrewtweber - This example shows how to create a presigned URL request (which is not a feature supported by Flysystem). However, Flysystem gives you access to the client libraries that it uses, so ANYTHING that the client library can do, you can do. So, when he is calling `getClient()` he is actually getting the `Aws\S3\S3Client` object. Now he is just using the `createPresignedRequest()` method from the Amazon library. So, actually it is really cool that you are able to get the low-level libraries that Flysystem is built on. – Mike McLin Aug 16 '15 at 22:58
  • I am using a Lumen 5.1 install, and the accepted `getObjectUrl()` answer still works. Not sure what issues you were running into. It has nothing to do with Laravel anyways. As I explained to Andrew above, you are just using the Amazon S3 library. Laravel really has nothing to do with it once you dig down that deep and are using the adapter directly. – Mike McLin Aug 16 '15 at 23:00
  • @MikeMcLin `getObjectUrl` does not return a signed request. Unless all of your S3 files are public, which is probably not a good idea, you have to create a presigned request – andrewtweber Aug 19 '15 at 05:19
  • @andrewtweber - The user didn't ask for a signed URL. In fact, at the end of the question, the user specifically stated the format that she wanted for the URL (which was not a signed URL format). Also, I disagree with your assessment that the pre-signed mechanism is the only way to access private assets on S3. You can also grant certain Users/Groups/Apps access (via API tokens), and you can enable assets for a specific domain using CORS. All while keeping your assets private to everyone else. – Mike McLin Aug 19 '15 at 23:20
  • @MikeMcLin I never said the user asked for a signed URL, it's just a comment informing other people who may find this question. – andrewtweber Aug 20 '15 at 15:01
  • I'm new to S3, what's key and what's attachment and what value should I pass to the getFilePathAttribute() function? Is key the secret key of my AWS user? – peke_peke Mar 02 '16 at 18:14
  • its returning pre-signed url. but talking so much time for list of images. – romal tandel Apr 10 '20 at 05:41
8

Maybe I'm a little late to this question, but here's a way to use Laravel 5's built-in Filesystem.

I created a Manager class that extends Laravel's FilesystemManager to handle the public url retrieval:

class FilesystemPublicUrlManager extends FilesystemManager
{

    public function publicUrl($name = null, $object_path = '')
    {
        $name = $name ?: $this->getDefaultDriver();
        $config = $this->getConfig($name);

        return $this->{'get' . ucfirst($config['driver']) . 'PublicUrl'}($config, $object_path);
    }

    public function getLocalPublicUrl($config, $object_path = '')
    {
        return URL::to('/public') . $object_path;
    }

    public function getS3PublicUrl($config, $object_path = '')
    {
        $config += ['version' => 'latest'];

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

        return (new S3Client($config))->getObjectUrl($config['bucket'], $object_path);
    }
}

Then, I added this class to the AppServiceProvider under the register method so it has access to the current app instance:

$this->app->singleton('filesystemPublicUrl', function () {
    return new FilesystemPublicUrlManager($this->app);
});

Finally, for easy static access, I created a Facade class:

use Illuminate\Support\Facades\Facade;

class StorageUrl extends Facade
{

    /**
     * Get the registered name of the component.
     *
     * @return string
     */
    protected static function getFacadeAccessor()
    {
        return 'filesystemPublicUrl';
    }
}

Now, I can easily get the public url for my public objects on my local and s3 filesystems (note that I didn't add anything for ftp or rackspace in the FilesystemPublicUrlManager):

$s3Url = StorageUrl::publicUrl('s3') //using the s3 driver
$localUrl = StorageUrl::publicUrl('local') //using the local driver
$defaultUrl = StorageUrl::publicUrl() //default driver
$objectUrl = StorageUrl::publicUrl('s3', '/path/to/object');
Kevin Lee
  • 1,171
  • 12
  • 30
3

Another form of Storage::cloud():

    /** @var FilesystemAdapter $disk */
    $s3 = Storage::disk('s3');
    return $s3->url($path);
Greg
  • 12,119
  • 5
  • 32
  • 34
2

Using presigned request S3:

public function getFileUrl($key) {
        $s3 = Storage::disk('s3');
        $client = $s3->getDriver()->getAdapter()->getClient();
        $bucket = env('AWS_BUCKET');

        $command = $client->getCommand('GetObject', [
            'Bucket' => $bucket,
            'Key' => $key
        ]);

        $request = $client->createPresignedRequest($command, '+20 minutes');

        return (string) $request->getUri();
    }
2

For private cloud use this

Storage::disk('s3')->temporaryUrl($path);
Dave Runner
  • 226
  • 1
  • 5