Получение данных POST из AJAX-запроса

28 апр. 2016 г., 21:08:41
Просмотры: 34.6K
Голосов: 5

У меня есть следующий скрипт 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;
        }

На стороне 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"];
        }
    }

Как вы догадались, я могу получить доступ к $_POST["newFormRecherche"], но не могу получить ни $_POST["field1"], ни $_POST["field2"].

jQuery метод serialize() работает правильно: я протестировал переменную newFormRecherche с помощью alert, и она отображается в правильном формате: $field1=whatever&$field2=anything.

Обычно мне не нужно парсить результаты для доступа к переменным $_POST[], согласно тому, что я прочитал здесь, но очевидно это не работает. В чем проблема? Должен ли я использовать что-то другое вместо data для передачи моих аргументов?

РЕДАКТИРОВАНИЕ: $_POST["newFormRecherche"] существует на стороне PHP и содержит ожидаемую строку $field1=whatever&$field2=anything.

РЕДАКТИРОВАНИЕ #2: Вот обновление, согласно интересному замечанию от @czerspalace и подробному посту от @bosco. Попробую здесь кратко изложить то, что они сказали, и предложить несколько решений.

Проблема здесь была в двойной сериализации - одна сделана "вручную", другая выполнена jQuery при выполнении AJAX-запроса. Тот факт, что переменные $_POST[] не могут быть правильно получены на стороне сервера, связан с data, которая должна соответствовать формализму WordPress, а именно: action (который является PHP-функцией) и отправляемые данные (обычно из формы).

  • Решение от @Bosco - Использовать jQuery метод serializeArray(). В этом случае отправленные данные состоят из 2 объектов. Чтобы правильно получить поля на стороне сервера, я должен работать с ассоциативным массивом так: $_POST['newFormRecherche'][0]['name'] и $_POST['newFormRecherche'][0]['value']. То же самое для других полей (заменяя [0] на другие числа). Для решения этого @Bosco предлагает ниже функцию formFieldsToObject, которая вызывается для данных при выполнении AJAX-запроса.

  • Решение от @czerspalace - Использовать jQuery метод serialize() и выполнить ручную десериализацию на стороне сервера, используя parse_str( $_POST[ 'newFormRecherche' ], $newFormRecherche );, чтобы иметь возможность получить нужные поля: $newFormRecherche['field1'] и так далее.

В обоих случаях данные на стороне сервера должны быть правильно санитизированы, как всегда должны быть очищены данные, отправленные пользователем через формы. Это включает: проверку типов полей, проверку (и даже обрезку) длины полей... другими словами: никогда не доверяйте пользователю.

РЕДАКТИРОВАНИЕ #3: В случае использования FormData убедитесь, что вы добавили эту строку в ваш AJAX-запрос: processData: false,. Однако, я не реализовал полное решение с этой техникой.

7
Комментарии

Что если попробовать $_POST["newFormRecherche"]["field1"] и $_POST["newFormRecherche"]["field2"]

czerspalace czerspalace
28 апр. 2016 г. 21:17:57

Я получаю предупреждение на указанных строках: Warning: Illegal string offset 'field1'...

Fafanellu Fafanellu
28 апр. 2016 г. 21:37:26

пожалуйста, выполните минимальную отладку перед тем как задавать вопрос. Это означает проверку всех переменных (например $_POST["newFormRecherche"]) на соответствие ожидаемым значениям и указание этих значений в вопросе. Также посмотрите эту страницу: http://stackoverflow.com/questions/12769982/reference-what-does-this-error-mean-in-php

mmm mmm
28 апр. 2016 г. 21:57:26

Вероятно, вам нужно разобрать $_POST['newFormRecherche'] с помощью parse_str($_POST["newFormRecherche"], $output);, а затем попробовать $output["field1"]

czerspalace czerspalace
28 апр. 2016 г. 22:00:15

большое спасибо, это работает! хотя я не понимаю, почему я не мог получить доступ к переменным "обычным" способом. В предыдущей версии этой функции "action" был вне параметра data, и я мог получить доступ через $_POST['field1'].

Fafanellu Fafanellu
28 апр. 2016 г. 22:55:36

Отличное резюме в EDIT #2, @Fafanellu! В заключение, я добавил небольшую функцию formFieldsToObject() в решение с jQuery, которая должна исправить проблему с неудобным форматом $_POST['newFormRecherche'][0]['name'] / $_POST['newFormRecherche'][0]['value'] =]

bosco bosco
30 апр. 2016 г. 12:33:34

отличная работа, чистое и простое решение! почему такой функции нет встроенной?!

Fafanellu Fafanellu
30 апр. 2016 г. 12:43:32
Показать остальные 2 комментариев
Все ответы на вопрос 1
9

Проблема

"Сериализация" — это процесс преобразования объекта данных в строковое представление. jQuery автоматически сериализует свойство data перед отправкой AJAX-запроса. Затем сервер десериализует строку запроса GET из URL и тело запроса для POST-запросов перед заполнением переменных запроса в PHP.

Ваш код работал ожидаемо, когда data состоял только из сериализованных данных формы (т.е. data: newFormRecherche,), так как данные уже были в строковом формате и не могли быть сериализованы повторно. Десериализация на сервере корректно преобразовывала данные формы в переменные запроса.

Однако, когда аргумент data является объектом, jQuery должен его сериализовать для передачи на сервер. Будучи свойством этого объекта, ваши предварительно сериализованные данные формы обрабатываются как обычная строка — а именно, экранируются таким образом, чтобы предотвратить их "ошибочное" распознавание как сериализованного объекта. Поэтому при десериализации data сервером, newFormRecherche преобразуется в исходное строковое значение, и для получения ассоциативного массива данных формы требуется второй проход десериализации, о котором упоминал @czerspalace в комментариях.


Решения

- jQuery

Чтобы избежать двойной сериализации данных формы, получите их как массив пар ключ/значение, используя метод jQuery .serializeArray() вместо .serialize().

Хотя jQuery может корректно сериализовать такой массив пар ключ/значение, она не справляется, когда этот массив является вложенным свойством объекта data (вместо этого отправляя строку '[object Object]' вместо каждой пары ключ/значение), а также при попытке вложить такой массив в другой. Поэтому лучший способ отправить данные формы как часть многомерных данных — преобразовать массив пар ключ/значение в объект.

Всё это можно сделать следующим образом:

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

Альтернативно, если вам нужно поддерживать только современные браузеры или вы не против использовать полифил для старых, можно использовать объект FormData для передачи данных формы в виде объекта:

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

- Двойная десериализация на стороне сервера

Как предложил @czerspalace в комментариях, обработка двойной сериализации путём ручной десериализации данных формы на сервере также является полностью валидным решением:

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

Я склонен считать, что другие подходы более "профессиональны", так как все данные отправляются на сервер в едином и ожидаемом формате — серверу не требуется дополнительная "декодировка", что улучшает модульность кода.

Но модульность кода и субъективная "профессиональность" не всегда являются главными факторами — иногда самое простое решение оказывается наиболее подходящим.


Не забывайте проверять и очищать данные запроса на сервере перед использованием, чтобы снизить риски уязвимостей безопасности.

29 апр. 2016 г. 01:51:31
Комментарии

большое спасибо за это ясное и понятное объяснение, теперь я понимаю, в чем была проблема! Хотя вы написали, что "jQuery правильно сериализует эти пары ключ/значение в переменные запроса", я все равно не могу получить к ним доступ и больше не могу их парсить, так как они больше не являются строками. Какое самое чистое решение для получения их значений?

Fafanellu Fafanellu
29 апр. 2016 г. 11:34:54

@Fafanellu похоже, я переоценил возможности jQuery - она не очень хорошо работает с массивами ключ/значение во вложенных структурах данных и фактически не может правильно их сериализовать в таких ситуациях. Я добавил небольшую вспомогательную функцию, которая должна исправить ситуацию - данные должны быть доступны в PHP через правильно структурированный массив $_POST, например $_POST["newFormRecherche"]["field1"]. Вы можете использовать echo json_encode( $_POST ); в вашем AJAX обработчике или var_dump( $_POST ); чтобы увидеть точную структуру данных формы.

bosco bosco
29 апр. 2016 г. 23:14:44

спасибо за эту очень интересную тему! Я обновил свой первоначальный пост, чтобы учесть конструктивные комментарии, оставленные здесь

Fafanellu Fafanellu
30 апр. 2016 г. 11:25:28

Кстати, для чего нужен die( json_encode( $newFormRecherche ) )?

Fafanellu Fafanellu
30 апр. 2016 г. 11:55:29

Это просто временная заглушка вместо вашего кода =). Функция die() немедленно завершает выполнение PHP-скрипта, при этом может вывести аргумент с помощью echo(), если он указан - таким образом она сразу завершает AJAX-запрос, возвращая полученные сервером данные формы в виде JSON-объекта (хотя, вероятно, с неправильным заголовком Content-Type, но jQuery не должен возражать). В сочетании с callback-функцией success, которую вы указали в JavaScript и которая выводит ответ сервера через console.log(), вы получите удобное для просмотра представление данных формы в консоли JavaScript вашего браузера.

bosco bosco
30 апр. 2016 г. 12:02:18

Ок, спасибо! На самом деле я не писал весь PHP-код, но в конце был пустой die(). Если я правильно понимаю, цель - как можно быстрее завершить AJAX-запрос, чтобы отправить данные обратно.

Fafanellu Fafanellu
30 апр. 2016 г. 12:05:27

Именно так! Эта страница Codex кратко затрагивает этот вопрос. Основная идея в том, что после обработки AJAX-запроса и вывода ответа через echo(), нет особого смысла продолжать выполнение WordPress. Если позволить WordPress работать дальше в обычном режиме, есть риск что код, выполняемый после вашего AJAX-обработчика, может что-то ещё вывести через echo() в ответ, что скорее всего сломает ваш JavaScript, когда callback success попытается его обработать.

bosco bosco
30 апр. 2016 г. 12:21:52

wp_die() считается более предпочтительной альтернативой die(). Редко когда стоит использовать что-то из этого вне AJAX-обработчиков, за исключением временной отладки - почти всегда есть лучший способ обработать проблему или сообщить об ошибке, чем просто прерывать выполнение скрипта!

bosco bosco
30 апр. 2016 г. 12:26:01

Ещё раз спасибо за ваши понятные объяснения! Я обновил свой исходный пост, чтобы включить ваши предложения :)

Fafanellu Fafanellu
30 апр. 2016 г. 12:35:09
Показать остальные 4 комментариев