Skip to content

Enforcing validation on user input

Greg Bowler edited this page Mar 5, 2023 · 16 revisions

Adding attributes like required, min or type=email to HTML inputs automatically enables client-side validation in the browser, but to enforce validation on the server, this library has to be triggered in your PHP code, so any validation errors can be handled, and appropriate error messages can be added to the invalid form elements.

There's no set method for adding validation error messages to your HTML, so this library provides a mechanism to apply your own validation logic in a generic way to all form submissions.

Calling the validate() function

With an HTMLDocument representing the current page (where the form is), and a reference to the <form> element you wish to validate, call the validate() function of a Validator object, passing in the reference to the Form and the submitted user input to check against.

The validate() function will throw a ValidationException if there are any errors with the form's validation. You can catch this exception and output appropriate error messages on the invalid fields.

Catching the ValidationException

If any of the submitted user input is invalid according to the provided <form>, a ValidationException will be thrown. However, the exception only means that there is invalid data - the Validator object will give you a list of all the fields that are invalid, with the appropriate error messages for each field.

The general approach to handling invalid fields is to loop over the array of errors within the catch block. The Validator::getErrorList() function returns an associative array of all errors, where the key of the array is the name of the form input, and the value of the array is the error message string.

function example(HTMLDocument $document, Input $input):void {
// First, obtain a reference to the form we wish to validate.
	$form = $document->querySelector("#example-form");

	try {
// Then "try" to validate the form with the submitted user input:
		$validator->validate($form, $input);
	}
	catch(ValidationException) {
// If there are any validation errors, we can iterate over them to display
// to the page, and return early as to not action the user input.
		foreach($validator->getLastErrorList() as $name => $message) {
// Here we can add an attribute to the parent of the input, for displaying
// the error message using CSS, for example.
			$errorElement = $form->querySelector("[name=$name]");
			$errorElement->parentNode->dataset->validationError = $message;
		}
        
	// Return early so user input isn't used when there are validation errors. 
		return;
	}

	// Finally, if the input contains no validation errors, continue as usual.
}

The Document and Form objects

In the above example, $document is assumed to be constructed as a Gt\Dom\HTMLDocument from PHP.Gt/Dom. In WebEngine applications, this is provided automatically. An example for both WebEngine and plain PHP are available below.

The first parameter of the validate function is the $form element. It must be a Gt\Dom\Element with an elementType of HTMLFormElement, representing the <form> in the Document that you wish to validate.

The user input

The second parameter of the validate function is the user's input. This can be the value of $_POST, but most frameworks provide an object oriented alternative to the $_POST superglobal. Any form of iterable<string, string>, or an associative array, can be used to pass in the user input to the validate function, as long as the key corresponds to the name of the input field, and the value corresponds to the value of the input field.

Plain PHP example

$html = <<<HTML
<!doctype html>
<style>
[data-validation-error] {
	border-left: 2px solid red;
}
[data-validation-error]::before {
	content: attr(data-validation-error);
	color: red;
	font-weight: bold;
}
label {
	display: block;
	padding: 1rem;
}
label span {
	display: block;
}
</style>
<form method="post">
	<label>
		<span>Your name</span>
		<input name="name" required />	
	</label>
	<label>
		<span>Credit Card number</span>
		<input name="credit-card" pattern="\d{16}" required />
	</label>
	<label>
		<span>Expiry month</span>
		<input name="expiry-month" type="number" min="1" max="12" required />	
	</label>
	<label>
		<span>Expiry year</span>
		<input name="expiry-year" type="number" min="18" max="26" required />
	</label>
	<label>
		<span>amount</span>
		<input name="amount" type="number" value="100.50" readonly required />
	</label>
	<button name="do" value="buy">Buy the thing!</button>
</form>
HTML;

function example(HTMLDocument $document, array $input) {
	$validator = new Validator();
	$form = $document->forms[0];

	try {
		$validator->validate($form, $input);
	}
	catch(ValidationException $exception) {
		foreach($validator->getErrorList() as $name => $message) {
			$errorElement = $form->querySelector("[name=$name]");
			$errorElement->parentNode->dataset->validationError = $message;
		}
		return;
	}

	echo "Payment succeeded!";
	exit;
}

$document = new HTMLDocument($html);

if(isset($_POST["do"]) && $_POST["do"] === "buy") {
	example($document, $_POST);
}

echo $document;

WebEngine example

In WebEngine applications, the HTMLDocument, Validator and Input are all available via dependency injection, using the do function's parameters.

example.html:

<!doctype html>
<style>
[data-validation-error] {
	border-left: 2px solid red;
}
[data-validation-error]::before {
	content: attr(data-validation-error);
	color: red;
	font-weight: bold;
}
label {
	display: block;
	padding: 1rem;
}
label span {
	display: block;
}
</style>
<form method="post">
	<label>
		<span>Your name</span>
		<input name="name" required />	
	</label>
	<label>
		<span>Credit Card number</span>
		<input name="credit-card" pattern="\d{16}" required />
	</label>
	<label>
		<span>Expiry month</span>
		<input name="expiry-month" type="number" min="1" max="12" required />	
	</label>
	<label>
		<span>Expiry year</span>
		<input name="expiry-year" type="number" min="18" max="26" required />
	</label>
	<label>
		<span>amount</span>
		<input name="amount" type="number" value="100.50" readonly required />
	</label>
	<button name="do" value="buy">Buy the thing!</button>
</form>
use Gt\Dom\HTMLDocument;
use Gt\DomValidation\Validator;
use Gt\Input\Input;
use Gt\DomValidation\ValidationException;

function do_submit(HTMLDocument $document, Validator $validator, Input $input):void {
// First, obtain a reference to the form we wish to validate.
	$form = $document->querySelector("#example-form");

	try {
// Then "try" to validate the form with the submitted user input:
		$validator->validate($form, $input);
	}
	catch(ValidationException) {
// If there are any validation errors, we can iterate over them to display
// to the page, and return early as to not action the user input.
		foreach($validator->getLastErrorList() as $name => $message) {
// Here we can add an attribute to the parent of the input, for displaying
// the error message using CSS, for example.
			$errorElement = $form->querySelector("[name=$name]");
			$errorElement->parentNode->dataset->validationError = $message;
		}
        
// Return early so user input isn't used when there are validation errors. 
		return;
	}

// Finally, if the input contains no validation errors, continue as usual.
}

Next, learn about extending custom rules.