How to retrieve file from Laravel storage subfolder? - laravel

I'm trying to retrieve a file from this path in a laravel project:
/storage/app/public/blog/3.jpg
These approaches produce following errors:
1.
$image = Storage::disk('public')->get('/storage/blog/3.jpg');
->
local.ERROR: File not found at path: storage/blog/3.jpg {"userId":16,"exception":"[object] (Illuminate\\Contracts\\Filesystem\\FileNotFoundException(code: 0): File not found at path: storage/blog/3.jpg at /Users/artur/PhpstormProjects/safa-ameedee.com/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php:171)
[stacktrace]
$image = Storage::disk('public')->get('/storage/app/public/blog/3.jpg');
$image = Storage::get('/storage/app/public/blog/3.jpg');
->
local.ERROR: File not found at path: storage/app/public/blog/3.jpg {"userId":16,"exception":"[object]
The weird thing is that I store the files in the storage like so:
Storage::disk('public')->put('/blog/' . $request->path, $image);
So should they not be retrievable in the same way?

TL/DR
Storage::disk('public')->get('block/3.jpg');
Explanation
The problem is you're putting storage in the path for some reason. It's not necessary and is leading to the wrong path being built.
Take a look at the default filesystems config:
'disks' => [
'local' => [
'driver' => 'local',
'root' => storage_path('app'),
'throw' => false,
],
'public' => [
'driver' => 'local',
'root' => storage_path('app/public'),
'url' => env('APP_URL').'/storage',
'visibility' => 'public',
'throw' => false,
],
The root is what is useful to see here. storage_path() returns the full path to the storage folder. So something like storage_path('folder_1') -> /home/user/project/storage/folder_1.
The local disk is the default, so just doing Storage::get() will use it automatically.
You're using the public disk, so the actual location of these files is storage/public (symlinked into public/storage). This means doing Storage::disk('public') already begins at /home/user/project/storage/app/public. Adding storage again makes the path incorrect.
Using path may help with future debugging. Storage::disk('public')->path('block/3.jpg') will output the full path, and you can see where it's going wrong. For your code given it would probably show something like /home/user/project/storage/app/public/storage/app/public.

Related

Question about Storing Laravel File Uploads on File System vs. Database, and how to Store in DB instead of File System if desired

Just starting to work with File uploads in Laravel 8.x. I have a UI built and routes and a Controller, and I'm having an internal debate about where and how to store the files. The expectation is that the files will be .gif, .jpg, .png, .pdf, ?.docx, and possibly some video files types, but mostly .pdf, and we might restrict to .pdf only to make it easier initially. Also, the file size probably won't be more than 1-2 MB per file and would probably cap that at 10 MB max. There are up and downsides to using the file system vs. a database for that. I would want a DB anyways, so the question is whether to just store the path to the file system in the database, or to store the mime_type and some other info in the DB, along with a BLOB or base64 encoded version of the file, probably the base64 version.
I like the idea of using a BLOB or base64 because then it is all self-contained in the DB. The queries would be the same either way really because you would either have the raw data or a path to the file data in the DB. You take a hit on size using base64, but for smaller file sizes, not huge, and those are easier to deal because I will want to use data urls to display the file on the front end, or otherwise convert the blob to a dataurl with js. So, not really sure what the difference is for BLOB vs. base64 in the DB.
That is all just background, but welcome feedback.
The issue that I am having in the controller is probably simple because I have never worked with the Laravel File Methods. All I have so far is this, although I would probably want to extend that to an array of files rather than just one.
protected function attachToRequest(Request $request) {
Log::info($request->input('parent'));
Log::info($request->file('file'));
Log::info($request->file('file')->getClientOriginalName());
Log::info($request->file('file')->getMimeType());
Log::info($request->file('file')->getRealPath());
Log::info($request->file('file')->getSize());
Log::info(var_dump($request->file('file')->get()));
}
because I just want to understand how the Laravel File Facade works, so this is helpful:
https://laravel.com/api/8.x/Illuminate/Support/Facades/File.html
As a test case, I am seeing something like this is the laravel.log file when I post a file to the web routes:
2021-08-07 16:17:16] local.INFO: 2
[2021-08-07 16:17:16] local.INFO: /tmp/php3pLWBc
[2021-08-07 16:17:16] local.INFO: x.pdf
[2021-08-07 16:17:16] local.INFO: application/pdf
[2021-08-07 16:17:16] local.INFO: /tmp/php3pLWBc
[2021-08-07 16:17:16] local.INFO: 27856
[2021-08-07 16:17:16] local.INFO:
The part that I am stuck on is how to get the raw data for the file with something like file_get_contents or the Facade method, so that I can convert it to base64 or just the BLOB and store it in a database table if I want to play around with that.
I have Laravel running on NGINX and PHP in a docker container, and when I examine the /tmp directory using docker exec, I just seem some log files and a bunch of session ids, and I think my php info list the /tmp upload as empty.
Seems like there should be a really easy way to store it in the database rather the file system if I want to.
I'd like to try the DB method first and then delete the upload from wherever it is in a /tmp folder and also from the file storage for Laravel. If I'm not happy with that I can use the file system.
config/filesystems.php
'disks' => [
'local' => [
'driver' => 'local',
'root' => storage_path('app'),
],
'public' => [
'driver' => 'local',
'root' => storage_path('app/public'),
'url' => env('APP_URL').'/storage',
'visibility' => 'public',
],
's3' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION'),
'bucket' => env('AWS_BUCKET'),
'url' => env('AWS_URL'),
'endpoint' => env('AWS_ENDPOINT'),
],
'patients' => [
'driver' => 'local',
'root' => storage_path('patients'),
'visibility' => 'public',
]
],
'links' => [
public_path('storage') => storage_path('app/public'),
],
If I were to use the file system I would probably want to create another storage path for these files, something like:
'request_attachements' => [
'driver' => 'local',
'root' => storage_path('request_attachements'),
'visibility' => 'public',
]
This seems to answer the first part of my question, probably obvious for someone who is familiar with uploading files using Laravel.
That seems to work because I have that "disk" created and the file that I uploaded in that directory with a uuid and the file extension appended. I could then just use what is below. The $contents is what I can base64encoode, or not, and then store as a BLOB in the database, and I also have url to the storage disk. That is what I was looking for because not I can finish writing the code. sample url generated looks like:
2/aOkWzxR7W4yH1vaCW2fbft9yY5dO0GVpJs2jHWnM.png
config/filesystems.php
'requestattachements' => [
'driver' => 'local',
'root' => storage_path('./requestattachements'),
'visibility' => 'public',
]
app/Http/Controllers/MyControllers/OrdersController.php
protected function attachToRequest(Request $request) {
Log::info($request->input('parent'));
Log::info($request->file('file'));
Log::info($request->file('file')->getClientOriginalName());
Log::info($request->file('file')->getMimeType());
Log::info($request->file('file')->getRealPath());
Log::info($request->file('file')->getSize());
Storage::makeDirectory('requestattachements');
$path = $request->file('file')->store(
$request->input('parent'), 'requestattachements'
);
Log::info($path);
$contents = Storage::disk('requestattachements')->get($path);
Log::info($contents);
}
Optionally insert into DB:
DB::connection('mysql2')->table('request_attachments')->insert([** Insert stuff here . . ]);
routes/web.php
Route::middleware(['auth:sanctum', 'verified'])->post('Orders/attach_request', [OrdersController::class, 'attachToRequest']);
One question I do have about that though is whether or not the files in that storage container are accessible via the web ?
My storage folder structure is like below, and there is a symlink created with php artisan storage:link, which is basically:
ln -s storage/app/public public/storage
drwxrwxrwx# app
drwxrwxrwx# debugbar
drwxrwxrwx# framework
drwxrwxrwx# logs
drwxr-xr-x orderattachments
drwxr-xr-x# requestattachements
I take it that the files in requestattachments, etc. are not accessible unless I use a helper function to get those, and I should change the permission as needed ?

Shared Hosting Laravel 8 Hosting problem: Symlink is not working

I need the deploy my laravel project to the web. With this introduction But the hosting provider is Doesn't allow me to use a symbolic link(symlink), I called customer service, and told me blocked for security reasons.
So, since I do not use symbolic links, there are big problems in uploading images and deploying them.
Is there a way to do deployment my app?
Change your disk configurations in the config/filesystems.php to read and write to public folder via public_path() function.
If you're deploying over a shared host, you probably have another folder like public_html as your public folder which is not included in your source. Try to link to the public_html using dots ../../public_html/
'public' => [
'driver' => 'local',
// 'root' => storage_path('app/public'),
'root' => public_path(),
'url' => env('APP_URL'),
'visibility' => 'public',
],
'photos' => [
'driver' => 'local',
'root' => public_path('photos'),
'url' => env('APP_URL') . '/photos',
'visibility' => 'public',
],
Another way is just create something like symlink using php, example you can create a route like below:
Route::get(
'/storage/{file}',
function ($file) {
$filepath = "../storage/app/public/".$file;
header("Cache-Control: public");
header("Content-Type: ".mime_content_type($filepath));
header('Content-Length: '.filesize($filepath));
readfile($filepath);
die();
}
);
Maybe not best practice, but it will solve your problem. It's will do same as symlink.

Laravel and React Upload image via Storage Facades fail

I want to upload a logo for my reports.
This is a snippet from my uploadLogo function
$file = $request->file;
Storage::disk('logo')->put('logo.png', $file);
I've created a logo profile in filesystems.php like this.
'logo' => [
'driver' => 'local',
'root' => public_path() . '/img',
'url' => env('APP_URL').'/public',
'visibility' => 'public',
],
But it eventually created the file in a 'random' ( or misunderstood ) location with a random name.
public\img\logo.png\M4FGLpZzAsyxn8NHiJLxo95EoP7I3CkIWvqkiQsv.png
What am I missing in my setup here?
You can store the file directly of the request's file (UploadedFile) object. And use storeAs to save by the name you supply. The Storage::put and UploadedFile::store` methods generate random names for the filed being stored.
$path = $request->file->storeAs('img', 'logo.png', 'logo');
More info https://laravel.com/docs/8.x/filesystem#storing-files and https://laravel.com/docs/8.x/requests#storing-uploaded-files

Illuminate\Contracts\Filesystem\FileNotFoundException in Laravel 5.6 using Storage facade

This code is returning an strange error:
$file = Storage::get(Storage::disk('notas_xml')->path('') . 't.txt');
As you can see the file does exist.
Get the file directly from the disk
$exists = Storage::disk('notas_xml')->exists('t.txt');
if ($exists) {
$file = Storage::disk('notas_xml')->get('t.txt');
}
And if you didn't setup notas_xml disk in filesystems.php
$file = Storage::get('public/arquivos/notas_xml/t.txt');
And to use your code, you need to setup a disk like so in config/filesystems.php
'notas_xml' => [
'driver' => 'local',
'root' => storage_path('app/public/arquivos/notas_xml'),
'url' => env('APP_URL') . '/storage',
'visibility' => 'public',
],
And get the file simply like this
$file = Storage::disk('notas_xml')->get('t.txt');
You need to get the file as below code:
Storage::disk('notas_xml')->has('t.txt');
Above has method may be used to determine if a given file exists on the disk:
Please read documentation https://laravel.com/docs/5.1/filesystem#retrieving-files
To better understand all this... The trick is in: config/filesystems.php
If you have this code (which is the default value of Laravel in Github)
'disks' => [
'local' => [
'driver' => 'local',
'root' => storage_path('app'),
],
This Facade Storage will act in the folders
root_laravel_project/storage/app
So if you want to check if an "israel.txt" file exists
if( Storage::exists('israel.txt') ){
echo "File found. And it exists on the path: root_laravel_project/storage/app/israel.txt";
}
Remember that up to this point it has nothing to do with the symbolic link php artisan storage: link
This symbolic link is only to make a folder called "public" within the "storage" folder be part of the public access through HTTP
'disks' => [
'local' => [
'driver' => 'local',
'root' => storage_path('app'),
],
'public' => [
'driver' => 'local',
'root' => storage_path('app/public'),
'url' => env('APP_URL').'/storage',
'visibility' => 'public',
],
Then at the time of doing the sym. You can access the files by http (which are public for any user)
This example assumes that you are using a virtual host (and if not, you must do it as a recommendation for work better locally)
http:// root_laravel_project.test/storage/israel-alvarez.txt
Or so that it is better understood as in the old school without a virtual host
http:// localhost/public/storage/israel-alvarez.txt
Then these urls will look inside your folder
root_laravel_project/storage/app/public/israel-alvarez.txt
Laravel's documentation is somewhat brief and can be confusing regarding this issue. But you just have to remember that one thing is to access through the "storage Facade" (which is the correct way to upload and verify if there are files) and another thing is to access through the http (through url) which is the symbolic link (which is the treatment you already give to users to download files or see if it is a PDF for example).
I hope it helps. Good day

Storing files outside the Laravel 5 Root Folder

I am developing a laravel 5 project and storing image files using imagine. I would want to store my image files in a folder outside the project's root folder. I am stuck at the moment The external folder where image files are supposed to be stored, I want to make it accessible via a sub-domain something like http://cdn.example.com Looking towards your solutions.
The laravel documentation could give you a helping hand.
Otherwise you could go to config/filesystems.php and add your own custom storage path for both local and production:
return [
'default' => 'custom',
'cloud' => 's3',
'disks' => [
'local' => [
'driver' => 'local',
'root' => storage_path().'/app',
],
'custom' => [
'driver' => 'custom',
'root' => '../path/to/your/new/storage/folder',
],
's3' => [
'driver' => 's3',
'key' => 'your-key',
'secret' => 'your-secret',
'region' => 'your-region',
'bucket' => 'your-bucket',
],
'rackspace' => [
'driver' => 'rackspace',
'username' => 'your-username',
'key' => 'your-key',
'container' => 'your-container',
'endpoint' => 'https://identity.api.rackspacecloud.com/v2.0/',
'region' => 'IAD',
],
],
];
get ur path name from base_path(); function, then from the string add your desired folder location.
suppose ur
base_path() = '/home/user/user-folder/your-laravel-project-folder/'
So ur desired path should be like this
$path = '/home/user/user-folder/your-target-folder/'.$imageName;
make sure u have the writing and reading permission
You can move all or a part of storage folder in any folder of yours in your server. You must put a link from old to new folder.
ln -s new_fodler_path older_folder_path
You can make a new virtual host to serve the new folder path.

Resources