How to solve this common problem

Lets say you do some business logic and insert a record in the database, if the record was saved successfully you proceed to save a file in the filesystem, e.g an image for a user’s profile.

How would you guys handle the failure of storing the file in the file system?

Do you remove the newly inserted record and report an error back to the user? If this is the case, then do you guys use transactions and commit after the file is saved successfully?

thanks in advance for any input.

I validate the record, but I don’t save it until the file is successfully saved.

In Yii:


if($model->validate()) {

  if(file_was_saved) {

	$model->save(false); // false means do not validate as we already validated it earlier

  }

}



So…

For one, you shouldn’t tie the filesytem success to your database success. They can both fail independently and you will have to code for it.

What I prefer, and to me is a much cleaner approach, is to accept the file upload via some ajax method and return a file name where the uploader stored your file.

Typically, I have a /public/data folder into which I allow uploading (and no executing!). Once my ajax handler stores a file there it returns the file name (which is an md5 hash of various data points like user id, file size, etc) to the consumer.

So then the user clicks save or whatever, my action accepts the file name from the POST and validates it (with my md5 hash) then stores the row and the file name in the row or in a child table. It can be moved to a more permanent home or not. Your call.

Finally, all of my apps have background jobs. I tend to queue everything to keep the user engaged. One of my background scripts checks the last mod time of the files in /public and if they are over 10 minutes old, they are removed/deleted.

Hope that helps!

hi,

But then i got back to my initial problem, what happens in the case the move to a permanent location fails? Do you assume it will never fail?

Hi Jacmoe,

What if for some reason the record fails to save?

Do you add code to remove the file or do you assume it will never fail?

A more difficult scenario would be if more than 1 image needs to be created after a record is successfully saved.

For example, a user uploads a new profile image and you need to create 3 thumbnails of different size.

How would you handle failure of creating one of the 3 thumbnails?

Is it not same as


created=false


if(create thumb #1){

  created=true

}


if(create thumb #2){

 created=true

}


if(create thumb #3){

 created=true

}


save(created)

the thing is handling of errors, what would you do if thumb2 fails?

do you delete thumb 1?

what if saving the record in the DB fails? do you delete thumb 1, 2, 3

If it validates, it will almost always succeed in saving.

However, if it doesn’t, the file should be deleted.

If it doesn’t validate, the entire file saving process is skipped.

As in the last section of algorithm, the result will be saved only to database when these are set to true.

so, the extended solution will be




created=false


if(create thumb #1){

  created=true

}


if(create thumb #2){

 created=true

}


if(create thumb #3){

 created=true

}


if(created)

save(created)

else

delete all saved files



we can also, set the error cased by which part of code i.e. what is failed.

The logic is flawed as ‘created’ will be true if any creates fails.

Better to check for fail:




failed=false


if(!create thumb #1){

  failed=true

}


if(!create thumb #2){

    failed=true

 }


if(!create thumb #3){

  failed=true

 }


if(!failed)

save(created)

else

delete all saved files



When flying over the topic i only had one word in mind: afterSave. It’s situations like this, when i really love the events that some Yii components provide. You could for example try a logic like this:




public $uploadedFile;


public function afterSave()

{

    if (!($this->uploadeFile instanceof CUploadedFile) || $this->saveUploadedFile())

        return parent::afterSave();

    else

        $this->delete();

}