Resize Images after Upload

I have many models that have "image" column. Each image is uploaded using the FileUpload of filament, but I need to resize it to 5 different sizes. I couldn't use Spatie's package as I have "image" field in many models instead of creating one "media" table and creating all images there.
FileUpload::make('image_file_name')
->image()
->required(),
FileUpload::make('image_file_name')
->image()
->required(),
Right now the solution I am using is writing code in CreateProduct and EditProduct to handle image resize (and deletion of resized images) Here is an example, which works, but doing this for other 50 models would require duplicating this code 50 times. What else can i do to make sure image resize is handled by a single code base. I could think of probably through an event listener, or a middleware or confuguring FileUpload to do it for all. But I'm unaware how I can achieve this in filament.
protected function beforeSave(): void
{
// Runs before the form fields are saved to the database.
$oldPrimaryImage = $this->record->image_file_name;
$newPrimaryImage = Arr::first($this->data['image_file_name']);

// Do this only if new image is received
if ($oldPrimaryImage !== $newPrimaryImage) {
ResizeImages::dispatch([$newPrimaryImage]);
DeleteImages::dispatch([$oldPrimaryImage]);
}

// Getting only the values of both arrays to compare
$oldSecImages = array_values($this->record->sec_images ?: []);
$newSecImages = array_values($this->data['sec_images'] ?? []);
$deletedImages = array_diff($oldSecImages, $newSecImages);
$addedImages = array_diff($newSecImages, $oldSecImages);

if (count($addedImages)) {
ResizeImages::dispatch($addedImages);
}
if (count($deletedImages)) {
DeleteImages::dispatch($deletedImages);
}
}
protected function beforeSave(): void
{
// Runs before the form fields are saved to the database.
$oldPrimaryImage = $this->record->image_file_name;
$newPrimaryImage = Arr::first($this->data['image_file_name']);

// Do this only if new image is received
if ($oldPrimaryImage !== $newPrimaryImage) {
ResizeImages::dispatch([$newPrimaryImage]);
DeleteImages::dispatch([$oldPrimaryImage]);
}

// Getting only the values of both arrays to compare
$oldSecImages = array_values($this->record->sec_images ?: []);
$newSecImages = array_values($this->data['sec_images'] ?? []);
$deletedImages = array_diff($oldSecImages, $newSecImages);
$addedImages = array_diff($newSecImages, $oldSecImages);

if (count($addedImages)) {
ResizeImages::dispatch($addedImages);
}
if (count($deletedImages)) {
DeleteImages::dispatch($deletedImages);
}
}
18 Replies
wyChoong
wyChoong17mo ago
eloquent model listener?
Kiran Timsina
Kiran TimsinaOP17mo ago
Observer? That would require me to write observer for every Model. No? Or do you mean events? I am trying with event using ->dispatchEvent(). But I might need an example or some doc on how dispatchEvent() is working to be able to make it work. Have you used ->dispatchEvent() on formfield ?
toeknee
toeknee17mo ago
I would personally write a job, and on each one where you use the image, queue the job.
wyChoong
wyChoong17mo ago
if you looking for an easy way, just use spatie's
Kiran Timsina
Kiran TimsinaOP17mo ago
That's what I am doing right now. But the problem is I need to write on beforeSave() (EditRecord) and afterCreate() (CreateRecord) for each resource. I started with this but the problem I faced is in the OP. I need to restructure the whole db and do a lot of refactoring for spatie to work.
wyChoong
wyChoong17mo ago
but, i did say the effort and time will be the same and spatie's are more flexible is your app still in development or already in production which requires data migration? if still in development, i guess the answer is pretty straightforward but its your decision
Kiran Timsina
Kiran TimsinaOP17mo ago
It's in production. a few gigabyes of db. Migrating will be a big pain. I'll see what I can do, and share if I am able to find a solution.
toeknee
toeknee17mo ago
If you write a job, you can then run existing media through it with a command to find existing media and pass it into the job. You'll also then know if they failed or not. Traits would be a good solution if you are not using a job queue however.
Kiran Timsina
Kiran TimsinaOP17mo ago
trait ImageUpload
{
public static function make($field, $label = null): FileUpload
{
Image::configure(['driver' => 'imagick']);
return FileUpload::make($field)
->image()
->imagePreviewHeight(200)
->imageResizeTargetHeight(FileService::ORIGINAL_SIZE)
->imageResizeMode('contain')
->imageResizeUpscale(false)
->enableOpen()
->maxSize(FileService::MAX_UPLOAD_SIZE)
->directory(fn ($livewire) => FileService::getUploadDirectory(class_basename($livewire->record)))
->label($label ?? null)
->getUploadedFileNameForStorageUsing(function (TemporaryUploadedFile $file, $get): string {
return (string) FileService::filename($file, $get('slug') ?: $get('name'));
});
}
}
trait ImageUpload
{
public static function make($field, $label = null): FileUpload
{
Image::configure(['driver' => 'imagick']);
return FileUpload::make($field)
->image()
->imagePreviewHeight(200)
->imageResizeTargetHeight(FileService::ORIGINAL_SIZE)
->imageResizeMode('contain')
->imageResizeUpscale(false)
->enableOpen()
->maxSize(FileService::MAX_UPLOAD_SIZE)
->directory(fn ($livewire) => FileService::getUploadDirectory(class_basename($livewire->record)))
->label($label ?? null)
->getUploadedFileNameForStorageUsing(function (TemporaryUploadedFile $file, $get): string {
return (string) FileService::filename($file, $get('slug') ?: $get('name'));
});
}
}
This is my trait to upload image. It works like I expect it to. I need to figure out a way to trigger an event when upload is successfull so that i can call the ResizeImage Job that I've made to resize the images passed to it.
toeknee
toeknee17mo ago
Nice, have you seen laravel-workflows ? It's really handy for that type of approach, it will wait until you send a signal to then proceed to the next step etc It is likely overkill for one job through 😉
Kiran Timsina
Kiran TimsinaOP17mo ago
just checked it, it's totally overkill. All i need to do is figure out how dispatchEvent() works with filament fields. ImageUpload::make('image')->dispatchEvent() I see there's such option. have you used it?
AlexAnder
AlexAnder17mo ago
Create a laravel console comand, put in crn to run every 5 min to chreck and resize
Kiran Timsina
Kiran TimsinaOP17mo ago
that's not feasible as I will have to check for changes in several dozen models. I will explore more on if I can queue a job after file is uploaded.
wyChoong
wyChoong17mo ago
what is the problem with using model observer? you probably can use the updating event to do the checking of new and old value to replace beforeSave, so whenever filament update your record it gets triggered to dispatch the job you can then extract the logic into trait and add the trait to the required models and let the model boot to register it
Kiran Timsina
Kiran TimsinaOP17mo ago
thought about this too. But then again there is no consistency with the field names across the models. There are about 200 models and about 50 of them have images field. I am thinking about defining what fields are images in each model so the observer can watch for changes in those fields only.
wyChoong
wyChoong17mo ago
you can define a property to configure the image field name, and if set then it will use that
Kiran Timsina
Kiran TimsinaOP17mo ago
Done! I won't have to use spatie's package now. This is much faster (since image is loaded along with the model = much faster query for ecommerce application like mine) and offer more customization than it. Thanks a tons everyone!
Want results from more Discord servers?
Add your server