Framework: Get local file path with Storage

Created on 18 May 2016  路  46Comments  路  Source: laravel/framework

Feature request:
The way Storage::disk('local')->url('filename.png') actually returns the path to the file, but it lacks the folder that was listed in the "/config/filesystem.php": eg "app" folder:
'local' => [
'driver' => 'local',
'root' => storage_path('app'),
],
As a result, we get the path: "/storage/filename.png", instead of "/storage/app/filename.png"

This is necessary when we need to work with the uploaded files by the local path. For example using ExcelFileReader

I made a small method for these reasons,
https://github.com/laravel/framework/pull/13605

Most helpful comment

Help us @GrahamCampbell, you're our only hope.

Help Us Graham

All 46 comments

This should already work. Make sure you clear the config cache.

Its not working, im still getting path without 'app' folder.
I have clear the config cache by commands:

php artisan cache:clear
php artisan config:clear

Also i have update Laravel to

laravel/framework (v5.2.32)

But method Storage::disk('local')->url($fileName) returns that:

screenshot

I have same issue

Same here. The Storage::disk('diskName')->url($filename) ignores the 'root' attribute of the filesystems.php configuration. Is there anyway to solve this?

@jfvoliveira No. For local driver it is hardcoded only as 'storage' prefix and it's all. You should make symlink in public folder to storage/app/public.

For reference the functionality appears to be that any path passed into the url function then has either the S3 path or local hardcoded ./storage path appended to it as seen here - It would make sence to me to use the root path rather than a hard coded one as this would obviously offer better flexability and its allready waiting in a configuration file to be used?

If we are agreeing to always prepend the storage prefix, would it make sense to put the same prefix logic in the put method?

My issue is that I registered my driver to store to public/uploads/, so nothing is ever uploaded to my storage directory. It's not obvious that all local drivers are meant to use the storage directory, so devs will be wasting time troubleshooting the url method (as I did!). Putting the same prefix in put method at least bypasses the consistency issue.

Or am I misunderstanding the situation?

I fought with the same situation for some hours!

Added new disk for excel templates:

'templates' => [
    'driver' => 'local',
    'root' => storage_path('templates'),
],

Put main.xlsx into this folder.
After that this code:

Storage::disk('templates')->url('main.xlsx')

returns me /storage/main.xlsx. :flushed:

Some time later I found this question on SO:
http://stackoverflow.com/questions/28964412/how-to-get-file-path-using-storage-facade-in-laravel-5

Now I'm using this method that returns full path to file:

$file = Storage::disk('templates')->getDriver()->getAdapter()->applyPathPrefix('main.xlsx');

Does it change in next Laravel versions?

p.s. Also this thing works and no need to change filesystems.php:

storage_path('templates/main.xlsx');

Method url() is still not usable for the files are placed in subdirectories

Same issue here - I don't understand why this was closed so quickly. The hack that I used to get around this was to leave the root directory blank and then just pass it in (as it relates to /storage) as the path argument in whatever storage method you're using.

Also having this issue resulting in hacky workarounds.

@GrahamCampbell can we reopen this issue?

Help us @GrahamCampbell, you're our only hope.

Help Us Graham

Could someone clarify the issue here; which Laravel version and what is the expected output? The following works for me in 5.3

'my-disk' => [
    'driver' => 'local',
    'root'   => storage_path(),
    'url'    => '/my-url'
],


\Storage::disk('my-disk')->url('path/filename.ext')

returns"/my-url/path/filename.ext"

Have you tried modifying the 'root' config?

'root'   => storage_path('app'),

Check if 'app' is present in the return url, it seems this is the main issue here...

Had this as an issue as well.

        'application' => [
            'driver'     => 'local',
            'root'       => storage_path('app/'),
            'visibility' => 'public'
        ],

Also had to do the getDriver()->getAdapter() work around like so:

        $this->disk = $filesystem->disk('application');
        $this->disk_base_path = $this->disk->getDriver()->getAdapter()->getPathPrefix();

@fernandobandeira I havn't tried anything; I have no idea what the actual issue is [anymore]. It was opened a long time ago, and mentioned the url() method and that it didn't return correct paths. There has been changes to how this is done in 5.3; and I have no idea what is requested anymore. When you tell me to check if app is present in the result; what is the expected result? In my view it shouldn't be there; it's a configuration for the disk.

There seems to be a mix of talk about local paths and the use of the url() method, but the url() method isn't meant to provide local paths. They just happen to provide similarly looking strings.

Are we talking about getting the physical path that the file ended up on local disk? There is no such helper since not all disks have this information. The paths provided to the storage, and used when reading, is opaque strings that is local to the storage. The proper way would be to get() the file-content and parse it that way, or write it to a temporary file if your code requires an existing local file. That's the only way you can be coding against the filesystem/disk facades and have them swappable so you can change your local storage to a S3 storage without having to rewrite file access stuff.

In your config file when you specify a root directory within storage:

'root' => storage_path('app')

using the url method you should get a path return like:

/storage/app/myfile.txt

but instead you get:

/storage/myfile.txt

The url method should return a valid path to the file that was passed in, but the returned path drops the 'root' path that was specified in the filesystem config file

@johnvoncolln No, you shouldn't. The method is named "url", it is a simple url-builder that allows you to prefix your url with anything you want, like a cdn root. It happen to work out-of-the-box with the default configuration of a symlinked local disk, but that is a convenience thing.

Now, you could argue that the issue is that the result is prefixed with "/storage/", I do not know why that is done. It looks odd. But then the issue is why that string is used as a prefix, not why it doesn't include the app (from your example).

The url() method gives you _urls_ that should be browsable to by the user; not physical paths in your local filesystem. This becomes obvious if you use another storage, like S3.

Reference: https://github.com/laravel/framework/blob/d1a08426bc667d336c2398754aeedf49d7cfaa81/src/Illuminate/Filesystem/FilesystemAdapter.php#L330

@sisve Yeah, it's totally written like that, but the way that I interpret the Laravel Documentation is that you are able to use any filesystem you want, whether it be local or S3, and still have access to all the available methods. If we can't use the url method on local files, then maybe it just shouldn't be available when the driver is set to local.

You can use the url method on the local disk, but it requires you to symlink the storage folder into the public folder using the storage:link command, and configure the base url where you've exposed it.

Remember, if you are using the local driver, all files that should be publicly accessible should be placed in the storage/app/public directory. Furthermore, you should create a symbolic link at public/storage which points to the storage/app/public directory.

Source: https://laravel.com/docs/5.3/filesystem#file-urls

Of course, these built-in things will only work if you use the default disk. Creating your own requires you to do some manual symlinking and configuration.

You are incorrect - you can use the url method on the _public_ disk with the symlink setup as you described, but not the _local_ disk with a 'root' path given. That is the issue. Most of the people in here are attempting to retrieve the local disk path with the url method. It returns an incorrect path.

The common assumption is by providing a 'root' directory for the _local_ filesystem in the config, you should probably get returned a path that includes that 'root' directory - but the url method just returns a path to the base storage directory suffixed with the filename.

Its correct actually since url() is meant to access public files on public/storage resulting in YOUR_URL+/storage/file...

The real issue is that there's no way to access a disk root path, you have to access the root storage path with storage_path() and then pass the root folder together with the file,

I think we should add another method for accessing local files, IK it's bad since it's a method that will work only with one adapter but we shouldn't leave it as is since a lot of ppl are having issues with this and the workarounds aren't good. Something like the PR suggested on the first comment should be good, not sure why it was closed, maybe it's worth revisiting it...

Or just make provisions in the url() method to add the 'root' directory if it's the local disk being used

I retract all statements that I posted before... as Taylor has stated in the PR related to this thread, the URL method is indeed only for public files.

But, with that said, it sure would be nice to have a method to get a local path...

If you mean server path and not URL then you could create a function to get the disk root from the filesystems.php config file:

function diskFilePath($disk, $filename)
{
    return config('filesystems.disks.' . $disk . '.root') . '/' . $filename;
}

@filljoyner that wont work for S3 or anything other than local/public as they have a root config.. and ideally one would like a function to work globally? - i was going to do something like that myself but stopped because of what i said.

a slightly diff variant of @filljoyner solution to get correct path with nested dirs

here is a gist

Over a year later and this is still broken. I can't believe this issue is still closed. 馃憥

Commenting on a closed 5.2 issue isn't going to bring any more attention to possible current issues. It would be best to file a new issue on 5.5 with clear steps to reproduce

This is _NOT_ broken because the paths in the Storage system are opaque strings that are only used within the different Storage adapters. There is no support for the Storage facade to return local paths because most of the different adapters aren't local in nature.

  1. If you think you need the local path to build an url to the file, use the Storage::url() method.
  2. If you need the path to the file for some external processing; download it using Storage::get(), do the processing, and then upload the file again.

If you want to work with the local disk, and only the local disk, use the File facade instead.

Looks like it is best just to write your own helpers which isn't a huge deal. I personally create a functions.php file in the /app directory and include it via composer.json for stuff like this. I'm sure many of you do the same but if there is anyone out there that needs help here, you can add it to the existing "autoload" option like so:

    "autoload": {
        "classmap": [
            "database/seeds",
            "database/factories"
        ],
        "psr-4": {
            "App\\": "app/"
        },
        "files": [
            "app/functions.php"
        ]
    },

Once you add that in you'll want to use composer to dump the generated autoload file:
composer dump-autoload

One other thing here and I'll pipe down. In my projects where I'm using file storage (local, s3, etc), I'll keep the path in my .env file. The Storage disk config will get me where I need to go (local storage, s3, etc), I create functions as helpers to help build out full paths, and those functions pull the path from the .env file so it is nice and dynamic.

Not saying this is the way to go. Just a work around I use that may be helpful.

To apply a storaged file to a download response:

public function getFile($filename) { ... Storage::disk($disk)->getDriver()->getAdapter()->applyPathPrefix($filename); return response()->download($imageFilePath, null, [], null); }

Taken from TerrePorter:
https://laracasts.com/discuss/channels/laravel/response-download-with-file-from-storage

Does that even look remotely portable? What happens when $disk isn't the local disk?

If you're so focused on hardcoding your code to work with the local filesystem, why are you not using the local filesystem via the File facade? Why are you even using the Storage facade system that has portable storage solutions if you write code that ignore the portability that the Storage system provides?

If you wanted to download something from Storage, either Storage::get() the file (if it is private) and return the content, or redirect to the Storage::url() of the file assuming it is publicly available.

Taken from https://github.com/laravel/framework/issues/13610#issuecomment-341969586

So I had a similar issue, and I was able to overcome it using the local driver as is. I'll share in case it is useful for someone else.

I had a local directory located on the server that was outside of the base_path for the app. These files were not supposed to be accessible publicly, but only manipulated (copied and such) by the app.

I was able to solve it by adding a 'url' attribute to my disks in my config/filesystems.php . Then I was able to access it with the url attribute of that disk.

'archive' => [
'driver' => 'local',
'root' => env('ARCHIVE_FILES_ROOT_DIRECTORY', '/home/.../archive/files'),
'url' => env('ARCHIVE_FILES_ROOT_DIRECTORY', '/home/.../archive/files'),
],

Storage::disk('archive')->url($file);

This should work for local drivers also in sub folders in storage/app directory like so:

'templates' => [
'driver' => 'local',
'root' => storage_path('app/templates'),
'url' => storage_path('app/templates'),
],

Storage::disk('templates')->url($file);

So I recently had the same problem and I didn't found any easy solution so, here is a hack and it works, somehow...
($this->file :is how I get the url of my file inside storage/app directory this is in my database)
e.g = $this->file is avatar/usuario.jpg and the full url for that file is "c:/..../storage/app/avatar/usuario.jpg"
Problem is that the method doesnt expose "/app/" so this is how I add it:

/here I found all character before the url of my file ("avatar/usuario.jpg"). So il be getting something like ("c:/.../storage/")/
$path=explode($this->file,storage_path($this->file))[0];
//then just concat strings
$urlfile=$path.'app/'.$this->file;

and there you have the full url... as easy as that I hope someone finds it usefull :)

This isn't about finding a solution, because you're using the wrong tool.

  1. If you want to use the local filesystem, use the File facade.
  2. If you want to use the default private storage, use Storage::disk('local') which is the default storage.
  3. If you want to use the default public storage, use Storage::disk('public') which stores stuff in a folder that you've symlinked (php artisan storage:link)
  4. If you need an url where the user should be able to see the stored file, use $disk->url($filename). This works for for the default public disk, and some other storages.

@sisve This isn't entirely right. Sometimes you might rely on a third party package that is not Laravel specific, and for better or worse, it might rely on full, absolute file paths. I do believe that for any given file via a Filesystem object, there should be a method to get the absolute path to that file/directory.

@mtrpcic A scenario where you are using third party code that doesn't use Laravel suggest that you need to do whatever that code is doing. You could probably use file_get_contents directly since that third party code aren't using the Laravel facades, and thus not affected by any configuration. You could probably hard-code any paths in that case.

I do believe that for any given file via a Filesystem object, there should be a method to get the absolute path to that file/directory.

If you had that capability, what do you expect to be able to do with that absolute path? Imagine that you had the path S3 used to store files on their disks, do you expect to be able to use that absolute path in any way?

If you're working with absolute paths (and presumedly the local filesystem), and need to be working with these paths for compatibility issues, use the File facade (or php functions that work directly on the filesystem).

Not all (read: cloud) systems expose the internal storage path, and as such, the Storage facade does not expose this functionality either.

For those who are seeking for a full path instead of url, just try this out:

$filename = $image->store('', 'users');
$avatar = Storage::disk('users')->path($filename);

Storage::disk('name')->path('file.jpg') works. Thanks @soulessvadi!

does not work in S3
if local :
C:xampp\htdocs\myappnamestorage\app\documents/b8Hhjw6g0k8S43nDpQSAxlM1yR8fhStNPmMSaeVr.pdf

but if DigitalOcean Space (S3 driver)
documents/b8Hhjw6g0k8S43nDpQSAxlM1yR8fhStNPmMSaeVr.pdf

i use the full path for embedding PDF manipulation (take only 3 pages of total PDF document)
i guess i need to modify the code using storage::get()

Hey everyone,

I'm locking this issue because it either has gone off-topic, become a dumping ground for things which shouldn't be in an issue tracker or is just too old. Please try to discuss things further on one of the below channels:

Was this page helpful?
0 / 5 - 0 ratings