Универсальный обработчик форм на PHP своими руками

Практически во всех фреймворках реализована очень удобная работа с формами, позволяющая легко сделать валидацию вводимых данных. А что, если проект не настолько крупный, чтоб использовать целый фреймворк, но формы для ввода данных все равно хочется проверять. Сегодня я хочу показать, как на php можно написать достаточно универсальный класс валидации данных.

И так, задача следующая: необходимо проверить соответствие данных, введённых в форму некоторым правилам. В случае ошибок расставить сообщения об ошибках, а также обеспечить сохранность данных в форме. Обработка формы и формирование её кода должно происходить в разных скриптах, поэтому необходимо использовать сессии.

Для начала напишем костячок формы, над которой мы будем проводить эксперименты:

<form method='post'>
Имя<br>
<input type='text' name='name'><br>
E-mail<br>
<input type='text' name='email'><br>
<input type='submit'>
</form>

К примеру, мне хочется, чтоб поле name этой формы было обязательно заполнено, а поле email — содержало корректный e-mail адрес.

Для решения нашей задачи я напишу простенький класс. Суть его работы сводится к следующему: обеспечить проверку соответствия значений, введённых в форму, определённым правилам, сохранить в сессию сообщения об ошибках, сохранить уже введённые данные, чтоб пользователю не приходилось вводить повторно те данные, которые он ввёл верно. Для удобства каждой форме мы назначаем имя. Поскольку мы заранее не знаем, как проверять какое поле формы, мы используем мощнейший инструмент, имеющийся в php — тип данных callable.

<?php
class Form {
    private $name, $values = [], $errors = []; // Для php < 5.4 array ()
	
	/**
	 * Это поле должно быть перекрыто в производном классе. Оно представляет собой ассоциативный массив,
	 * ключи которого - названия полей, а значения представляют собой обычные массивы,
	 * первый элемент - callable функции, проверяющей соответствие нужному правилу, 
	 * а второй - сообщение об ошибке, например (в php < 5.4 нужно использовать полный синтаксис массивов):
	 * ["name"=> ["Form::required", "Необходимо ввести значение"]]
	 * В функцию будут переданны два параметра - массив с данными и имя поля, она должна вернуть 
	 * true в случае, если правило выполнено, или false - в противном случае
	 * @var array 
	 */
	protected $rules = []; // Это поле надо перекрыть в производном классе,
	                       // чтоб указать, какие правила следует использовать
   
    public function __construct($name) {
		$this->name = $name;
		
		// Проверяем, есть ли данные, сохранённые обработчиком в прошлый раз
		if (isset($_SESSION["formdata"][$name])) {
			$this->values = $_SESSION["formdata"][$name];
			// Если есть сохранённые данные, значит есть и ошибки
			$this->errors = $_SESSION["formdata"]["$name-errors"];
			// После того, как мы их считали из сессии, эти данные потеряли актуальность
			unset($_SESSION["formdata"][$name], $_SESSION["formdata"]["$name-errors"]);
		}
    }
	/**
	 * Выводит значение, сохранённое в $values, если там что-то есть
	 * @param string name - имя поля
	 */
	public function value($name) {
	    if (isset($this->values[$name]))
		    echo $this->values[$name];
	}
	
	/**
	 * Выводит ошибку, соответствующую полю с именем $name, если таковая имеется
	 * @param string name - имя поля
	 */
	public function error($name) {
	    if (isset($this->errors[$name]))
		    echo $this->errors[$name];
	}
	
	/**
	  * Проверяет данные, введённые в форму. В случае ошибок также сохраняет данные формы и
	  * сообщения об ошибках в сессию 
	  * @param array $data - данные из формы
	  * @return boolean - true, если данные введены правильно, false - если были ошибки
	  */
	public function validate($data) {
		$errors = [];
		foreach ($this->rules as $field_name=>$rule_options) {
			list($func, $message) = $rule_options;
			$success = call_user_func($func, $data, $field_name);
			if (!$success) {
				$errors[$field_name] = $message;
				// нам не нужно, чтоб выводились ошибочно введённые данные
				unset($data[$field_name]);
			}
		}
		if (!empty($errors)) {
			$_SESSION["formdata"][$this->name] = $data;
			$_SESSION["formdata"]["$this->name-errors"] = $errors;
			return false;
		}
		return true;
	}
	
	// определим несколько пару простейших правил, вы сможете потом доработать свои при желании
	/**
	 * Возвращает true, если поле заполнено
	 * @param array $data  - данные формы
	 * @param string $name - имя поля
	 */
	static public function required(array $data, $name) {
		return !empty($data[$name]);
	}
	
	/**
	 * Возвращает true, если в поле содержится корректный e-mail
	 * @param array $data  - данные формы
	 * @param string $name - имя поля
	 */
	static public function email(array $data, $name) {
		return !empty($data[$name]) && filter_var($data[$name], FILTER_VALIDATE_EMAIL) !== false; 
	}
}

Код данного класса сохраните в form.php. Теперь определим производный класс, который уже будет представлять нашу форму. Назовём его UserForm. Поскольку этот класс будет использоваться в двух файлах —
в скрипте генерации формы и в скрипте проверки введённых значений, запишем его тоже в отдельный файл, к примеру, user_form.php.

<?php
require_once(__DIR__ . "/form.php");
class UserForm extends Form {
	// Фактически, для нашей простой задачи достаточно переопределить поле $rules
	protected $rules =
		[
			"name" => ["Form::required", "Необходимо заполнить имя пользователя"],
			"email"=> ["Form::email", "Необходимо ввести корректный e-mail"],
		];
}

Теперь давайте посмотрим, скрипты, использующие этот класс. К примеру, решим задачу, чтоб в случае правильно введённых данных пользователю выводилось приветствие, а в случае ошибки — требовался повторный ввод. Приветствие будет выводить отдельный скрипт, на который мы будем переадресовывать пользователя (это делается для защиты от повторной отправки данных, ведь в реальных скриптах данные из формы обычно заносятся в базу, отправляются по электронной почте и т.п., поэтому случайное дублирование крайне нежелательно). Вот основной скрипт, который выводит нашу форму в браузер:

<?php
require_once(__DIR__ . "/user_form.php");

session_start();
$form = new UserForm("user");
?>
<!DOCTYPE html>
<html>
<meta charset='utf8'>
<style>
span.error {
  display: block;
  color: red;
}
</style>
<body>
<form method='post' action='process.php'>
	Имя:<br>
	<input type='text' name='name' value="<?php $form->value("name"); ?>"><br>
	<span class='error'><?php $form->error("name"); ?></span>
	Имя:<br>
	<input type='text' name='email' value="<?php $form->value("email"); ?>"><br>
	<span class='error'><?php $form->error("email"); ?></span>
	<input type='submit'>
</form>
</body>
</html>

Сохраните этот код в файл под названием, к примеру, user.php. Теперь напишем код файла process.php. Благодаря тому, что большую часть работы мы уже сделали, он будет совсем простой:

<?php
require_once(__DIR__ . "/user_form.php");

session_start();
$form = new UserForm("user");
if ($form->validate($_POST)) {
	// функция header позволяет добавить http-заголок,
	// а заголовок Location перенаправляет пользователя на другую страницу
	header("Location: hello.php?name=$_POST[name]");
}
else {
    header("Location: user.php");
}

В заключении напишем последний файл нашего небольшого приложения, hello.php. В данном случае, он совсем простой:

<!DOCTYPE html>
<html>
<meta charset='utf8'>
<body>
	<h1>Привет, <?= $_GET["name"]; ?></h1>
</body>
</html>

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

Примечание: Значительную часть функционала класса Form реализует стандартная функция php filter_input_array, однако данный класс, помимо собственно контроля данных, реализует также вывод ошибок и сохранение введённой информации в сессию.

Добавить комментарий

Ваш e-mail не будет опубликован.