Обработка произвольного количества полей на PHP

Владимир | | PHP, Web разработка.

php input fields

В этой заметке я хочу показать пример небольшого PHP скрипта, который обрабатывает данные из форм с произвольным количеством полей.

Такие ситуации возникают, например, когда пользователь может добавлять или удалять поля в форме.

Прежде всего немного теории.

Как известно, сколько бы полей форма не содержала, PHP скрипт получит все эти значения в массиве $_POST. Ключи элементов этого массива будут соответствовать атрибутам name полей формы. Используя эти ключи мы можем получить значение любого из полей.

Т.е. можно в цикле перебрать все элементы массива $_POST и узнать значения всех полей.

На этом можно было бы и остановиться, но на практике часто встречается такая ситуация. Форма содержит несколько обязательных полей и произвольное количество необязательных. Причем обычно необязательные поля содержат однотипные данные (которые и обрабатывать нужно отдельно).

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

Естественно, в такой ситуации удобно выделить из массива $_POST массив с вариантами ответов. Этим мы и займемся.

Прежде всего нам нужно знать имена полей. С обязательным полем проблем нет, а имена полей с вариантами ответов скорее всего будут создаваться с помощью JavaScript и иметь вид: ans1, ans2 и т.д. Т.е. к приставке ans добавляется порядковый номер ответа.

Теперь рассмотрим PHP скрипт.

$answers = array();

while ($curField = each($_POST)) {
	if (strpos($curField['key'], 'ans') !== FALSE) {
		$answers['key'] = $curField['value'];
	}
}

Наибольший интерес тут представляет функция each. Она возвращает массив, который мы присваиваем переменной $curField.

В этом массиве доступны четыре поля:

$curField[0] и $curField['key'] – содержат ключ текущего элемента исходного массива, который был передан функции each.
$curField[1] и $curField['value'] – содержат значения этого же элемента.

Принцип работы скрипта такой. Проходим в цикле весь массив $_POST.

Когда будет достигнут конец массива, функция each вернет FALSE и цикл завершиться.

В цикле мы с помощью функции strpos ищем приставку 'ans' в ключе текущего элемента. Если она найдена, копируем текущий элемент в массив $answers, если нет – пропускаем.

В результате в массиве $answers будут только значения из полей с вариантами ответов.

Как видите, ничего сложного!

Если есть вопросы, пишите, с удовольствием отвечу 🙂

Интересно почитать:

Престижные автомобили в Одессе — почувствуйте себя королем дороги.
Копии элитных часов — отличное качество за разумные деньги.
Недвижимость Одессы — идеальный объект для инвестиций.

  • MAX

    Вообще-то все проще делается. В html можно указывать имена полей в виде массива:

    input name=»forms_fields[my1]»
    input name=»forms_fields[my2]»
    input name=»forms_fields[my3]»

    В итоге по _POST сразу получаем forms_fields как массив.

    • Да, неудачно как-то получилось…
      Идея была в том, чтобы показать пример разделения массивов с использованием each, а обработка полей формы — первое что пришло в голову…
      Спасибо!

  • MAX

    Вообще-то все проще делается. В html можно указывать имена полей в виде массива:

    input name=»forms_fields[my1]»
    input name=»forms_fields[my2]»
    input name=»forms_fields[my3]»

    В итоге по _POST сразу получаем forms_fields как массив.

    • Да, неудачно как-то получилось…
      Идея была в том, чтобы показать пример разделения массивов с использованием each, а обработка полей формы — первое что пришло в голову…
      Спасибо!

  • Обработка форм без валидации — явный баг в проекте! Конечно, я понимая что можно, в зависимости от имени вешать на поле те или иные фильтры, но тогда это превратится в постоянное сравнение подстрок по шаблонам.
    На мой взгляд гораздо логичнее создать набор правил фильтрации и контроллер обработки формы, в котором к нужному вам полю привязать набор фильтров и проверок.
    Конечно, необходимо будет помнить все названия полей в форме, но тогда мы избавимся от множества ненужных действий.

    • Полностью согласен!
      Даже как-то писал отдельный пост на эту тему (этот).
      Просто бывают ситуации когда нужен максимально простой скрипт, а из правил проверки — только требование чтобы поле было заполненным (как в этом примере). И такая проверка будет не намного длиннее, чем установка правил валидации.

    • MAX

      Я думаю, что правильней все-таки сделать так, чтобы скрипт перед приемом _POST проверял поля формы, а уже после этого сверял их с тем, что пришло. У меня в MaxSite CMS есть плагин Forms — там как раз решена подобная задача.

      • Честно говоря, я не понял 😕
        Как проверить поля формы до приема $_POST? Или речь о JS?

        • qnikst

          можно добавить к необходимым файлам auto_prepend (если не изменяет память) через php.ini или .htaccess. В данном файле обрабатывать $_REQUEST/$_GET/$_POST и т.д.
          Хотя исходя из моего опыта это может оправдывать себя только в cms или хаках других систем. В работе приходилось добавлять новые возможности для hastymail, при этом по возможности не трогая исходный код.

          Вобще js это только для удобства пользователя (по 2 раза неправильную форму не сабмитил чтобы).
          P.S. может я тоже не так понял

        • MAX

          Я так понял, задача стоит в том, чтобы разрешить пользователю самому определять поля формы. Таким образом на этапе приёма _POST у нас нет никаких данных о том, какие именно поля указал пользователь. Поэтому в принципе ничего не стоит подделать запрос и отправить любые данные с любыми полями, даже теми, которые пользователь не указывал.

          Если это так, то нужно где-то указывать какие поля присутствуют в форме. У меня это реализовано с помощью псевдокода [form]. То есть форма генерируется динамически (в тексте записи), отображается, а на этапе приема _POST уже отпарсенная форма (поля в массиве) проходится по указанным в ней элементам.

        • Посмотрел плагин Forms и до меня, наконец, дошло 🙂
          Я имел ввиду более простой вариант — когда посетитель может менять количество полей, но не их тип. Например, в форме для расчета длины маршрута пользователь выбирает через какие города он будет проезжать из выпадающих списков. Количество таких списков заранее неизвестно, а вот тип — всегда один и тот же.

          А в плагине Forms реализован шаблонизатор, который позволяет владельцу сайта сформировать любую форму (или почти любую)

  • Обработка форм без валидации — явный баг в проекте! Конечно, я понимая что можно, в зависимости от имени вешать на поле те или иные фильтры, но тогда это превратится в постоянное сравнение подстрок по шаблонам.
    На мой взгляд гораздо логичнее создать набор правил фильтрации и контроллер обработки формы, в котором к нужному вам полю привязать набор фильтров и проверок.
    Конечно, необходимо будет помнить все названия полей в форме, но тогда мы избавимся от множества ненужных действий.

    • Полностью согласен!
      Даже как-то писал отдельный пост на эту тему (этот).
      Просто бывают ситуации когда нужен максимально простой скрипт, а из правил проверки — только требование чтобы поле было заполненным (как в этом примере). И такая проверка будет не намного длиннее, чем установка правил валидации.

    • MAX

      Я думаю, что правильней все-таки сделать так, чтобы скрипт перед приемом _POST проверял поля формы, а уже после этого сверял их с тем, что пришло. У меня в MaxSite CMS есть плагин Forms — там как раз решена подобная задача.

      • Честно говоря, я не понял 😕
        Как проверить поля формы до приема $_POST? Или речь о JS?

        • qnikst

          можно добавить к необходимым файлам auto_prepend (если не изменяет память) через php.ini или .htaccess. В данном файле обрабатывать $_REQUEST/$_GET/$_POST и т.д.
          Хотя исходя из моего опыта это может оправдывать себя только в cms или хаках других систем. В работе приходилось добавлять новые возможности для hastymail, при этом по возможности не трогая исходный код.

          Вобще js это только для удобства пользователя (по 2 раза неправильную форму не сабмитил чтобы).
          P.S. может я тоже не так понял

        • MAX

          Я так понял, задача стоит в том, чтобы разрешить пользователю самому определять поля формы. Таким образом на этапе приёма _POST у нас нет никаких данных о том, какие именно поля указал пользователь. Поэтому в принципе ничего не стоит подделать запрос и отправить любые данные с любыми полями, даже теми, которые пользователь не указывал.

          Если это так, то нужно где-то указывать какие поля присутствуют в форме. У меня это реализовано с помощью псевдокода [form]. То есть форма генерируется динамически (в тексте записи), отображается, а на этапе приема _POST уже отпарсенная форма (поля в массиве) проходится по указанным в ней элементам.

        • Посмотрел плагин Forms и до меня, наконец, дошло 🙂
          Я имел ввиду более простой вариант — когда посетитель может менять количество полей, но не их тип. Например, в форме для расчета длины маршрута пользователь выбирает через какие города он будет проезжать из выпадающих списков. Количество таких списков заранее неизвестно, а вот тип — всегда один и тот же.

          А в плагине Forms реализован шаблонизатор, который позволяет владельцу сайта сформировать любую форму (или почти любую)

  • qnikst

    Как уже сказал Max у добавляемых инпутов name ff[key].
    и обработка
    foreach($ff as $key=>$value){
    проверка всех ключей и значений.и защищённей и быстрее.
    }
    В зависимости от задачи возможен вариант 2 поля ff_name и ff_value с целочисленными индексами.
    Тогда обработку можно будет сделать циклом for, а не «небыстрым» foreach, опять же лишний уровень защиты.
    $l=sizeof($ff_name);//или может быть другой критерий кол-ва полей
    for ($i=0;$i<$l;$i++){
    $key=$ff_name[$i]
    $value=$ff_value[$i]
    .. проверки и обработка
    }

    • С циклом for могут быть проблемы если номера полей идут не по-порядку и не подряд.
      Например, пользователь может добавлять три поля, а потом удалить второе. Тут нужно либо усложнять JS (проверять нумерацию при каждом удалении), либо использовать foreach. Как вариант — использовать while с функцией each (не проверял какой вариант быстрее).

      • qnikst

        Я специально написал, что в зависимости от задачи. В большинстве случаев (когда пользователь добавляет поля сам) действительно проще и удобнее использовать foreach.
        Но бывают случаи, когда вариант с for подойдёт.
        Например, тот же случай, но без возможности добавления полей (т.е. поле добавляется перезарузкой формы или с помощью ajax). Это может быть полезным, если для добавления поля необходимо провести дополнительные операции с данными (проверки) на стороне сервера. Тогда ничего не мешает, на php генерировать правильную нумерацию.

        • Все-таки немного поспорю 😉
          По идее нельзя доверять никаким данным, полученным от клиента. Точнее их нужно проверять.
          Что будет если кто-то сам сформирует и отправит некорректный запрос?
          Все-таки, как минимум, какую-нибудь элементарную проверку (вроде if (isset($ff_value[$i]))) добавить в цикл нужно.

  • qnikst

    Как уже сказал Max у добавляемых инпутов name ff[key].
    и обработка
    foreach($ff as $key=>$value){
    проверка всех ключей и значений.и защищённей и быстрее.
    }
    В зависимости от задачи возможен вариант 2 поля ff_name и ff_value с целочисленными индексами.
    Тогда обработку можно будет сделать циклом for, а не «небыстрым» foreach, опять же лишний уровень защиты.
    $l=sizeof($ff_name);//или может быть другой критерий кол-ва полей
    for ($i=0;$i<$l;$i++){
    $key=$ff_name[$i]
    $value=$ff_value[$i]
    .. проверки и обработка
    }

    • С циклом for могут быть проблемы если номера полей идут не по-порядку и не подряд.
      Например, пользователь может добавлять три поля, а потом удалить второе. Тут нужно либо усложнять JS (проверять нумерацию при каждом удалении), либо использовать foreach. Как вариант — использовать while с функцией each (не проверял какой вариант быстрее).

      • qnikst

        Я специально написал, что в зависимости от задачи. В большинстве случаев (когда пользователь добавляет поля сам) действительно проще и удобнее использовать foreach.
        Но бывают случаи, когда вариант с for подойдёт.
        Например, тот же случай, но без возможности добавления полей (т.е. поле добавляется перезарузкой формы или с помощью ajax). Это может быть полезным, если для добавления поля необходимо провести дополнительные операции с данными (проверки) на стороне сервера. Тогда ничего не мешает, на php генерировать правильную нумерацию.

        • Все-таки немного поспорю 😉
          По идее нельзя доверять никаким данным, полученным от клиента. Точнее их нужно проверять.
          Что будет если кто-то сам сформирует и отправит некорректный запрос?
          Все-таки, как минимум, какую-нибудь элементарную проверку (вроде if (isset($ff_value[$i]))) добавить в цикл нужно.

  • qnikst

    ну спорить так спорить 😉 я же писал проверки и обработка
    я бы писал
    $key=htmlspecialchars(trim(*));или trim(*);
    $value=intval(*);floatval(*); htmlspecialchars(*)
    и далее уже:
    if (($key!=»)) //в записимости от задачи проверки могут быть другими..

    Впрочем с foreach или while-each опять же потребуются те же самые trim/intval, так что такая нагрузка внутри цикла не будет лишней.

    • Да, без конкретной задачи, спорить сложно 😉
      В реальном проекте проверять нужно все. Просто с foreach или while-each удобнее контролировать индексы.
      К тому же если форма небольшая — вряд ли разница между скорость for и foreach будет заметна.

  • qnikst

    ну спорить так спорить 😉 я же писал проверки и обработка
    я бы писал
    $key=htmlspecialchars(trim(*));или trim(*);
    $value=intval(*);floatval(*); htmlspecialchars(*)
    и далее уже:
    if (($key!=»)) //в записимости от задачи проверки могут быть другими..

    Впрочем с foreach или while-each опять же потребуются те же самые trim/intval, так что такая нагрузка внутри цикла не будет лишней.

    • Да, без конкретной задачи, спорить сложно 😉
      В реальном проекте проверять нужно все. Просто с foreach или while-each удобнее контролировать индексы.
      К тому же если форма небольшая — вряд ли разница между скорость for и foreach будет заметна.

  • elCreator

    Помимо отсутствия валидации (а ее тут и не может быть, т.к. не задается тип каждого поля), возникает проблема элементарного DoS путем отправки POST (который в отличие от GET не ограничен 1024 байтами) for($i=0;$i<1000000;$i++)$post.='p'.rand(1000,100000).'=manydossymbols'

  • Проблема DoS существует не зависимо от длины запроса.

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

    Если все-таки нужно предоставлять доступ всем, то бороться можно проверкой на общую длину массива POST. И фильтровать спецсимволы.