Skip to content

Instantly share code, notes, and snippets.

@atomjoy
Last active March 31, 2025 10:18
Show Gist options
  • Save atomjoy/04991ab6fdd74565bdd9463f06bd4dfc to your computer and use it in GitHub Desktop.
Save atomjoy/04991ab6fdd74565bdd9463f06bd4dfc to your computer and use it in GitHub Desktop.
Upload, Resize and Save images with Intervention Image v3 in Laravel Storage. Test UploadedFile::fake() with real image file.

Resize images with Intervention Image v3 in Laravel Storage

Upload, Resize and Save images with Intervention Image v3 in Laravel Storage. Test UploadedFile::fake() with real image file.

Upload form

<form action="/upload/avatar" method="post" enctype="multipart/form-data">
	@csrf
	<label>Image</label>
	<input type="file" name="image">
	<input type="submit" name="submit" value="Upload">
</form>

Install

composer require intervention/image

Change .env (public, ftp, sftp, s3)

Here you will set the disk where our files will be saved by the Storage::put() method and from where they will be downloaded by the Storage::get() or Storage::download() methods. !!! Do not use the Storage::disk('public') method when saving files!!!

# Private files default setting (storage/app/private)
FILESYSTEM_DISK=local

# For local server public diectory (storage/app/public)
FILESYSTEM_DISK=public

# For local or remote ftp server with ssl (works well, config in StorageFtpProvider.php)
FILESYSTEM_DISK=ftp

Link storage to public

If you want to make public disk uploaded files accessible from the web, you should create a symbolic link from directory storage/app/public to target public/storage directory.

php artisan storage:link

Upload, Resize, Scale and Crop Images

This is how we save files with the Storage class to (public, ftp, sftp, s3).

<?php
	
use Intervention\Image\ImageManager;
use Intervention\Image\Drivers\Gd\Driver;

$user =  Auth::user();
$path = 'media/avatars/' . $user->id . '.webp';

// Resize uploaded image
$image = (new ImageManager(new Driver()))->read($request->file('image'))->resize(256,256)->toWebp();

// Scale proportionally uploaded image
$image = (new ImageManager(new Driver()))->read($request->file('image'))->scale(256)->toWebp();

// Crop uploaded image from center
$image = (new ImageManager(new Driver()))->read($request->file('image'))		
	->crop(config('default.banner_width', 1920), config('default.banner_height', 540), 0, 0, position: 'center-center')
	->toWebp();

// Save file with Storage class
Storage::put($path, (string) $image);

// Save with Intervention localy private file (only example don't use here)
// $image->save(storage_path('private/avatars/abcxyz.webp'));

Display Images from Laravel Storage (public or remote)

Here's how you can access files using the Storage class (exampe file ShowImage.php).

Get image url

You may use the url method to get the URL for a given file. If you are using the public disk, this will typically just prepend /storage to the given path and return a relative URL to the file. If you are using the s3 driver, the fully qualified remote URL will be returned.

// For javascript onerror method
Route::get('img/url', function() {	
	return Storage::url(request($path));	
	return Storage::temporaryUrl(request($path), now()->addMinutes(5));
});

Get image from storage

Here's how to get the content of file from a public disk, ftp, sftp or s3 server.

// Display image from html
<img src="image/show?path=media/avatars/1.webp">
	
// Display image with path from img/show route
Route::get('img/show', function() {	
	$path = request($path);	
	try {
		// From storage (check null or use Throwable)
		if ($path != null && Storage::exists($path)) {
			return Storage::response($path);
		}
	} catch(\Throwable $e) {		
		return response()->file(public_path('/default/error.webp'));
	}	
	return response()->file(public_path('/default/default.webp'));
}

Get image from storage resize with watermark

// Get image content from storage and then display from server
$content = Storage::get($path);
return response($content)->header('Content-Type', 'image/webp');

// Get from storage scale add watermark and display
$width = request()->integer('size');	   
$image = (new ImageManager(new Driver()))
	->read(Storage::get($path))
	->scale(width: $width)
	->place('images/watermark.png', 'bottom-right');	   
$encoded = $image->toWebp();	   
return response($encoded)->header('Content-Type', 'image/webp');

// Get and display file from local disk
return response(file_get_contents(storage_path('app/private/foto/secret.webp'))->header('Content-Type', 'image/webp');

Download files with Storage

Force file download in browser.

// Download file from storage
return Storage::download('orders.webp');
return Storage::download('file.webp', $name, $headers);
		
// Download private file from local server
return response()->download(storage_path('app/private/foto/secret.webp'));

Validate image

<?php
use Illuminate\Validation\Rule;

// Max size in KB (bail for stop on first error, sometimes for validate if exists in request)
$validated = request()->validate([
	'avatar' => [
		'required|image|mimes:webp,jpeg,png,jpg,gif|max:2048',
		'dimensions:min_width=100,min_height=100,max_width=500,max_height=500'
	],
	'image' => [
		'bail',
		'sometimes|image',
		Rule::file()->types(['webp', 'jpeg', 'jpg', 'png', 'gif'])->max(2 * 1024),
		Rule::dimensions()->maxWidth(1920)->maxHeight(1080),
	]
]);

Test upload

Test image upload with real file

<?php

$disk = config('filesystems.default', 'public');

Storage::fake($disk);

$response = $this->postJson('/web/api/upload/avatar', [    
    'avatar' => UploadedFile::fake()->createWithContent('avatar.webp', file_get_contents(base_path('tests\Dev\image\fake.webp'))),
     // 'avatar' => UploadedFile::fake()->image('avatar.webp', 200, 200),
]);

$response->assertStatus(200)->assertJson([
    'message' => 'Avatar has been uploaded.',
    'avatar' => 'avatars/' . $user->id . '.webp'
]);

Links

<?php
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Storage;
use Intervention\Image\ImageManager;
use Intervention\Image\Drivers\Gd\Driver;
// Js onerror fetch url for img.src
Route::get('/img/url', function () {
try {
$path = request('path');
if (Storage::exists($path)) {
return Storage::url($path);
}
return config('default.error_file_placeholder', rtrim(config('app.url'), '/') . '/default/avatar.webp');
} catch (\Throwable $e) {
return config('default.error_file_placeholder', rtrim(config('app.url'), '/') . '/default/avatar.webp');
}
});
// Image response
Route::get('/img/show', function () {
try {
$path = request('path');
if (Storage::exists($path)) {
return Storage::response($path);
}
return response()->file(public_path('/default/default.webp'));
} catch (\Throwable $e) {
return response()->file(public_path('/default/default.webp'));
}
});
// Image response
Route::get('/img/resize', function () {
try {
$path = request('path');
if (Storage::exists($path)) {
// Display image
// $content = Storage::get($path);
// return response($content)->header('Content-Type', 'image/webp');
// Display image with file path
// return Storage::response($path);
// Get from Laravel Storage then resize and display
$image = (new ImageManager(new Driver()))
->read(Storage::get($path));
if (in_array(request('size'), ['s', 'm', 'l', 'xl'])) {
if (request('size') == 's') {
$image->scale(width: 360);
}
if (request('size') == 'm') {
$image->scale(width: 480);
}
if (request('size') == 'l') {
$image->scale(width: 768);
}
if (request('size') == 'xl') {
$image->scale(width: 1024);
}
}
$image = $image->toWebp();
return response($image)->header('Content-Type', 'image/webp');
}
return response()->file(public_path('/default/default.webp'));
} catch (\Throwable $e) {
return response()->file(public_path('/default/default.webp'));
}
});
// Image response
Route::get('/img/avatar', function () {
try {
$path = request('path');
// Check is path null or use Throwable not Exception
if ($path != null && Storage::exists($path)) {
return Storage::response($path);
}
return response()->file(public_path('/default/avatar.webp'));
} catch (\Exception $e) {
return response()->file(public_path('/default/avatar.webp'));
}
});
// Get image response
// Route::get('/img', function () {
// return '<img src="/img/show?path=avatars/1.webp>';
// return '<img src="/img/resize?path=avatars/1.webp&size=s">';
// });
/*
<picture>
<source media="(min-width:1024px)" srcset="/img/resize?path=media/images/111.webp">
<source media="(min-width:800px)" srcset="/img/resize?path=media/images/111.webp&size=xl">
<source media="(min-width:600px)" srcset="/img/resize?path=media/images/111.webp&size=l">
<source media="(min-width:400px)" srcset="/img/resize?path=media/images/111.webp&size=m">
<img src="/img/resize?path=media/images/111.webp&size=s" alt="Flowers" style="width:auto;">
</picture>
*/
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
/**
* Ftp storage class
*
* https://laravel.com/docs/12.x/filesystem#sftp-driver-configuration
* Install:
* composer require league/flysystem-ftp "^3.0"
*/
class StorageFtpProvider extends ServiceProvider
{
/**
* Register services.
*/
public function register(): void
{
// Change storage disk to ftp (or in .env)
if (config('filesystems.default') == 'local' || config('filesystems.default') == 'public') {
config(['filesystems.default' => 'ftp']);
}
// Add storage ftp
$this->app->config['filesystems.disks.ftp'] = [
'driver' => 'ftp',
'username' => env('FTP_USERNAME', 'laravel'),
'password' => env('FTP_PASSWORD', 'password'),
'host' => env('FTP_HOST', 'localhost'),
'port' => (int) env('FTP_PORT', 21),
'ssl' => env('FTP_SSL', true),
'passive' => true,
'timeout' => 30,
// Optional FTP Settings...
// 'root' => env('FTP_ROOT'),
];
}
/**
* Bootstrap services.
*/
public function boot(): void
{
//
}
}
<?php
namespace App\Http\Controllers\Auth;
use Exception;
use App\Http\Controllers\Controller;
use App\Events\UploadAvatar;
use App\Exceptions\JsonException;
use App\Http\Requests\Auth\UploadAvatarRequest;
use App\Models\Profile;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Response;
use Intervention\Image\ImageManager;
class UploadAvatarController extends Controller
{
function index(UploadAvatarRequest $request)
{
// Validate
// request()->validate(['avatar' => 'required|image|mimes:jpeg,png,jpg|max:2048']);
// Save
// $path = $request->file('avatar')->storeAs('avatars', $filename, 'public');
// Save with request
// $path = Storage::putFileAs('avatars', $request->file('avatar'), $filename);
try {
$user = Auth::user();
$filename = $user->id . '.webp';
$path = 'avatars/' . $filename;
$image = ImageManager::gd()
->read($request->file('avatar'))
->resize(
config('default.avatar_resize_pixels', 256),
config('default.avatar_resize_pixels', 256)
)->toWebp();
Storage::put($path, (string) $image);
$data = ['avatar' => $path];
$profile = $user->fresh(['profile'])->profile;
if ($profile == null) {
$data = [
'avatar' => $path,
'name' => $user->name ?? 'Guest',
'username' => uniqid('user.'),
];
}
Profile::updateOrCreate([
'user_id' => $user->id
], $data);
UploadAvatar::dispatch(Auth::user(), $path);
return response()->json([
'message' => __('upload.avatar.success'),
'avatar' => $path,
], 200);
} catch (Exception $e) {
report($e);
throw new JsonException(__('upload.avatar.error'), 422);
}
}
}
<?php
namespace App\Http\Requests\Auth;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\ValidationException;
use Illuminate\Validation\Rule;
class UploadAvatarRequest extends FormRequest
{
protected $stopOnFirstFailure = true;
public function authorize()
{
return Auth::check(); // Allow logged
}
public function rules()
{
return [
'avatar' => [
'required',
'mimes:webp,jpeg,jpg,png,gif',
Rule::file()->types(['webp', 'jpeg', 'jpg', 'png', 'gif'])
->max(config('default.max_upload_size_mb', 5) * 1024),
Rule::dimensions()
->minWidth(config('default.avatar_min_pixels', 64))
->minHeight(config('default.avatar_min_pixels', 64)),
Rule::dimensions()
->maxWidth(config('default.avatar_max_pixels', 1025))
->maxHeight(config('default.avatar_max_pixels', 1025)),
]
];
}
public function failedValidation(Validator $validator)
{
throw new ValidationException($validator, response()->json([
'message' => $validator->errors()->first()
], 422));
}
function prepareForValidation()
{
$this->merge(
collect(request()->json()->all())->only(['avatar'])->toArray()
);
$this->merge([
'email' => strtolower($this->email),
]);
}
}
<?php
namespace Tests\Dev;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Tests\TestCase;
class UploadAvatarTest extends TestCase
{
use RefreshDatabase;
function test_upload_avatar()
{
$disk = config('filesystems.default');
Storage::fake($disk);
$user = User::factory()->create([
'name' => 'Adelajda Brzęczyszczykiewicz',
'email' => uniqid() . '@gmail.com'
]);
$this->assertDatabaseHas('users', [
'name' => $user->name,
'email' => $user->email,
]);
$this->actingAs($user);
$response = $this->postJson('/web/api/upload/avatar', [
'avatar' => UploadedFile::fake()->image('avatar.webp'),
]);
$response->assertStatus(422)->assertJson([
'message' => 'The avatar field has invalid image dimensions.',
]);
$response = $this->postJson('/web/api/upload/avatar', [
'avatar' => UploadedFile::fake()->image('avatar.bmp', 200, 200),
]);
$response->assertStatus(422)->assertJson([
'message' => 'The avatar field must be a file of type: webp, jpeg, jpg, png, gif.',
]);
$response = $this->postJson('/web/api/upload/avatar', [
// 'avatar' => UploadedFile::fake()->image('avatar.webp', 200, 200),
'avatar' => UploadedFile::fake()->createWithContent('avatar.webp', file_get_contents(base_path('tests\Dev\image\fake.webp'))),
]);
$response->assertStatus(200)->assertJson([
'message' => 'Avatar has been uploaded.',
'avatar' => 'avatars/' . $user->id . '.webp'
]);
$response = $this->postJson('/web/api/upload/avatar', [
// 'avatar' => UploadedFile::fake()->image('avatar.webp', 200, 200),
'avatar' => UploadedFile::fake()->createWithContent('avatar.png', file_get_contents(base_path('tests\Dev\image\fake.png'))),
]);
$response->assertStatus(200)->assertJson([
'message' => 'Avatar has been uploaded.',
'avatar' => 'avatars/' . $user->id . '.webp'
]);
$this->assertDatabaseHas('profiles', [
'avatar' => 'avatars/' . $user->id . '.webp',
]);
Storage::disk($disk)->assertExists('avatars/' . $user->id . '.webp');
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment