Yes the form processing code and the form should be on the same webpage. Here’s an example, with just an email field -
<?php
// initialization
session_start();
$post = []; // array to hold a trimmed working copy of the form data
$errors = []; // array to hold user/validation errors
// post method form processing, detect if a post method form has been submitted
if($_SERVER['REQUEST_METHOD'] == 'POST')
{
// trim all the data at once
$post = array_map('trim',$_POST); // if any input is an array, use a recursive trim call-back function here instead of php's trim
// validate inputs
if($post['email'] === '')
{
$errors['email'] = "Email is required.";
}
else if(!filter_var($post['email'], FILTER_VALIDATE_EMAIL))
{
$errors['email'] = "Email format is not valid.";
}
// if no errors, use the form data
if(empty($errors))
{
// use the data in $post here...
}
// if no errors, success
if(empty($errors))
{
$_SESSION['success_message'] = "Some success message.";
die(header("Refresh:0"));
}
}
// html document starts here...
?>
<?php
// output and clear any success message
if(isset($_SESSION['success_message']))
{
echo "<p>{$_SESSION['success_message']}</p>";
unset($_SESSION['success_message']);
}
?>
<?php
// output any errors
if(!empty($errors))
{
echo "<p>".implode('<br>',$errors)."</p>";
}
?>
<?php
// output the form, repopulating any field values with the existing form data
?>
<form method='post' autocomplete='off'>
<label>Email: <input type='email' name='email' value='<?=htmlentities($post['email']??'',ENT_QUOTES)?>'></label><br>
<input type='submit'>
</form>
An enhancement of this, when you have more than 2-3 fields, is to use a data-driven design, where you have a data structure (array, database table) that defines the fields, validation rules, and processing, that you use to dynamically validate and process the form data, rather than to write out bespoke code for each field.
I just tested the commonly suggested Post Redirect Get (PRG) practice used in this, to prevent the form from resubmitting data and it does work, but only for the last/single form submission. If there is more than one submission of the same form, such as when correcting multiple validation errors, the browser will offer to resubmit the invalid form data at the previous step(s) when using the back-button. If you do reload the page when offered to resubmit the form data, the form will get redisplayed with the old, invalid, values. It doesn’t appear that there is a 100% sure way of preventing the browser from caching these previously submitted sets of form data, except closing the browser, hence the common suggestion to always close the browser when using a public computer.
The above undesirable operation can be partially corrected by storing a value in a session variable that indicates the form has been completed (or not unsetting the success message and testing it), then not processing any of the resubmitted form data during the current browser session (the browser will still offer to do so when using the back-button.)
BTW - in your example markup, since the <label></label>
tags are around the field, there’s no need for the for=’…’ attributes. If you did need the for=’…’ attributes, there must be a corresponding id=’…’ attribute, which your example doesn’t have.