- PVSM.RU - https://www.pvsm.ru -
При подготовке материала по Symfony Form я решил уделить некоторое внимание теоретической части по работе с формами со стороны клиента – что они из себя представляют, как ведут себя браузеры при отправке, в каком формате путешествуют, в каком виде поступают на сервер.
Вводная часть несколько растянулась и в итоге вылилась в отдельную небольшую статью, которая, по моему мнению, может быть интересна и другим бэкенд-разработчикам (не только PHP или Symfony).
В тексте будут использоваться два похожих термина: поле формы и элемент формы. Оба в большинстве случаев взаимозаменяемы и обозначают некоторую часть формы. Но для удобства и порядка полями чаще будут называться части, которые содержат непосредственно значения и, вероятно, будут отправлены на сервер, а элементами – декоративные части формы.
Раньше работа с формами была в каком-то смысле проще: в том виде, в котором форма приходила к пользователю, в том же она отправлялась и обратно, но уже с заполненными полями. Бэкенд-разработчики точно знали какой набор данных им следует ожидать, всё было легко и предсказуемо. С широким распространением JS всё изменилось: сегодня уже никого не удивляет поведение приложения, при котором форма приходит с одним набором полей, а отправляется с совершенно иным, когда громоздкие формы имеют в своём составе небольшие динамические подформы, которые независимо от основной могут отправляться на сервер для обработки, когда выбранные файлы загружаются в фоновом режиме, в то время как пользователь заполняет остальную часть формы. Таким образом, всё большее количество логики переносится на сторону клиента, и это отлично, в первую очередь, конечно же, для пользователя. Для разработчика процесс работы с формами несколько усложнился. Но как бы тяжело ему не было, игнорировать развитие технологий сегодня невозможно, поэтому необходимо уметь эффективно их использовать.
Мы постоянно стремимся к автоматизации всего, с чем нам приходится работать, особенно рутинных задач, при этом пытаемся привнести в эти процессы некий порядок. Поэтому многих разработчиков несколько пугает динамичность на клиентский стороне, которая часто воспринимается ими как неконтролируемый хаос. Можно подумать, что у фронтенд-разработчиков развязаны руки, и они могут творить подобно художникам и скульпторам, их действия ничем не ограничены, а нам потом разбирать на сервере полёты их фантазии. Нередко так и случается, но это ошибочный, тупиковый путь. Это можно сравнить со schemaless-хранилищами данных: никто вас не ограничивает жёсткой структурой хранения данных, каждый из разработчиков в рамках одного проекта может придумать свой формат хранения, потом поменять его в зависимости от настроения, что в конечном счёте, скорее всего, приведёт к печальным последствиям. Если же вы ожидаете от своего продукта гибкости, легкой расширяемости и динамичного развития, вам необходимо будет придерживаться определённых правил, которые помогут поддерживать порядок в вашем приложении. То же самое вы в праве требовать и от фронтенд-разработчиков – творите, но придерживайтесь правил.
Итак, что вам, как бэкенд-разработчику, необходимо понять и принять:
Отлично, мы каким-то образом сгенерировали HTML для некоторой формы и отправили её пользователю. Заполнив поля и, возможно, изменив структуру формы, пользователю необходимо отправить её на сервер для обработки. Каким образом он может это сделать? Есть разные способы, большинство из которых потребует выполнения скриптов. Рассмотрим основные:
Способы в списке расположены в порядке возрастания прикладываемых фронтенд-разработчиком усилий, необходимых для отправки формы на сервер. Также можно условно разделить их на две группы: первые два способа составляют группу, в которой формированием и отправкой запроса занимается браузер, вторая группа включает последние два способа, при которых эти действия выполняет JS.
Для начала рассмотрим первую группу, оба способа которой мало чем отличаются друг от друга. В этом случае всю работу и ответственность на себя берёт браузер пользователя, причем действует он достаточно прямолинейно:
Определение отправляемой формы зависит от рассматриваемых нами способов. Если это нажатие на кнопку, форма определяется атрибутом form нажимаемой кнопки, а при его отсутствии – родительским для кнопки тегом form. Если же это вызов метода submit(), отправляемой является форма, связанная с объектом, для которого вызывается данный метод.
Атрибуты formmethod, formenctype и formaction нажимаемой кнопки переопределяют соответственно значения атрибутов method, enctype и action родительской формы.
Это очень простой процесс, но важно в нём разобраться, чтобы однозначно понимать, какие данные и в каком виде мы получаем на сервере.
Из всего списка наиболее интересен третий пункт – генерация пар ключ=значение. Каждый элемент формы должен иметь атрибут name, который будет использован браузером в качестве ключа. Рассмотрим на примере. Имеется следующая форма:
<form method="POST" enctype="application/x-www-form-urlencoded">
<input type="text" name="nickname" value="ghost">
<input type="password" name="password" value="123456">
</form>
В этом случае браузер сформирует две пары ключ=значение: nickname=ghost и password=123456. При отправке они будут склеены с помощью символа амперсанда (&), и в результате на сервер будет отправлен запрос, тело которого содержит строку:
nickname=ghost&password=123456
Получив такой запрос, PHP начнёт его разбор, примерный алгоритм которого можно посмотреть в описании функции parse_str [1]. В результате разбора будут определены два поля и их значения, они будут помещены в суперглобальный массив $_POST, который примет следующий вид:
$_POST = [
'nickname' => 'ghost',
'password' => '123456',
]
На что в этом процессе следует обратить внимание:
На данном правиле основан известный хак с полем типа hidden, которое размещается вместе с полем типа checkbox, для корректного определения его состояния при отправке формы. К концу данного списка станет понятно, где надо размещать скрытое поле и почему этот хак работает.
Также в запрос не будут включены значения кнопок, как обычных, так и submit, которые не участвуют в отправке формы, т.е. те кнопки, по которым не было совершено нажатие. Таким образом, если отправка выполняется методом submit(), в запрос не будет включена ни одна кнопка.
Не будут отправлены также поля, находящиеся в отключённом состоянии (имеющие атрибут disabled).
<form method="POST" enctype="application/x-www-form-urlencoded">
<input type="text" name="fullname" value="John Doe">
<input type="text" name="fullname" value="Mike Ross">
</form>
Многие предполагают, что браузер каким-то образом должен решить, какое одно из двух полей необходимо отправлять в запросе. Логика подсказывает, что отправлять необходимо последнее (в данном случае второе), которое перекрывает все предшествующие поля с тем же именем. Это предположение неверно!
Браузер вообще не волнует, есть в форме какие-то конфликты имён или их нет. Непустое имя поля не является критерием исключения данного поля из отправляемого запроса. Другими словами, для браузера оба поля являются корректными и оба будут включены в набор отправляемых полей, причем в том порядке, в котором они представлены в форме.
Таким образом, приведённая выше форма при отправке на сервер примет следующий вид:
fullname=John+Doe&fullname=Mike+Ross
Именно такой запрос и придёт на наш сервер. Вы можете проверить это, прочитав значение php://input. Что же с этим запросом будет делать PHP? Всё как обычно – разбирает строку и формирует массив $_POST. Но, как мы знаем, ключи в массивах уникальны, поэтому первоначальное значение «John Doe» будет перезаписано значением «Mike Ross». В итоге мы получим следующий результат:
$_POST = [
'fullname' => 'Mike Ross',
]
Теперь должно стать понятно, как работает хак со скрытым полем для checkbox. Посмотрим на следующую форму:
<form method="POST" enctype="application/x-www-form-urlencoded">
<input type="hidden" name="accept" value="0">
<input type="checkbox" name="accept" value="1">
</form>
Видим два поля с одинаковыми именами. Если checkbox будет выбран, в запрос будут добавлены оба поля в том порядке, в котором они представлены в форме, а значит значение «0» поля hidden будет перезаписано значением «1» поля checkbox. Если же checkbox выбран не будет, то согласно пункту 2 его значение не отправляется, а значит в запросе будет только поле hidden со значением «0», которое и будет получено на сервере и помещено в массив $_POST.
$_POST = [
'manager' = [
'fullname' => 'John Doe',
],
'developer' = [
'fullname' => 'Mike Ross',
],
]
Этот вариант удобно использовать для создания динамических форм, или, например, если необходимо выделить некоторые поля в логическую подформу.
fullname[]=John+Doe&fullname[]=Mike+Ross
Примерный алгоритм разбора такого запроса в PHP:
$_POST = [];
$_POST['fullname'][] = 'John Doe';
$_POST['fullname'][] = 'Mike Ross';
Таким образом, массив $_POST будет содержать следующие значения:
$_POST = [
'fullname' = [
'John Doe',
'Mike Ross',
],
]
Этот вариант удобно использовать для набора значений неопределенного размера. Например, набор полей типа checkbox.
<form method="POST" enctype="application/x-www-form-urlencoded">
<select name="hobbies" multiple>
<option value="movies">Movies</option>
<option value="music">Music</option>
<option value="cooking">Cooking</option>
<option value="photography">Photography</option>
<option value="painting">Painting</option>
<option value="golf">Golf</option>
</select>
</form>
Форма содержит список, в котором мы можем выбрать несколько вариантов. Допустим, мы выбирали Movies, Cooking и Golf. Можно предположить, что при отправке поля будет использован некий разделитель значений. На самом же деле запрос будет выглядеть следующим образом:
hobbies=movies&hobbies=cooking&hobbies=golf
Т.е. в браузере мы отправляем одно поле с тремя значениями, но серверная сторона видит это как отправку трех полей, которые содержат по одному значению. Очевидно, что согласно пункту 4 массив $_POST будет содержать только одно последнее значение golf, которое перезапишет первые два. Для решения этой проблемы необходимо воспользоваться советом из пункта 5 и изменить имя тега select на «hobbies[]».
Кратко также рассмотрим вторую группу способов отправки форм. Эта группа отличается от первой тем, что созданием, формированием и отправкой запроса занимается непосредственно разработчик. Данная группа имеет лишь отдалённое отношение к рассматриваемой нами теме, так как не имеет жёсткой привязки к HTML-формам: разработчик может включать в запрос и исключать из него любые данные. Способ сериализации полей отличается от полностью произвольного запроса наличием в широко распространённых JS-фреймворках готовых алгоритмов, которые берут на себя большую часть работы. Эти способы удобны при отправке данных форм с использованием Ajax.
Рассмотрим небольшой пример использования JS-библиотеки jQuery для формирования и отправки формы таким способом.
Форма:
<form id="userform" method="GET" enctype="multipart/form-data">
<input type="text" name="fullname" value="John Doe">
<input type="text" name="email" value="johndoe@gmail.com" disabled>
<input type="checkbox" name="accept" value="1" checked>
<input type="file" name="photo">
<button type="submit" name="submit" value="Send">
</form>
JS-код:
var postbody = $("#userform").serialize();
$.ajax({
url: "server.php",
type: "POST",
data: postbody,
success: function(data) {
// ...
},
error: function(data) {
// ...
}
});
Всю основную работу выполняет метод serialize(), который, используя имена и значения полей формы, генерирует следующую строку:
fullname=John+Doe&accept=1
Почему из пяти полей формы в запрос включаются только два? Поле email не включается, т.к. находится в отключённом состоянии (атрибут disabled), а поля photo и submit – т.к. метод serialize() данной JS-библиотеки не включает файлы и кнопки в набор полей при формировании строки запроса.
Далее создаём Ajax-запрос с определёнными параметрами и отправляем нашу форму в виде полученной ранее строки. Тут важно понять, что при использовании данного способа от HTML-формы требуется только набор полей с их именами и значениями, все остальные параметры запроса определяет непосредственно разработчик. Таким образом, атрибуты action, method и enctype тега form игнорируются: при вызове метода ajax() мы явно указываем, что данные будут переданы методом POST в обработчик «server.php», а кодирование полей в данном методе по умолчанию – «application/x-www-form-urlencoded».
На стороне сервера такой запрос будет почти идентичен обычному, и только благодаря дополнительным заголовкам мы можем определить, что он был выполнен с использованием технологии Ajax. На основании этой информации формат и содержание ответа в большинстве случаев будет отлично от обычных запросов.
При использовании этой группы способов формирования и отправки запросов фронтенд-разработчик практически ничем не ограничен. Клиентская часть приложения может, например, передать в запросе лишь некоторую часть формы или передать эту форму в формате JSON, серверная же часть должна уметь корректно обрабатывать такие данные, поэтому для решения подобных задач взаимодействие и координация работы бэкенд и фронтенд-разработчиков должны быть усилены.
Работа с формами считается одной из самых сложных задач в веб-разработке. Но если чётко понимать, как работают формы, найти удобные, функциональные и гибкие инструменты как для клиентской, так и для серверной части, наладить взаимодействие между бэкенд и фронтенд-разработчиками, задача намного упрощается.
Эта статья не отвечает на все существующие вопросы, многие не без оснований отметят, что всё описанное — это общеизвестные факты, но надеюсь, что предоставленная информация покажется кому-то полезной и заставит пересмотреть своё отношение к работе с формами, а возможно даже поможет побороть фобию перед ними.
Автор:
Источник [2]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/php-2/69559
Ссылки в тексте:
[1] parse_str: http://php.net/parse_str
[2] Источник: http://habrahabr.ru/post/236837/
Нажмите здесь для печати.