cakePHP saveall() and transactions, jQuery form plugin AJAX error handling

cakePHP, saveAll() and Transactions
MySQL transactions and cakePHP, the two play nicely together. Its very easy to wrap your inserts and updates in transactions with cake:


try {
    if (!Model->save()) throw new Exception('Model did not save');
    if (!Model->subModel->save()) throw new Exception('Sub Model did not save');
catch (Exception $e) {

Or something like that. What you don't want to do is substitute either of the save()'s for saveall()'s. saveAll() uses a transaction internally to save all the data, and you can pass it some useful options such as 'validate'=>'first' which will validate all the objects before saving any.

At least you don't want to substitute unless you pass the option: 'atomic'=>false which is going to nullify the transaction inside saveAll() so your outer transaction isn't committed prematurely - which is what I was finding was happening.

jQuery, AJAX and the Form plugin
The jQuery form plugin is super useful. You just plug it in and then forms which you want to be submitted via AJAX are done so - just like that.

The way it works with file uploads is slightly different to how it works on a standard form. File uploads can't be made over AJAX:

Since it is not possible to upload files using the browser's XMLHttpRequest object, the Form Plugin uses a hidden iframe element to help with the task

So, even if you have a file upload enabled form, if you don't add a file for upload the form plugin submits the form data via AJAX, if you have added a file then the hidden iframe is used.

The only problem I have come across is implementing a failure mechanism for this file upload.

In the options for the form plugin you can add .ajax() options of the jQuery object. In that lot you will see the 'error' option which can specify a callback upon AJAX error - if the request fails.

In my cakePHP code I was triggering this by returning a 500 header, and this works fine with the form plugin submitting a standard form via AJAX. But when I added files, and the form plugin started using the iframe, thats when I noticed although the code was still returning a 500 header server side, that didn't seem to trigger the error callback.

In fact, digging in a little deeper, I created an ajaxSuccess callback to see what sort of header the code thought it was getting:

$("#loading").ajaxSuccess(function(event, XMLHttpRequest, ajaxOptions){

And the result was not 500 - it was 0.

I'm not entirely sure about the inner workings of the form plugin at all and how it uses iframes to upload files and processes the result. My guess is that when you return your data from the server, and that data is placed in the iFrame, the response header is lost. So you're best to return an error in the response and process that response as a success. Which is fine for most occassions, not on my occassion unfortuantely so I've hacked my code into a mess to get around it.

But there you go, if you are using the form plugin submitting files for upload, the error callback on the ajax request might not work for you and that might be why.

Any jQuery/cakePHP gurus are welcome to point out the shortcomings in my assessments... :-)