В предыдущей части мы сгенерировали простейшую html форму и обработали пользовательский ввод. Продолжим, немного усложним форму: добавим элемент, генерирующий массив, а перед обработкой пользовательских данных проверим их. Функция formCheck(), вернет результат проверки ввода. И мы либо повторно выведем форму, либо обрабатаем полученные данные.
Последовательно, с помощью filter_has_var() и filter_input() проверяем поступившие от пользователя данные.
filter_has_var() проверяет наличие элемента в данных, переданных браузером (не в $_POST['something']), например:
выведет:
filter_input(), как и filter_var() — служат для проверки значений, их отличие в том, откуда они берут эти значения. Их прототипы:
Для наших целей подходит filter_input(), первый параметр указывает в каких данных от браузера искать, второй — название поля, третий — применяемый фильтр, четвертый — дополнительные параметры фильтра. Как и filter_has_var(), filter_input() проверяет данные от браузера, а не суперглобальные переменные:
вернет:
filter_input() возвращает отфильтрованное значение, если переменную получилось отфильтровать, FALSE, если фильтрация не удалась и NULL, если переменная не установлена. В фильтр можно передать дополнительные параметры, один из которых 'default', тогда вернется его содержимое, в случае, если в переменной было установлено неправильное значение. Обращаю внимание: содержимое параметра 'default' возвращается не всегда, если тип фильтруемой переменной не совпадает с флагом FILTER_REQUIRE_SCALAR или FILTER_REQUIRE_ARRAY — вернется FALSE.
Внятное объяснение использования дополнительных параметров дано в примере к функции filter_var(). Посмотрим, что функция возвращает в разных случаях:
Внимательный читатель мог заметить, что в массиве я указал (integer) 100, а в строках (string) '100'. FILTER_VALIDATE_REGEXP — '/[A-Za-z]/' одинаково забраковал оба значения.
Параметр 'default' было бы неплохо использовать для того, чтоб устанавливать значение по-умолчанию атрибута value у тега input. Если пользователь ввел неправильные данные и ошибка не критическая, её можно исправить простой коррекцией ввода. Нужно просто перевывести форму с «неправильным» значением, введенным пользователем. Сделаем это в следующей версии.
В остальном formCheck() делает самую базовую проверку ввода:
Проверка длины имени, тут — строка, 3 символа. Далее несколько сложнее:
У меня остался вопрос: а в каком случае в $choices могут попасть данные, не совпадающие с array_keys($colors)? Ошибка передачи, ошибка браузера или если пользователь вручную формирует запросы?
В случае удачной проверки мы выводим результат в функции formResult(). В бизнес-логику вдаваться не буду, в любом случае перед записью данных в базу данных или выводом на экран их необходимо подготавливать с помощью экранирования при подготовке запроса к БД или htmlentities(). Подобные проверки:
Также лучше заменить
Поподробнее остановлюсь на
Или, в виде кода:
Интересно, что будет быстрее на больших массивах: пара вложенных foreach или жонглирование массивами с помощью встроенных средств?
Итог:
Об этом в следующий раз.
Часть 1
<?php
$colors = array('red' => 'Красный', 'green' => 'Зеленый', 'blue' => 'Синий');
//принятие решения
if ($_SERVER['REQUEST_METHOD'] == 'GET') {
formShow($colors);
} else {
if (formCheck($colors)) {formResult($colors);}
else {formShow ($colors);}
}
//вывод формы
function formShow(array $colors) {
echo '<form action="'.htmlentities($_SERVER['SCRIPT_NAME']).'" method="post">'."\n";
echo 'Введите имя:<br>'."\n";
echo '<input type="text" name="name" /><br>'."\n";
echo 'Введите возраст:<br>'."\n";
echo '<input type="text" name="age" /><br>'."\n";
echo 'Выберите цвет(а):<br>'."\n";
foreach ($colors as $value => $text) {echo '<input type="checkbox" name="colors[]" value="'.$value.'">'.$text."\n";}
echo '<input type="submit" value="Отправить" />'."\n";
echo '</form>'."\n";
}
//проверка ввода
function formCheck(array $colors) {
$isOK = TRUE;
if (! (filter_has_var(INPUT_POST, 'name') &&
(strlen(filter_input(INPUT_POST, 'name', FILTER_SANITIZE_STRING)) > 3)) ) { //обязательное поле
echo 'Введите имя, не менее 3 букв.<br>'."\n";
$isOK = FALSE;
}
if (! (filter_has_var(INPUT_POST, 'age') &&
filter_input(INPUT_POST, 'age', FILTER_VALIDATE_INT)) ) { //обязательное поле
echo 'Введите возраст, целое число.<br>'."\n";
$isOK = FALSE;
}
if (filter_has_var(INPUT_POST, 'colors')) { //необязательное поле
$choices = filter_input(INPUT_POST, 'colors', FILTER_VALIDATE_REGEXP,
array('flags' => FILTER_FORCE_ARRAY,
'options' => array('regexp' => '/[a-z]+/')));
if (($choices !== FALSE && (array_intersect($choices, array_keys($colors)) != $choices)) ||
$choices === FALSE) {
echo 'Выберите цвет(а) из предложенных.<br>'."\n";
$isOK = FALSE;
}
}
return ($isOK);
}
//обработка ввода
function formResult(array $colors) {
echo '<form action="'.htmlentities($_SERVER['SCRIPT_NAME']).'" method="get">'."\n";
echo 'Привет, '.$_POST['name'].', Вам '.$_POST['age'].' лет.<br>'."\n";
$choices = filter_input(INPUT_POST, 'colors', FILTER_SANITIZE_STRING, FILTER_FORCE_ARRAY);
if ($choices != NULL) {
echo 'Вы выбрали: '.implode(', ', array_values(array_intersect_key($colors, array_flip($choices)))).'.<br>'."\n";
}
else { //если ничего не выбрано $choices == NULL
echo 'Вы не выбрали цвет(а).<br>'."\n";
}
echo '<input type="submit" value="Повторить" />'."\n";
echo '</form>'."\n";
}
Последовательно, с помощью filter_has_var() и filter_input() проверяем поступившие от пользователя данные.
filter_has_var() проверяет наличие элемента в данных, переданных браузером (не в $_POST['something']), например:
$_POST['test'] = 'test';
if (filter_has_var(INPUT_POST, 'test')) {echo "найден test\n";}
else {echo "ничего нет\n";}
выведет:
ничего нет
filter_input(), как и filter_var() — служат для проверки значений, их отличие в том, откуда они берут эти значения. Их прототипы:
mixed filter_input ( int $type , string $variable_name [, int $filter = FILTER_DEFAULT [, mixed $options ]] )
mixed filter_var ( mixed $variable [, int $filter = FILTER_DEFAULT [, mixed $options ]] )
Для наших целей подходит filter_input(), первый параметр указывает в каких данных от браузера искать, второй — название поля, третий — применяемый фильтр, четвертый — дополнительные параметры фильтра. Как и filter_has_var(), filter_input() проверяет данные от браузера, а не суперглобальные переменные:
$_POST['test'] = 'текст';
var_export(filter_input(INPUT_POST, 'test', FILTER_SANITIZE_STRING));
echo "\n";
var_export(filter_var($_POST['test'], FILTER_SANITIZE_STRING));
echo "\n";
вернет:
NULL
'текст'
filter_input() возвращает отфильтрованное значение, если переменную получилось отфильтровать, FALSE, если фильтрация не удалась и NULL, если переменная не установлена. В фильтр можно передать дополнительные параметры, один из которых 'default', тогда вернется его содержимое, в случае, если в переменной было установлено неправильное значение. Обращаю внимание: содержимое параметра 'default' возвращается не всегда, если тип фильтруемой переменной не совпадает с флагом FILTER_REQUIRE_SCALAR или FILTER_REQUIRE_ARRAY — вернется FALSE.
Внятное объяснение использования дополнительных параметров дано в примере к функции filter_var(). Посмотрим, что функция возвращает в разных случаях:
$colors = array('red', 'green', 'blue', 100);
$options = array(
'flags' => FILTER_FORCE_ARRAY,
'options' => array(
'regexp' =>'/[A-Za-z]/',
'default' => 'ошибка',
),
);
var_export(filter_var($colors, FILTER_VALIDATE_REGEXP, $options));//array ( 0 => 'red', 1 => 'green', 2 => 'blue', 3 => 'ошибка',)
var_export(filter_var('string', FILTER_VALIDATE_REGEXP, $options));//array (0 => 'string',)
var_export(filter_var('100', FILTER_VALIDATE_REGEXP, $options));//array (0 => 'ошибка',)
$options['flags'] = FILTER_REQUIRE_ARRAY;
var_export(filter_var($colors, FILTER_VALIDATE_REGEXP, $options));//array ( 0 => 'red', 1 => 'green', 2 => 'blue', 3 => 'ошибка',)
var_export(filter_var('string', FILTER_VALIDATE_REGEXP, $options));//false
var_export(filter_var('100', FILTER_VALIDATE_REGEXP, $options));//false
$options['flags'] = FILTER_REQUIRE_SCALAR;
var_export(filter_var('string', FILTER_VALIDATE_REGEXP, $options));//'string'
var_export(filter_var('100', FILTER_VALIDATE_REGEXP, $options));//'ошибка'
var_export(filter_var(array('string'), FILTER_VALIDATE_REGEXP, $options));//false
var_export(filter_var(array('100'), FILTER_VALIDATE_REGEXP, $options));//false
Внимательный читатель мог заметить, что в массиве я указал (integer) 100, а в строках (string) '100'. FILTER_VALIDATE_REGEXP — '/[A-Za-z]/' одинаково забраковал оба значения.
Параметр 'default' было бы неплохо использовать для того, чтоб устанавливать значение по-умолчанию атрибута value у тега input. Если пользователь ввел неправильные данные и ошибка не критическая, её можно исправить простой коррекцией ввода. Нужно просто перевывести форму с «неправильным» значением, введенным пользователем. Сделаем это в следующей версии.
В остальном formCheck() делает самую базовую проверку ввода:
strlen(filter_input(INPUT_POST, 'name', FILTER_SANITIZE_STRING)) > 3
Проверка длины имени, тут — строка, 3 символа. Далее несколько сложнее:
if (filter_has_var(INPUT_POST, 'colors')) { //необязательное поле
$choices = filter_input(INPUT_POST, 'colors', FILTER_VALIDATE_REGEXP,
array('flags' => FILTER_FORCE_ARRAY,
'options' => array('regexp' => '/[a-z]+/')));
if (($choices !== FALSE && (array_intersect($choices, array_keys($colors)) != $choices)) ||
$choices === FALSE) {
echo 'Выберите цвет(а) из предложенных.<br>'."\n";
$isOK = FALSE;
}
}
- Проверяем, введено ли что-либо. filter_has_var() возвращает TRUE, если значение установлено и FALSE, если нет. Не установлено — значит никакой выбор не сделан, а т.к. поле не обязательное, значит это допустимо.
- Читаем выбор в массив, фильтруя его. Результат — либо массив значений, прошедших фильтр, либо FALSE, если передано что-то странное. NULL (ничего не выбрано) мы отсекли раньше.
- Если значения прошли через фильтр, необходимо проверить, попадают ли они в допустимый диапазон, т.е. ключи массива $colors. Товарищи Скляр и Трахтенберг предложили изящное решение (цитата несколько изменена):
Функция array_intersect() находит все элементы пользовательского выбора ($choices), которые присутствуют в array_keys($colors). Иначе говоря, она фильтрует отправленные варианты ($choices), пропуская только приемлемые значения — ключи из массива $colors. Если все значения из ввода являются приемлемыми, то результат array_intersect($choices, array_keys($colors)) представляет собой немодифицированную копию ввода ($choices). Итак, если результат не равен $choices, значит, были отправлены недействительные значения.
- FALSE в $choices возвращается, если данные не прошли проверку фильтром, поэтому фиксируем ошибку.
У меня остался вопрос: а в каком случае в $choices могут попасть данные, не совпадающие с array_keys($colors)? Ошибка передачи, ошибка браузера или если пользователь вручную формирует запросы?
В случае удачной проверки мы выводим результат в функции formResult(). В бизнес-логику вдаваться не буду, в любом случае перед записью данных в базу данных или выводом на экран их необходимо подготавливать с помощью экранирования при подготовке запроса к БД или htmlentities(). Подобные проверки:
$choices = filter_input(INPUT_POST, 'colors', FILTER_SANITIZE_STRING, FILTER_FORCE_ARRAY);
if ($choices != NULL) {...}
в этом месте неуместны, сюда должны попадать подготовленные данные, которые необходимо только экранировать. В данном случае $choices должна содержать массив, если никакого выбора не было сделано — пустой массив.Также лучше заменить
htmlentities($_SERVER['SCRIPT_NAME'])
и подобные места на filter_input(INPUT_SERVER, 'SCRIPT_NAME', FILTER_SANITIZE_SPECIAL_CHARS)
чтоб не обращаться к суперглобальным переменным напрямую. Это сделаем в следующей версии.Поподробнее остановлюсь на
implode(', ', array_values(array_intersect_key($colors, array_flip($choices))))
. Тут происходит следующее:- На входе два массива: $colors: пары ключ — значение и $choices: ключи из первого массива, значения которых необходимо вывести на экран.
array_flip($choices)
меняем местами ничего не зачащие цифровые ключи и значения. Значения становятся ключами массива, в значениях — мусор.array_intersect_key($colors, array_flip($choices))
формируем массив, в котором содержатся только выбранные пары ключ — значение из $colorsarray_values(array_intersect_key($colors, array_flip($choices)))
получаем выбранные значения из $colors по установленным пользователем ключам из $choices.- С помощью implode() формируем текстовую строку для вывода на экран
Или, в виде кода:
choices: [array (
0 => 'red',
1 => 'green',
)]
colors: [array (
'red' => 'Красный',
'green' => 'Зеленый',
'blue' => 'Синий',
)]
array_flip($choices): [array (
'red' => 0,
'green' => 1,
)]
array_intersect_key($colors, array_flip($choices)): [array (
'red' => 'Красный',
'green' => 'Зеленый',
)]
array_values(array_intersect_key($colors, array_flip($choices))): [array (
0 => 'Красный',
1 => 'Зеленый',
)]
implode(', ', array_values(array_intersect_key($colors, array_flip($choices)))): ['Красный, Зеленый']
Интересно, что будет быстрее на больших массивах: пара вложенных foreach или жонглирование массивами с помощью встроенных средств?
Итог:
- Мы добавили проверку данных (formCheck()), переданных предположительно браузером от пользователя.
- Функция formCheck() осуществляет вывод информации — это некорректно, вывод информации о неправильном заполнении должен осуществляться в процессе отрисовки формы.
- Для удобства пользователя следует оставлять его некорректный ввод,
возможно ошибка была допущена непреднамеренно и ему проще будет исправить что-то, чем вводить все поля заново. - Мы передаем массив $colors с нашим множественным выбором во все функции. Что делать, если в нашей форме несколько элементов, возвращающих массив? Или большое количество однотипных элементов?
Об этом в следующий раз.
Часть 1