Формы, изменяющие данные, устроены сложнее как с клиентской стороны, так и с серверной. Для уверенной работы с ними необходимо разбираться в следующих вопросах:
Начнём с того, что за вывод формы и её обработку должны отвечать два разных обработчика (а значит это разные маршруты). Ниже пример маршрутов для создания нового пользователя:
action
.Я выбрал именно такие маршруты не случайно. Подобная схема именования рекомендуется и автоматически создаётся многими фреймворками, такими как Rails. Она хорошо ложится на REST-архитектуру, о которой мы ещё поговорим.
<!-- templates/users/new.phtml -->
<form action="/users" method="post">
<div>
<label>
Имя
<input type="text" name="user[name]">
</label>
</div>
<div>
<label>
Email
<input type="email" required name="user[email]">
</label>
</div>
<div>
<label>
Пароль
<input type="password" required name="user[password]">
</label>
</div>
<div>
<label>
Подтверждение пароля
<input type="password" required name="user[passwordConfirmation]">
</label>
</div>
<div>
<label>
Город
<select name="user[city]">
<option value="3">Москва</option>
<option value="13">Пенза</option>
<option value="399">Томск</option>
</select>
</label>
</div>
<input type="submit" value="Sign Up">
</form>
Интересный момент в форме выше, то как задаются имена. Каждое имя определяется как ключ в массиве user
. Такой способ определения имён не является обязательным, но он очень удобен для массовой обработки значений формы. Их изоляция в одном массиве позволяет избежать потенциальных пересечений с другими данными. В поисковых формах эта схема тоже удобна, если количество элементов больше одного.
Здесь стоит сказать, что с точки зрения HTTP не существует способа передавать массивы. Если не указано иного, то данные формы кодируются в теле запроса как application/x-www-form-urlencoded. Чисто технически это выглядит, как строка запроса с парами "ключ-значение", объединённые символом &.
POST /users HTTP/1.1
Host: example.com
Content-type: application/x-www-form-urlencoded
Content-length: 42
key=value&key2=value2&user%5Bname%5D%3Djohn
В конце тела закодирован ключ user[name]
. Превращение таких ключей в массив идёт на уровне интерпретатора, в случае PHP, либо самого фреймворка в случае остальных языков.
<?php
$repo = new App\UserRepository();
$app->post('/users', function ($request, $response) use ($repo) {
$validator = new Validator();
$user = $request->getParsedBodyParam('user');
$errors = $validator->validate($user);
if (count($errors) === 0) {
$repo->save($user);
return $response->withRedirect('/users', 302);
}
$params = [
'user' => $user,
'errors' => $errors
];
return $this->get('renderer')->render($response, "users/new.phtml", $params);
});
Обработка данных формы начинается с извлечения данных из тела запроса. Это можно сделать двумя способами, похожими на то как мы извлекаем параметры запроса:
getParsedBody()
– извлекает все данныеgetParsedBodyParam($name, $defaultValue)
– извлекает значение конкретного параметра. Вторым параметром принимает значение по умолчанию.<?php
$user = $request->getParsedBodyParam('user');
Далее нужно убедиться в том, что данные введены верно. Процесс проверки корректности данных называется валидацией. Slim, как и большинство микрофреймворков, не предоставляет никаких механизмов для валидации. Её можно получить из сторонних библиотек. В нашем случае валидация реализуется классом с одним методом validate()
, который проверяет данные формы и возвращает специальный массив $errors
, в котором ключ — это название поля, а значение — текст ошибки, который нужно вывести в форме.
<?php
$validator = new Validator();
// function validate($user)
// {
// $errors = [];
// if (empty($user['name'])) {
// $errors['name'] = "Can't be blank"
// }
//
// // ...
//
// return $errors;
// }
$errors = $validator->validate($user);
Если ошибок нет, то данные формы сохраняются, например, в базу данных. Об этом подробнее в следующем уроке. После сохранения выполняется перенаправление (HTTP redirect). За перенаправление отвечает заголовок Location и статусы с кодом 3XX.
<?php
if (count($errors) === 0) {
$repo->save($user);
return $response->withRedirect('/users');
}
Если в процессе обработки возникли ошибки, выполняется рендеринг формы из того же шаблона, что мы использовали для /users/new. В этот шаблон передаются как данные формы, так и список ошибок. Редиректа не происходит, в адресной строке остаётся адрес /users. Если попробовать в этот момент нажать f5, то браузер выдаст предупреждение о том, что вы пытаетесь повторно отправить данные. Это сообщение предупреждает о том, что метод POST не идемпотентен, и повторная отправка формы может привести к повторному созданию пользователя.
<?php
$params = [
'user' => $user,
'errors' => $errors
];
return $this->get('renderer')->render($response, "users/new.phtml", $params);
Теперь давайте вернёмся к нашей форме и изменим её так, чтобы в неё подставлялись как возникающие ошибки, так и значения полей, введённые пользователем.
<!-- templates/users/new.phtml -->
<form action="/users" method="post">
<div>
<label>
Имя
<input type="text" name="user[name]" value="<?= htmlspecialchars($user['name']) ?>">
</label>
<?php if (isset($errors['name'])): ?>
<div><?= $errors['name'] ?></div>
<?php endif ?>
</div>
<div>
<label>
Email
<input type="email" required name="user[email]" value="<?= htmlspecialchars($user['email']) ?>">
</label>
<?php if (isset($errors['email'])): ?>
<div><?= $errors['email'] ?></div>
<?php endif ?>
</div>
<div>
<label>
Пароль
<input type="password" required name="user[password]" value="<?= htmlspecialchars($user['password']) ?>">
</label>
<?php if (isset($errors['password'])): ?>
<div><?= $errors['password'] ?></div>
<?php endif ?>
</div>
<div>
<label>
Подтверждение пароля
<input type="password" required name="user[passwordConfirmation]" value="<?= htmlspecialchars($user['passwordConfirmation']) ?>">
</label>
</div>
<div>
<label>
Город
<select name="user[city]">
<option value="">Select</option>
<option <?= $user['city'] === '3' ? 'selected' : '' ?> value="3">Москва</option>
<option <?= $user['city'] === '13' ? 'selected' : '' ?> value="13">Пенза</option>
<option <?= $user['city'] === '399' ? 'selected' : '' ?> value="399">Томск</option>
</select>
</label>
<?php if (isset($errors['city'])): ?>
<div><?= $errors['city'] ?></div>
<?php endif ?>
</div>
<input type="submit" value="Sign Up">
</form>
В свою очередь такое изменение формы требует изменения обработчика /users/new. Во избежание ошибок необходимо передать в шаблон пустой массив $errors
и массив $user
, в котором необходимо задать значения по умолчанию для соответствующих полей формы. Таким образом в шаблоне не придётся выполнять проверку данных формы на существование.
<?php
$app->get('/users/new', function ($request, $response) {
$params = [
'user' => ['name' => '', 'email' => '', 'password' => '', 'passwordConfirmation' => '', 'city' => ''],
'errors' => []
];
return $this->get('renderer')->render($response, "users/new.phtml", $params);
});
Обратите внимание на то, как увеличилась в размерах форма. На практике она будет ещё больше из-за дополнительного оформления, например, отступов и подсветки ошибок. Сделав десяток форм вы быстро поймёте что так жить нельзя. Ради простейшей обработки придётся писать много практически идентичного кода в HTML. Эта работа требует автоматизации и, к счастью, давно автоматизирована. Для генерации форм используются специальные билдеры. По традиции, микрофреймворки не имеют встроенных билдеров, поэтому придётся искать их самостоятельно. Довольно популярны формы из фреймворка Symfony. В этом компоненте каждая форма представлена своим собственным классом. Компонент поддерживает валидацию, имеет встроенные механизмы защиты от некоторых атак и многое другое.
Создайте шаблон и добавьте обработчик, выводящий форму создания пользователя с полями: nickname и email. А вот id (должен генерироваться внутри приложения).
Добавьте обработчик, который сохраняет введенные данные. Для хранения пользователя используйте файл. Сами данные можно кодировать в json с помощью json_encode()
и декодировать с помощью json_decode()
. Для работы с файлом используйте функции file_put_contents() и file_get_contents()
После добавления данных в файл должен происходить редирект на адрес /users.
Вам ответят команда поддержки Хекслета или другие студенты.
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
Наши выпускники работают в компаниях:
Зарегистрируйтесь или войдите в свой аккаунт