PHP PDO Form Validation Showing the Wrong Errors

I’m re-writing a registration form for an application and I’m having some issues with the form validation.

Here is the code I have so far:

<?php

session_start();

include ('api/dbconnect.php');

$msg = "";

if($_SERVER["REQUEST_METHOD"] == "POST") {
	$first_name = $_POST['first_name'];
	$last_name = $_POST['last_name'];
	$email = $_POST['email'];
	$username = $_POST['username'];
	$password = password_hash($_POST['password'], PASSWORD_DEFAULT);

	if(empty($_POST['first_name']) && empty($_POST['last_name']) && empty($_POST['username']) && empty($_POST['password']) && empty($_POST['confirm_pwd'])) {
		$msg = "Please complete the form to add a new user";
	}
	
	if(empty($_POST['first_name'])) {
		$msg = "First Name is required";
	}
	
	if(empty($_POST['last_name'])) {
		$msg = "Last Name is required";
	}
	
	if(empty($_POST['username'])) {
		$msg = "A username is required";
	}
	
	if(empty($_POST['password'])) {
		$msg = "A password is required";
	}
	
	if(!preg_match("/[A-Za-z0-9]+/", $_POST['username'])) {
 		$msg = "The username provided is invalid";
	}
	
	if(strlen($_POST['password']) > 20 || strlen($_POST['password']) < 5) {
		$msg = "Password must be between 5 and 20 characters";
	}
	
	if($_POST['password'] != $_POST['confirm_pwd']) {
		$msg = "The two passwords do not match";
	}
	
	/*if (!filter_var($_POST['email'], FILTER_VALIDATE_EMAIL)) {
  		$msg = "The email address provided is invalid";
	}*/
	
	/*
	$sql = "INSERT INTO users (first_name, last_name, email, username, password) VALUES (?, ?, ?, ?, ?)";
	$stmt = $pdo->prepare($sql);
	$stmt->bindParam(1, $_POST['first_name']);
	$stmt->bindParam(2, $_POST['last_name']);
	$stmt->bindParam(3, $_POST['email']);
	$stmt->bindParam(4, $_POST['username']);
	$stmt->bindParam(5, $password);
	$result = $stmt->execute();
	header('Location: users.php');
	exit(); */

}
						
?>

<!DOCTYPE html>
<html lang="en">

<head>

  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <meta name="description" content="">
  <meta name="author" content="">

  <title>CABGOP | Add New User</title>

  <!-- Custom fonts for this template-->
  <link href="vendor/fontawesome-free/css/all.min.css" rel="stylesheet" type="text/css">
  <link href="https://fonts.googleapis.com/css?family=Nunito:200,200i,300,300i,400,400i,600,600i,700,700i,800,800i,900,900i" rel="stylesheet">

  <!-- Custom styles for this template-->
  <link href="css/sb-admin-2.min.css" rel="stylesheet">

</head>

<body style="background-color: #2658a8;">

  <div class="container">

    <div class="card o-hidden border-0 shadow-lg my-5">
      <div class="card-body p-0">
        <!-- Nested Row within Card Body -->
        <div class="row">
          <div class="col-lg-5 d-none d-lg-block"><img src="img/cabgop_newuser.png" style="background-color: #d8342b;" width="475" height="530"></div>
          <div class="col-lg-7">
            <div class="p-5">
              <div class="text-center">
                <h1 class="h4 text-gray-900 mb-4">Add New User</h1>
              </div>
              <form class="user" method="post" action="user_new.php">
                <div class="form-group row">
                  <div class="col-sm-6 mb-3 mb-sm-0">
                    <input type="text" class="form-control form-control-user" id="first_name" name="first_name" placeholder="First Name" autocomplete="off">
                  </div>
                  <div class="col-sm-6">
                    <input type="text" class="form-control form-control-user" id="last_name" name="last_name" placeholder="Last Name" autocomplete="off">
                  </div>
                </div>
                <div class="form-group row">
				<div class="col-sm-6 mb-3 mb-sm-0">
                  <input type="email" class="form-control form-control-user" id="email" name="email" placeholder="Email Address (optional)" autocomplete="off">
				  </div> 
				<div class="col-sm-6">
                    <input type="text" class="form-control form-control-user" id="username" name="username" placeholder="Username" autocomplete="off">
                  </div>
				  </div>
                <div class="form-group row">
                  <div class="col-sm-6 mb-3 mb-sm-0">
                    <input type="password" class="form-control form-control-user" id="password" name="password" placeholder="Password" autocomplete="off">
                  </div>
                  <div class="col-sm-6">
                    <input type="password" class="form-control form-control-user" id="confirm_pwd" name="confirm_pwd" placeholder="Confirm Password" autocomplete="off">
                  </div>
				  </div>
				  <div class="custom-control small">
			  	     <p style="text-align: center"><b class="text-danger"><?php echo $msg; //echo '<pre>'.print_r($_POST,true).'</pre>'; ?></b></p>
			  	  </div>			  				  
				<button type="submit" name="register-submit" class="btn btn-primary btn-user btn-block" style="background-color: #a40000; border-color: #a40000;">Create User Account</button>
				<hr>
				<a href="users.php" class="btn btn-warning btn-user btn-block" style="background-color: #2658a8; border-color: #2658a8;">Return to Users</a>
              </form>
            </div>
          </div>
        </div>
      </div>
    </div>

  </div>

  <!-- Bootstrap core JavaScript-->
  <script src="vendor/jquery/jquery.min.js"></script>
  <script src="vendor/bootstrap/js/bootstrap.bundle.min.js"></script>

  <!-- Core plugin JavaScript-->
  <script src="vendor/jquery-easing/jquery.easing.min.js"></script>

  <!-- Custom scripts for all pages-->
  <script src="js/sb-admin-2.min.js"></script>

</body>

</html>

The way I want it to work is:

  • First Name, Last Name, Username, Password and Confirm Password are required
  • Password and Confirm Password need to match
  • Password must be between 5 and 20 characters

So, the problem is that the form works and the errors appear, but the errors are mixed up. For example, if I try to submit a blank form, then the error message Password must be between 5 and 20 characters shows up. If I comment out that error and resubmit a blank form, then the previous error (The username provided is invalid) appears. The two passwords do not match error is working as expected. This is the current form with the error message shown:

image

How can I tell the code to show the right error?

What is the ‘right error’?

Let’s fix one obvious error:
if(strlen($_POST['password']) > 20 && strlen($_POST['password']) < 5) {
How can a string be both longer than 20 characters AND shorter than 5 characters?

Oops, I was messing around earlier and made that mistake. Yeah, I know it can’t be both. It needs to be OR, one or the other.

What I mean is if I don’t enter anything in this form, I need this line:

if(empty($_POST['first_name']) && empty($_POST['last_name']) && empty($_POST['username']) && empty($_POST['password']) && empty($_POST['confirm_pwd'])) {
		$msg = "Please complete the form to add a new user";
	}

to work. If I fill out the form, but not the first name, I need this to work:

if(empty($_POST['first_name'])) {
		$msg = "First Name is required";
	}

and so on. Right now, it skips all of them, and goes straight to:

if(strlen($_POST['password']) > 20 || strlen($_POST['password']) < 5) {
		$msg = "Password must be between 5 and 20 characters";
	}

I need to figure out how to make the right error work with the right code.

Ah, but it doesn’t skip them.
It goes through them.
Your code says:

$msg = "";

Set the variable $msg to the empty string.

if(empty($_POST['first_name']) && empty($_POST['last_name']) && empty($_POST['username']) && empty($_POST['password']) && empty($_POST['confirm_pwd'])) {
		$msg = "Please complete the form to add a new user";
	}

If all of those things are empty, which they are, set $msg to “Please complete the form”
Then it continues

if(empty($_POST['first_name'])) {
		$msg = "First Name is required";
	}

First_name is still empty, so your code does exactly what you told it to do: set $msg to “First name is required”…
Can you see the problem that’s forming?

So, the code is not stopping…

Okay, so how do I make it stop? I know about using exit(), but this turns everything into a blank white page…

Well, there’s a couple of ways of going about this.

If you want it to just stop at the first error it comes across, instead of doing

if X {
  A
}
if Y {
  B
}

, string them all together as one great big if:

if X {
  A
} elseif Y {
 B
}
...
else {
  There were no errors, do Z.
}

Alternatively, you can gather up the individual errors into an array, and let the user know they missed multiple things, outputting something like:

First Name is required
A username is required

So, something like this?

if(empty($_POST['first_name']) && empty($_POST['last_name']) && empty($_POST['username']) && empty($_POST['password']) && empty($_POST['confirm_pwd'])) {
		$msg = "Please complete the form to add a new user";
	} elseif(empty($_POST['first_name'])) {
		$msg = "First Name is required";
	} elseif(empty($_POST['last_name'])) {
		$msg = "Last Name is required";
	} elseif(empty($_POST['username'])) {
		$msg = "A username is required";
	} elseif(empty($_POST['password'])) {
		$msg = "A password is required";
	} elseif(!preg_match("/[A-Za-z0-9]+/", $_POST['username'])) {
 		$msg = "The username provided is invalid";
	} elseif(strlen($_POST['password']) > 20 || strlen($_POST['password']) < 5) {
		$msg = "Password must be between 5 and 20 characters";
	} elseif($_POST['password'] != $_POST['confirm_pwd']) {
		$msg = "The two passwords do not match";
	}elseif (!filter_var($_POST['email'], FILTER_VALIDATE_EMAIL)) {
  		$msg = "The email address provided is invalid";
	} else {
	
	/*
	$sql = "INSERT INTO users (first_name, last_name, email, username, password) VALUES (?, ?, ?, ?, ?)";
	$stmt = $pdo->prepare($sql);
	$stmt->bindParam(1, $_POST['first_name']);
	$stmt->bindParam(2, $_POST['last_name']);
	$stmt->bindParam(3, $_POST['email']);
	$stmt->bindParam(4, $_POST['username']);
	$stmt->bindParam(5, $password);
	$result = $stmt->execute();
	header('Location: users.php');
	exit(); */

	}

To steal a line from Rudy…
“What happens when you try it?” ™

It works, but I just thought of something that I don’t have.

I need to add a constraint to prevent a user signing up with a duplicate username.

How do I do this?

Depends on how you have constructed your table.

Is username a key on your table?

Yes, it is:

it doesnt appear to be a key on the table. There’s no Unique constraint visible, the primary key is “id”.

Oh, I see what you mean:

If you’ve made it a Unique field in the table, then when you try to insert it, it will fail if you insert a duplicate username, so you would check to see if your query succeeded or not.

So:

if(!$stmt) {
     $msg = "Another account with this username already exists."
}

$result is the result of your query, not $stmt.

Notice: Undefined variable: result in C:\xampp\htdocs\cabgop\user_new.php on line 49

if($_SERVER["REQUEST_METHOD"] == "POST") {
	$first_name = $_POST['first_name'];
	$last_name = $_POST['last_name'];
	$email = $_POST['email'];
	$username = $_POST['username'];
	$password = password_hash($_POST['password'], PASSWORD_DEFAULT);

	if(empty($_POST['first_name']) && empty($_POST['last_name']) && empty($_POST['username']) && empty($_POST['password']) && empty($_POST['confirm_pwd'])) {
		$msg = "Please complete the form to add a new user";
	} elseif(empty($_POST['first_name'])) {
		$msg = "First Name is required";
	} elseif(empty($_POST['last_name'])) {
		$msg = "Last Name is required";
	} elseif(empty($_POST['username'])) {
		$msg = "A username is required";
	} elseif(empty($_POST['password'])) {
		$msg = "A password is required";
	} elseif(!preg_match("/[A-Za-z0-9]+/", $_POST['username'])) {
 		$msg = "The username provided is invalid";	
	} elseif(strlen($_POST['password']) > 20 || strlen($_POST['password']) < 5) {
		$msg = "Password must be between 5 and 20 characters";
	} elseif($_POST['password'] != $_POST['confirm_pwd']) {
		$msg = "The two passwords do not match";
	}elseif (!filter_var($_POST['email'], FILTER_VALIDATE_EMAIL)) {
  		$msg = "The email address provided is invalid";
	} else {
	
	$sql = "INSERT INTO users (first_name, last_name, email, username, password) VALUES (?, ?, ?, ?, ?)";
	$stmt = $pdo->prepare($sql);
	$stmt->bindParam(1, $_POST['first_name']);
	$stmt->bindParam(2, $_POST['last_name']);
	$stmt->bindParam(3, $_POST['email']);
	$stmt->bindParam(4, $_POST['username']);
	$stmt->bindParam(5, $password);
	$result = $stmt->execute();
	header('Location: users.php');
	exit();

	}
	
	if(!$result) {
		$msg = "Another account with this username already exists.";
	}

Think about where you should be putting that if. It’s in the wrong place.

I moved it inside the query, and I get this:

Fatal error: Uncaught PDOException: SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'jmyrtle' for key 'username' in C:\xampp\htdocs\cabgop\user_new.php:43 Stack trace: #0 C:\xampp\htdocs\cabgop\user_new.php(43): PDOStatement->execute() #1 {main} thrown in C:\xampp\htdocs\cabgop\user_new.php on line 43

if($_SERVER["REQUEST_METHOD"] == "POST") {
	$first_name = $_POST['first_name'];
	$last_name = $_POST['last_name'];
	$email = $_POST['email'];
	$username = $_POST['username'];
	$password = password_hash($_POST['password'], PASSWORD_DEFAULT);

	if(empty($_POST['first_name']) && empty($_POST['last_name']) && empty($_POST['username']) && empty($_POST['password']) && empty($_POST['confirm_pwd'])) {
		$msg = "Please complete the form to add a new user";
	} elseif(empty($_POST['first_name'])) {
		$msg = "First Name is required";
	} elseif(empty($_POST['last_name'])) {
		$msg = "Last Name is required";
	} elseif(empty($_POST['username'])) {
		$msg = "A username is required";
	} elseif(empty($_POST['password'])) {
		$msg = "A password is required";
	} elseif(!preg_match("/[A-Za-z0-9]+/", $_POST['username'])) {
 		$msg = "The username provided is invalid";	
	} elseif(strlen($_POST['password']) > 20 || strlen($_POST['password']) < 5) {
		$msg = "Password must be between 5 and 20 characters";
	} elseif($_POST['password'] != $_POST['confirm_pwd']) {
		$msg = "The two passwords do not match";
	/*}elseif (!filter_var($_POST['email'], FILTER_VALIDATE_EMAIL)) {
  		$msg = "The email address provided is invalid";*/
	} else {
	
	$sql = "INSERT INTO users (first_name, last_name, email, username, password) VALUES (?, ?, ?, ?, ?)";
	$stmt = $pdo->prepare($sql);
	$stmt->bindParam(1, $_POST['first_name']);
	$stmt->bindParam(2, $_POST['last_name']);
	$stmt->bindParam(3, $_POST['email']);
	$stmt->bindParam(4, $_POST['username']);
	$stmt->bindParam(5, $password);
	$result = $stmt->execute();
	header('Location: users.php');
	exit();

	if(!$result) {
		$msg = "Another account with this username already exists.";
	}
	}
}

Oh sorry, you’ve got PDO in Exception throwing mode. try your execute, and if you catch a result then there was a duplication of a username.