Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

kendrick's avatar

Logo/ Avatar/ Image Upload Clarification

I am currently thinking about integrating User uploads in an efficient and storage friendly way, so uploads will get pushed to a long-term storage in the background, such as S3.

My current setup is a store method.

$this->validate($request, [
    'avatar' => 'max:600',
);

if($request->hasFile('avatar')){
    $user = Auth::user();
    $s3 = Storage::disk('s3');  

    $image = $request->file('avatar');
    $filename = time() . '.' . $image->getClientOriginalExtension();
    s3 = Storage::putFile('uploads/avatars/', $request->file('avatar'), 'public');

    Image::make($image)->resize(300,300)->save($s3);
            
    $user->avatar = $filename;
    $user->save();  
}
  1. This will only store the filename within the avatar column within my users_table right, not the uploaded file?

  2. What if I directly upload the file to S3, in the same web request that the file is originally uploaded in, and S3 is not available? Is their a way to first store it to disk on a directory on my application server and then create an Event/Job to push the Upload to my S3 storage in order to reduce the request and make it directly visible to the User without waiting for it to be on S3?

  3. Or could this thought be solved with a retry/if system in order to handle failures? Or is this already given with my store method above?

Would be nice to hear your thoughts.

0 likes
33 replies
rin4ik's avatar
rin4ik
Best Answer
Level 50

here is my setup for uploading images with jobs. When I updating channel user choose image from computer and dispatch UploadImage job

 public function update(ChannelUpdateRequest $request, Channel $channel)
    {
        $this->authorize('update', $channel);

        $channel->update([
            'name' => $request->name,
            'slug' => $request->slug,
            'description' => $request->description,
        ]);
        if ($request->file('image')) {
            $request->file('image')->move(
                storage_path() . '/uploads',
                $fileId = uniqid(true)
        );
            $this->dispatch(new UploadImage($channel, $fileId));
        }
        return redirect('/channel/' . $request->slug . '/edit');
    }

here is my job . in handle method I'm storing image to storage folder via Image Intervention made size 50x50. and send it to s3 bucket if it is successful I delete this image from storage folder and save that filename to my db column . also encode all formats to .png

 public $channel;
    public $fileId;

    /**
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct(Channel $channel, $fileId)
    {
        $this->channel = $channel;
        $this->fileId = $fileId;
    }

  public function handle()
    {
        $path = storage_path() . '/uploads/' . $this->fileId;
        $fileName = $this->fileId . '.png';

        Image::make($path)->encode('png')->fit(50, 50, function ($c) {
            $c->upsize();
        })->save();

        if (Storage::disk('s3images')->put('profile/' . $fileName, fopen($path, 'r+'))) {
            \File::delete($path);
        }
        $this->channel->image_filename = $fileName;
        $this->channel->save();
    }
1 like
kendrick's avatar

Thank you @rin4ik

image_filename is a column within your channels_table, right?

rin4ik's avatar

$c->upsize(); this line means if image is not enough to 50x50 it will make bigger . mark discussion as solved!

1 like
kendrick's avatar

This makes sense @rin4ik

I have not yet worked with slug, how can I understand it?

How would I structure it, if I had a pivot table of pictures, e.g. for uploading multiple images? Create a foreach loop and then store all within the local disk, then loop through them to push to S3 and delete from the disk, right?

rin4ik's avatar

@splendidkeen you don't need to use slug it is just my project and sample for. yes right give it a try!

kendrick's avatar

Which url are you using to make the upload directly visible to the User? @rin4ik

First the local directory and then the image_filename path to S3?

rin4ik's avatar

@splendidkeen create new config file and store there urls

return [
'buckets' => [
    'videos' => 'https://s3-us-west-1.amazonaws.com/videos.codetube.test',
    'videosu' => 'https://s3-us-west-1.amazonaws.com/drop.codetube.test',
    'images' => 'https://s3-us-west-1.amazonaws.com/images.codetub.com'
]

and create method in the User model which you can call

public function getImage()
    {
        if (!$this->image_filename) {
            return config('codetube.buckets.images') . '/default.png';
        }
        return config('codetube.buckets.images') . '/' . $this->image_filename;
    }
kendrick's avatar

Thank you @rin4ik

But still do I have to config the S3 information within config/filesystems.php, right?

How is your config file named (and it just contains the buckets url?)?

rin4ik's avatar

in my case config/codetube.php it doesn't matter where. just make sure put it correctly config('codetube.buckets.images'). You can create your own config file to your project! yes for now it just contains buckets url

kendrick's avatar

I am getting:

Undefined property: $fileId within the Upload Job, even though it is within the Upload method at the Controller as $fileId = uniqid(true)

Line:

 $path = storage_path() . '/uploads/' . $this->fileId;

Within the uploads folder at /storage, files will be stored like this: 15abcb3aabc0a0

Wouldn't it be better to store the temporary file within the public_path directory? Or is there no main advantage @rin4ik ?

rin4ik's avatar

wrap it with condition

 if ($request->file('image')) {
            $request->file('image')->move(
                storage_path() . '/uploads',
                $fileId = uniqid(true)
        );

and send it to your job

$this->dispatch(new UploadImage($channel, $fileId));
rin4ik's avatar

define it in constructor

    public $fileId;

    /**
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct( $fileId)
    {
        $this->fileId = $fileId;
    }
kendrick's avatar

Both is provided, within constructor and wrapped within a condition.

public function uploadLogo(Request $request, Partner $partner){

        $this->validate($request, [
                'logo' => 'max:800',
        ]);

        if ($request->file('logo')) {
                    $request->file('logo')->move(
                    storage_path() . '/uploads',
                    $fileId = uniqid(true)
        );
        $this->dispatch(new UploadLogo($partner, $fileId));
    }

    return redirect()
        ->route('partner.profile.logo')
        ->with('info', 'Your logo has been uploaded.');
            
}

UploadLogo (Job)

public function __construct(Partner $partner, $fileId)
    {
        $this->partner = $partner;
        $this->fileId = $fileId;
    }

    /**
     * Execute the job.
     *
     * @return void
     */
    public function handle()
    {
        $path = storage_path() . '/uploads/' . $this->fileId;
        $fileName = $this->fileId . '.png';

        Image::make($path)->encode('png')->fit(250, 250, function ($c) {
            $c->upsize();
        })->save();

        if (Storage::disk('s3')->put('uploads/partners/logos/' . $fileName, fopen($path, 'r+'))) {
            \File::delete($path);
        }
        $this->partner->logo_filename = $fileName;
        $this->partner->save();
    }
rin4ik's avatar

what about these fields?

public $fileId;
public $partner
1 like
kendrick's avatar

Yes, always forget this one. Thanks @rin4ik

Have you experience problems with the redirect route? Somehow I get a 404, page could not be found, after redirecting to the actual correct route.

Within S3 a Error Message occurred.

kendrick's avatar

Of course @rin4ik

Route

Route::post('partner.profile.logo', [
    'uses' => 'PartnerProfileController@uploadLogo', 
    'middleware' => ['auth:partner'],
    ]);

Yes Bucket is connected.

rin4ik's avatar

you have to give amazon s3 full access to user

kendrick's avatar

Always getting this error, even though I added my correct region. Have you experienced something similar?

Missing required client configuration options: region: (string) A "region" configuration value is required for the "s3" service (e.g., "us-west-2").
rin4ik's avatar

@splendidkeen here is my config .env

S3_KEY = ******************
S3_SECRET = ****************************
S3_DEFAULT_REGION = us-west-1
S3_BUCKET = drop.codetube.test
S3_URL = 

filesystem.php

's3' => [
            'driver' => 's3',
            'key' => env('S3_KEY'),
            'secret' => env('S3_SECRET'),
            'region' => env('S3_DEFAULT_REGION'),
            'bucket' => env('S3_BUCKET'),
            'url' => env('S3_URL'),
        ],

show your config please

kendrick's avatar

That worked. But somehow it will not store anything within the bucket.

I create the requested folder structure and gave it full access.

Then I trigger:

$s3 = Storage::putFile('/uploads/partners/logos/', $request->file('logo'), 'public');

it will store the picture locally, but not within S3

Same with:

if (Storage::disk('s3')->put('/uploads/partners/logos/' . $fileName, fopen($path, 'r+'))) {
            \File::delete($path);
        }
rin4ik's avatar

uploads/partners/logos do you have these folders on s3 bucket?

why you didn't use this instead?

if (Storage::disk('s3')->put('profile/' . $fileName, fopen($path, 'r+'))) {
            \File::delete($path);
        }
rin4ik's avatar

did you run your job?

php artisan queue:work

without this command it'll not work

kendrick's avatar

Bucket connection worked by now, tested it with $s3 = ... Line above.

Now I manage it with the Job.

What is this fopen($path, 'r+') about? @rin4ik

Do you have a efficient solution for telling the User that the upload may not have been successful/ size too large etc.

Next

Please or to participate in this conversation.