5

I have the following JS script :

jQuery('#form-recherche').submit(ajaxSubmit);

        function ajaxSubmit(){

            var newFormRecherche = jQuery(this).serialize();

            jQuery.ajax({
                type:"post",
                data: { 
                    action: "mon_action",
                    newFormRecherche: newFormRecherche,
                },

                url: ajaxurl,
                success: function(response){
                    console.log(response);
                }
            });

        return false;
        }

On PHP side :

    add_action( 'wp_ajax_mon_action', 'mon_action' );
    add_action( 'wp_ajax_nopriv_mon_action', 'mon_action' );

    function mon_action() {

        if (isset($_POST["newFormRecherche"])) {
            $field1= $_POST["field1"];
            $field2= $_POST["field2"];
        }
    }

As you guessed, I'm able to access $_POST["newFormRecherche"] while I can't retrieve $_POST["field1"] nor $_POST["field2"].

jQuery serialize() works properly : I tested newFormRecherche var with an alert and it is displayed under the right form : $field1=whatever&$field2=anything.

Normally, I shouldn't have to parse the results in order to access the $_POST[] variables, according to what I've read here, but obviously it's not working. Where does this problem come from ? Should I use something else than data to pass my arguments ?

EDIT : $_POST["newFormRecherche"] exists on PHP side, and contains the expected string $field1=whatever&$field2=anything.

EDIT #2 : Here is an update, according to the interesting remark from @czerspalace and the very detailed post from @bosco. I try here to put what they said in a nutshell, and give some solutions.

The problem here was a double serialization, one made "by hand", one made by jQuery while making the AJAX call. The fact that the $_POST[] vars can't be properly retrieved on server side comes from data, that has to match the Wordpress formalism, that is to say : an action (which is a PHP function) and the data sent (usually, from a form).

  • Solution by @Bosco - Use the jQuery method serializeArray(). In this case, the data sent is composed of 2 objects. In order to properly retrieve the fields on server side, I have to handle an associative array like this : $_POST['newFormRecherche'][0]['name'] and $_POST['newFormRecherche'][0]['value']. Same thing for the other fields (replacing [0] by other numbers). To solve that, @Bosco proposes below the formFieldsToObject function which is called on the data, when performing the AJAX call.

  • Solution by @czerspalace - Use the serialize() jQuery method, and make a manual deserialization on server side, using parse_str( $_POST[ 'newFormRecherche' ], $newFormRecherche ); in order to be able to retrieve the fields I want : $newFormRecherche['field1'],...and so on.

In both cases, the data on server side has to be properly sanitized, as data sent by an user via forms always must be. It implies : checking field types, checking (and even truncate) fields length..., in other words : never trust the user.

EDIT #3 : In the case you use FormData, make sure you add this line within your AJAX call : processData: false,. However, I didn't implement a full solution with this technique.

  • What if you try $_POST["newFormRecherche"]["field1"] anf $_POST["newFormRecherche"]["field2"] – czerspalace Apr 28 '16 at 18:17
  • I get a warning at the involved lines : Warning: Illegal string offset 'field1'... – Fafanellu Apr 28 '16 at 18:37
  • please do the minimum of debugging before to ask a question. that means looking at every variable (like $_POST["newFormRecherche"]) to see if the contains the expected values and writing these values in the question. look also this page : stackoverflow.com/questions/12769982/… – mmm Apr 28 '16 at 18:57
  • 1
    You probably need to parse the $_POST['newFormRecherche'] like parse_str($_POST["newFormRecherche"], $output); and then try $output["field1"] – czerspalace Apr 28 '16 at 19:00
  • thanks a lot, it works ! though I don't understand why I couldn't access the variables "normally". In a previous version of this function, "action" vas outside the data parameter, and I was able to retrieve $_POST['field1']. – Fafanellu Apr 28 '16 at 19:55
7

Problem

"Serialization" is the act of turning a data-object into a string-representation. jQuery automatically serializes the data property before sending an AJAX request. The server then deserializes the GET querystring from the URL, and the request body for POST requests before populating PHP's request variables.

Your code functioned as expected when data consisted solely of your serialized form data (i.e. data: newFormRecherche,) as the data was already in a string format, and thus could not be further serialized. The server's deserialization pass then properly parsed your form data into request variables.

However, when the data argument is an object jQuery must serialize it in order to pass it to a server. As a property of that object, your pre-serialized form data is handled as though it were any other string - specifically, it is escaped in such a manner to prevent it from being "mistaken" for a serialized object. So when the server then deserializes data, newFormRecherche is deserialized into the value which jQuery originally received it as - that is, a string - and thus requires the second pass of deserialization that @czerspalace alluded to in the comments in order to produce an associative array of form data.


Solutions

- jQuery

To prevent the double-serialization of your form data, acquire it as an array of key/value pairs instead of a serialized string by invoking jQuery's .serializeArray() method on the form element instead of .serialize().

While jQuery is capable of properly serializing this key/value-array-format of data, it seems it chokes when such an array is nested as property of a data object (instead sending the string '[object Object]' in place of each key/value pair), as well as when attempting to nest such a key/value-array inside another. As such, I think the best way to send form data as a part of multi-dimensional data is to convert the key/value-array into an object.

All of this can be done as follows:

function ajaxSubmit() {
  var newFormRecherche = jQuery( this ).serializeArray();

  jQuery.ajax({
    type:"POST",
    data: { 
      action: "mon_action",
      newFormRecherche: formFieldsToObject( newFormRecherche )
    },
    url: ajaxurl,
    success: function( response ){
      console.log( response );
    }
  });

  return false;
}

function formFieldsToObject( fields ) {
  var product = {};

  for( var i = 0; i < fields.length; i++ ) {
    var field = fields[ i ];

    if( ! product.hasOwnProperty( field.name ) ) {
      product[ field.name ] = field.value;
    }
    else {
      if( ! product[ field.name ] instanceof Array )
        product[ field.name ] = [ product[ field.name ] ];

      product[ field.name ].push( field.value );
    }
  }

  return product;
}

- HTML5 FormData

Alternately, if you need only support modern browsers or don't mind loading a polyfill to support older ones, just use the FormData object to pass your form data as a proper data object:

function ajaxSubmit() {
  var newFormRecherche = new FormData( this );

  jQuery.ajax({
    type:"POST",
    data: { 
      action: "mon_action",
      newFormRecherche: newFormRecherche
    },
    url: ajaxurl,
    success: function( response ){
      console.log( response );
    }
  });

  return false;
}

- Server-Side Double-Deserialization

As @czerspalace suggests in the comments, accounting for the double-serialization by simply manually deserializing the form data on the server is also an entirely valid solution:

add_action( 'wp_ajax_mon_action', 'mon_action' );
add_action( 'wp_ajax_nopriv_mon_action', 'mon_action' );

function mon_action() {
  if( isset( $_POST[ 'newFormRecherche' ] ) ) {
    parse_str( $_POST[ 'newFormRecherche' ], $newFormRecherche );
    die( json_encode( $newFormRecherche ) );
  }
}

I'm inclined to think that the other approaches are more "professional" in that all of the data sent to the server is packaged in a consistent and expected format - no additional "decoding" is necessary on the server's part, improving code modularity.

But code modularity and subjective "professionalism" aren't always the most important factors - sometimes the most simple solution is the most appropriate.


Remember to validate and sanitize request data on the server before you use it in order to mitigate security vulnerabilities.

  • thank you very much for this clear and clean explanation, I understand where the problem was now ! Though, you wrote that "jQuery will properly serialize these key/value pairs into request variables", but I still can't access them and I can no longer parse them as they aren't strings anymore. What is the cleanest solution to retrieve their values ? – Fafanellu Apr 29 '16 at 8:34
  • @Fafanellu it seems I gave jQuery more credit than is due - it doesn't like it when you use that key/value array in nested data structures, and actually fails to serialize it properly in such situations. I've added a little helper function that should set everything straight - the data should be accessible in PHP via a properly structured $_POST array, i.e. $_POST["newFormRecherche"]["field1"]. You can echo json_encode( $_POST ); in your AJAX handler, or var_dump( $_POST ); to see the exact structure of the form data. – bosco Apr 29 '16 at 20:14
  • thanks for this very interesting topic ! I've updated my initial post to take into account the constructive comments made here – Fafanellu Apr 30 '16 at 8:25
  • By the way, what is the purpose of die( json_encode( $newFormRecherche ) ) ? – Fafanellu Apr 30 '16 at 8:55
  • That's just a temporary placeholder for whatever your code would be =) . die() immediately ends a PHP script's execution, optionally echo()ing an argument, if supplied - so it will immediately end the AJAX request, responding with the form-data that the server received as a JSON object (though probably with the wrong Content-Type header, but jQuery shouldn't mind). In combination the success callback you specified in your JavaScript which console.log()s the server's response, you should end up with a nice, navigable readout of the form data in your browser's JavaScript console. – bosco Apr 30 '16 at 9:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.