Skip to content

Latest commit

 

History

History
executable file
·
125 lines (76 loc) · 14.2 KB

forms.md

File metadata and controls

executable file
·
125 lines (76 loc) · 14.2 KB

Алгоритм обработки данных форм

При работе с формами надо проверять введенные значения на правильность (это называется валидация), а при ошибке — выводить ту же форму повторно, с уже подставленными значениями и сообщениями об ошибке. Есть универсальный алгоритм, который позволяет это реализовать. Он работает с любыми формами, отправляемыми как через GET, так и через POST. Он может быть написан как с использованием ООП, так и без.

Чтобы это все реализовать, необходимо чтобы обрабатывал и выводил форму один и тот же скрипт. К примеру, при заходе по адресу /register.php мы видим форму регистрации, заполняем ее и отправляем на тот же адрес.

На тот случай, если ты не изучал протокол HTTP (что плохо), я напомню, что есть 2 метода отправки форм: GET и POST. Используемый метод задается в атрибуте method HTML-тега <form> (а у POST форм есть 2 способа кодирования данных, которые выбираются через атрибут enctype: application/x-www-form-urlencoded и multipart/form-data). Вот их основные особенности:

  • GET должен использоваться только для форм, которые не меняют данные на сервере: форма поиска, форма перехода на определенную страницу. POST используется для любых форм (добавление, изменение, удаление информации)
  • файлы можно прикладывать только к POST-формам, у которых enctype равен multipart/form-data
  • GET-форма добавляет введенные данные в URL и потому пользователь может скопировать и сохранить или переслать ссылку на результаты (например на результат поиска). POST форма ничего не добавляет в URL, и при открытии этого URL мы увидим лишь пустую форму.
  • объем данных в GET-форме ограничен 500-2000 символов (так как браузеры и серверы ограничивают длину URL)

Вот сам алгоритм:

$values = значения по умолчанию (пустые);
$errors = пустой массив;

Если (форма отправлена) {
    Копируем переданные значения полей в $values;
    Проверяем значения в $values и записываем найденные ошибки в $errors;

    Если (ошибок нет) {
        Делаем требуемое действие (например вставляем запись в БД);
        Редиректим куда-нибудь;
        Завершаем скрипт;
    }
}

Выводим форму($values, $errors);

Давай разберем его подробно.

В начале мы заводим 2 переменные: первая будет хранить введенные в форму значения, вторая — список найденных ошибок. $values мы заполняем значениями по умолчанию.

Затем мы проверяем, отправлена ли форма. Если форма использует метод POST, то достаточно проверить хранящиеся в $_SERVER['REQUEST_METHOD'] значение (каким методом была запрошена страница). Если форма использует метод GET, то можно проверять наличие какого-то параметра в массиве $_GET.

Если форма не отправлена, то мы просто выводим форму со значениями по умолчанию. Если же она отправлена, то мы копируем значения полей в $values. Это делается для того, чтобы при ошибке мы могли их вывести в форме. Копирование можно сделать отдельной функцией или методом.

При копировании значений стоит обрезать пробелы с краев с помощью функции trim(). Ведь человек может ввести пробел и не увидит его, а для компьютера строки 'hello' и 'hello ' — различаются.

Также, стоит помнить, что в массивах, полученных от пользователя ($_COOKIE, $_POST, $_GET), любые элементы могут отсутствовать или содержать что угодно (массив вместо строки например). Вот такой код предусматривает безопасное получение переданных значений:

// Некоторые используют isset вместо array_key_exists
$name = array_key_exists('name', $_POST) ? strval($_POST['name']) : '';

Функция strval принудительно преобразует любые переданные данные в строку.

Затем, мы проверяем их на правильность. Это тоже удобно вынести в функцию, которая например принимает на вход $values и возвращает $errors.

Если все данные введены правильно, то мы после их обработки делаем редирект на какую-то другую страницу, например на страницу просмотра введенной информации, страницу благодарности, и т.д. Редирект необходим, чтобы при обновлении страницы форма не отправлялась повторно (если ты не знаешь, что такое редирект, то это выдача заголовка вроде Location: /thankyou.php. В php для этого используется функция header()).

Этот подход (редирект после успешной обработки формы) называется Post/Redirect/Get.

HTML-код формы, разумеется, стоит поместить в отдельный файл, ведь смешивать вместе PHP-логику и HTML — плохая идея.

Форма редактирования

Если ты делаешь форму редактирования информации (например, форма редактирования комментария), то можешь заметить, что код будет очень похож на форму добавления информации (например, форму добавления комментария). Для обоих этих форм можно использовать один код, добавив несколько изменений:

$values = значения по умолчанию (пустые);

Если (мы редактируем сущность, а не создаем новую) {
    $values = загруженные из БД значения;
}

...

    Если (ошибок нет) {
        Если (мы редактируем сущность) {
            Обновляем запись в БД;
        } иначе {
            Вставляем новую запись в БД;
        }

        Редиректим куда-нибудь;
        Завершаем скрипт;
    }

...

Как видишь, добавив пару ветвлений (если/иначе), мы смогли использовать один и тот же код для выполнения двух похожих задач.

ООП-подход

Если ты используешь ООП (что хорошо), то в качестве $values удобно использовать объект, который соответствует редактируемой сущности. Например, если это форма регистрации, то объект User, хранящий информацию о пользователе.

Ошибки можно хранить в массиве или сделать специальный объект вроде FormErrors. У тебя может появиться желание хранить ошибки в том же объекте User, но это не очень правильно, так как лучше когда каждый класс занимается своим делом.

По той же причине валидацию лучше делать отдельным внешним классом или методом, а не помещать внутрь редактируемого объекта. Но в маленьких приложениях можно делать и так.

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

Подводные камни

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

Безопасность

Вот какие ошибки можно сделать при обработке данных форм:

Логические ошибки.

Ты можешь случайно дать пользователям возможность редактировать те поля, которые они не должны редактировать. Ну например, в базе данных пользователей у тебя есть колонки name, email, is_admin. Разумеется, форма содержит только поля name и email. Но ведь злоумышленник может передать любые данные, не только те, для которых в форме есть поля. Если ты просто обновляешь в объекте-пользователе все те поля, которые переданы через _POST, не проверяя их:

foreach ($_POST as $key => $value) {
    $user->$key = $value;
}

$userMapper->save($user); // сохраняем данные из объекта в БД

то злоумышленник получит админские права, передав значение is_admin=1 вместе с формой. Чтобы этого не произошло, нужно обновлять только поля из разрешенного списка.

Именно из-за такой ошибки некто Егор Хомаков смог взломать сайт гитхаб (написанный на Ruby on Rails): http://www.opennet.ru/opennews/art.shtml?num=33268

Уязвимости

Форма может быть отправлена не только с твоего сайта, но и с любого другого, причем без ведома пользователя. Это называется уязвимость XSRF. Чтобы от нее защититься, нужно добавлять к форме и проверять специальный токен. Об этом написано в отдельном уроке про XSRF.

Если ты выводишь данные в HTML-коде, не используя функцию экранирования спецсимволов вроде htmlspecialchars (или шаблонизатор, который это делает), то у тебя может быть уязвимость XSS, позволяющая злоумышленнику вставить произвольный скрипт в HTML-страницу, который выполнится в браузере пользователя. Прочти урок про XSS.