Recuperar datos POST de una llamada AJAX

28 abr 2016, 21:08:41
Vistas: 34.6K
Votos: 5

Tengo el siguiente script JS:

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;
        }

Del lado de PHP:

    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"];
        }
    }

Como habrás adivinado, puedo acceder a $_POST["newFormRecherche"] mientras que no puedo recuperar $_POST["field1"] ni $_POST["field2"].

El método serialize() de jQuery funciona correctamente: probé la variable newFormRecherche con una alerta y se muestra en el formato correcto: $field1=whatever&$field2=anything.

Normalmente, no debería tener que analizar los resultados para acceder a las variables $_POST[], según lo que he leído aquí, pero obviamente no está funcionando. ¿De dónde viene este problema? ¿Debería usar algo diferente a data para pasar mis argumentos?

EDICIÓN: $_POST["newFormRecherche"] existe del lado de PHP y contiene la cadena esperada $field1=whatever&$field2=anything.

EDICIÓN #2: Aquí hay una actualización, según la interesante observación de @czerspalace y la publicación muy detallada de @bosco. Intento aquí resumir lo que dijeron y dar algunas soluciones.

El problema aquí era una doble serialización, una hecha "a mano" y otra hecha por jQuery al realizar la llamada AJAX. El hecho de que las variables $_POST[] no puedan recuperarse correctamente en el lado del servidor proviene de data, que debe coincidir con el formalismo de WordPress, es decir: una action (que es una función PHP) y los datos enviados (generalmente, desde un formulario).

  • Solución de @Bosco - Usar el método jQuery serializeArray(). En este caso, los datos enviados se componen de 2 objetos. Para recuperar correctamente los campos en el lado del servidor, tengo que manejar un array asociativo así: $_POST['newFormRecherche'][0]['name'] y $_POST['newFormRecherche'][0]['value']. Lo mismo para los otros campos (reemplazando [0] por otros números). Para resolver esto, @Bosco propone a continuación la función formFieldsToObject que se llama en los datos al realizar la llamada AJAX.

  • Solución de @czerspalace - Usar el método serialize() de jQuery y hacer una deserialización manual en el lado del servidor, usando parse_str( $_POST[ 'newFormRecherche' ], $newFormRecherche ); para poder recuperar los campos que quiero: $newFormRecherche['field1'],... y así sucesivamente.

En ambos casos, los datos en el lado del servidor deben ser sanitizados adecuadamente, como siempre deben serlo los datos enviados por un usuario a través de formularios. Esto implica: verificar tipos de campos, verificar (e incluso truncar) la longitud de los campos..., en otras palabras: nunca confiar en el usuario.

EDICIÓN #3: En caso de que uses FormData, asegúrate de agregar esta línea dentro de tu llamada AJAX: processData: false,. Sin embargo, no implementé una solución completa con esta técnica.

7
Comentarios

¿Qué tal si pruebas $_POST["newFormRecherche"]["field1"] y $_POST["newFormRecherche"]["field2"]?

czerspalace czerspalace
28 abr 2016 21:17:57

Recibo una advertencia en las líneas involucradas: Warning: Illegal string offset 'field1'...

Fafanellu Fafanellu
28 abr 2016 21:37:26

por favor haz un mínimo de depuración antes de hacer una pregunta. Eso significa revisar cada variable (como $_POST["newFormRecherche"]) para ver si contienen los valores esperados y escribir esos valores en la pregunta. Mira también esta página: http://stackoverflow.com/questions/12769982/reference-what-does-this-error-mean-in-php

mmm mmm
28 abr 2016 21:57:26

Probablemente necesites analizar el $_POST['newFormRecherche'] como parse_str($_POST["newFormRecherche"], $output); y luego intentar $output["field1"]

czerspalace czerspalace
28 abr 2016 22:00:15

¡muchas gracias, funciona! aunque no entiendo por qué no podía acceder a las variables "normalmente". En una versión anterior de esta función, "action" estaba fuera del parámetro data, y podía recuperar $_POST['field1'].

Fafanellu Fafanellu
28 abr 2016 22:55:36

Un resumen sólido en EDIT #2, @Fafanellu! Como nota final, agregué una pequeña función formFieldsToObject() a la solución de jQuery que debería solucionar el problema engorroso de $_POST['newFormRecherche'][0]['name'] / $_POST['newFormRecherche'][0]['value'] =]

bosco bosco
30 abr 2016 12:33:34

¡Bien hecho, una solución limpia y sencilla! ¿Por qué no existe una función así de forma nativa?

Fafanellu Fafanellu
30 abr 2016 12:43:32
Mostrar los 2 comentarios restantes
Todas las respuestas a la pregunta 1
9

Problema

"Serialización" es el acto de convertir un objeto de datos en una representación de cadena. jQuery serializa automáticamente la propiedad data antes de enviar una solicitud AJAX. Luego, el servidor deserializa la cadena de consulta GET de la URL y el cuerpo de la solicitud para las solicitudes POST antes de poblar las variables de solicitud de PHP.

Tu código funcionó como se esperaba cuando data consistía únicamente en los datos de tu formulario serializados (es decir, data: newFormRecherche,) ya que los datos ya estaban en formato de cadena y, por lo tanto, no podían serializarse más. La pasada de deserialización del servidor luego analizó correctamente los datos del formulario en variables de solicitud.

Sin embargo, cuando el argumento data es un objeto, jQuery debe serializarlo para pasarlo al servidor. Como propiedad de ese objeto, tus datos de formulario pre-serializados se manejan como cualquier otra cadena; específicamente, se escapan de manera que evita que sean "confundidos" con un objeto serializado. Entonces, cuando el servidor deserializa data, newFormRecherche se deserializa en el valor que jQuery originalmente recibió, es decir, una cadena, y por lo tanto requiere una segunda pasada de deserialización, como mencionó @czerspalace en los comentarios, para producir un arreglo asociativo de los datos del formulario.


Soluciones

- jQuery

Para evitar la doble serialización de los datos del formulario, adquiérelos como un arreglo de pares clave/valor en lugar de una cadena serializada invocando el método .serializeArray() de jQuery en el elemento del formulario en lugar de .serialize().

Si bien jQuery es capaz de serializar correctamente este formato de arreglo clave/valor de data, parece que falla cuando dicho arreglo está anidado como propiedad de un objeto data (enviando la cadena '[object Object]' en lugar de cada par clave/valor), así como al intentar anidar dicho arreglo clave/valor dentro de otro. Por lo tanto, creo que la mejor manera de enviar datos de formulario como parte de datos multidimensionales es convertir el arreglo clave/valor en un objeto.

Todo esto se puede hacer de la siguiente manera:

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

Alternativamente, si solo necesitas soportar navegadores modernos o no te importa cargar un polyfill para soportar versiones antiguas, usa el objeto FormData para pasar los datos del formulario como un objeto de datos adecuado:

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;
}

- Deserialización Doble en el Servidor

Como sugiere @czerspalace en los comentarios, tener en cuenta la doble serialización simplemente deserializando manualmente los datos del formulario en el servidor también es una solución totalmente válida:

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 ) );
  }
}

Estoy inclinado a pensar que los otros enfoques son más "profesionales" en el sentido de que todos los datos enviados al servidor están empaquetados en un formato consistente y esperado; no se necesita "decodificación" adicional por parte del servidor, mejorando la modularidad del código.

Pero la modularidad del código y el "profesionalismo" subjetivo no siempre son los factores más importantes; a veces la solución más simple es la más apropiada.


Recuerda validar y sanitizar los datos de la solicitud en el servidor antes de usarlos para mitigar vulnerabilidades de seguridad.

29 abr 2016 01:51:31
Comentarios

muchas gracias por esta explicación clara y limpia, ¡ahora entiendo dónde estaba el problema! Aunque escribiste que "jQuery serializará correctamente estos pares clave/valor en variables de solicitud", todavía no puedo acceder a ellos y ya no puedo analizarlos ya que ya no son cadenas. ¿Cuál es la solución más limpia para recuperar sus valores?

Fafanellu Fafanellu
29 abr 2016 11:34:54

@Fafanellu parece que le di a jQuery más crédito del que merece - no le gusta cuando usas ese array de clave/valor en estructuras de datos anidadas, y de hecho falla al serializarlo correctamente en tales situaciones. He añadido una pequeña función auxiliar que debería arreglarlo todo - los datos deberían ser accesibles en PHP a través de un array $_POST correctamente estructurado, es decir, $_POST["newFormRecherche"]["field1"]. Puedes usar echo json_encode( $_POST ); en tu manejador AJAX, o var_dump( $_POST ); para ver la estructura exacta de los datos del formulario.

bosco bosco
29 abr 2016 23:14:44

¡gracias por este tema tan interesante! He actualizado mi publicación inicial para tener en cuenta los comentarios constructivos hechos aquí

Fafanellu Fafanellu
30 abr 2016 11:25:28

Por cierto, ¿cuál es el propósito de die( json_encode( $newFormRecherche ) )?

Fafanellu Fafanellu
30 abr 2016 11:55:29

Eso es solo un marcador temporal para lo que sería tu código =). die() termina inmediatamente la ejecución de un script PHP, opcionalmente echo()ando un argumento si se proporciona, por lo que finalizará inmediatamente la solicitud AJAX, respondiendo con los datos del formulario que el servidor recibió como un objeto JSON (aunque probablemente con la cabecera Content-Type incorrecta, pero a jQuery no le debería importar). En combinación con la función de retorno success que especificaste en tu JavaScript, que hace console.log() de la respuesta del servidor, deberías terminar con una salida clara y navegable de los datos del formulario en la consola JavaScript de tu navegador.

bosco bosco
30 abr 2016 12:02:18

¡Ok, gracias! En realidad no escribí todo el código PHP, pero había un die() vacío al final. Si entiendo correctamente, el propósito es terminar la solicitud AJAX lo antes posible para enviar los datos de vuelta.

Fafanellu Fafanellu
30 abr 2016 12:05:27

¡Básicamente! Esta página del Codex lo menciona brevemente. La idea general es que después de manejar la solicitud AJAX y haber hecho echo() de una respuesta, no hay mucha razón para continuar con la ejecución de WordPress. Si se permite que WordPress siga ejecutándose normalmente, existe el riesgo de que código ejecutado después de tu manejador AJAX haga echo() de algo más en la respuesta, lo cual probablemente rompería tu JavaScript cuando el callback success intente procesarlo.

bosco bosco
30 abr 2016 12:21:52

wp_die() se considera una mejor alternativa que die(). Es raro usar cualquiera de los dos fuera de los manejadores AJAX, excepto para solución temporal de problemas - ¡casi siempre hay una mejor manera de manejar un problema o reportar un error que simplemente matar la ejecución del script!

bosco bosco
30 abr 2016 12:26:01

Una vez más, ¡gracias por tus explicaciones claras! He actualizado mi publicación original para incluir tus sugerencias :)

Fafanellu Fafanellu
30 abr 2016 12:35:09
Mostrar los 4 comentarios restantes