facebook twitter
Webucator's Free PHP Tutorial

Lesson: PHP and HTML Forms

Welcome to our free PHP tutorial. This tutorial is based on Webucator's Introduction to PHP Training course.

A common way to pass data from one page to another is through HTML forms. There are two methods of submitting data through a form: the get method and the post method. In this lesson, you will learn to use both.

Lesson Goals

  • To process form data with PHP.

HTML Forms

How HTML Forms Work

A common way to pass data from one page to another is through HTML forms. There are two methods of submitting data through a form: the get method and the post method. The method used is determined by the value of the method attribute of the form tag. The default method is get.

Get Method

When the get method is used, data is sent to the server in name-value pairs as part of the query string. The get method is most commonly used by search pages and is useful when it is important to be able to bookmark the resulting page (i.e, the page that is returned after the form is submitted).

The get method is the default method for delivering all pages, not just action pages of forms. To see this, do the following:

  1. Navigate to http://localhost:8888/Webucator/php/Forms/Demos/ in Google Chrome.
  2. Open Chrome DevTools and make sure the Network tab is selected.
  3. Click on hello-who.html.
  4. In the bottom left of DevTools, click on hello-who.html and look at the Headers on the right side: Beatles Greet Network TabNotice the Request Method is GET.
  5. Now click on one of the links on the page and notice that the Request Method for that page is also GET:Paul Greet Network Tab
  6. Finally, in the Headers section in the bottom right, scroll down to the Query String Parameters section and notice that it tells you what parameters are being passed in with the request:Paul Greet Network Tab Params
  7. As we have already seen, we can access those parameters in PHP with $_GET['beatle'] and $_GET['greeting'].

Now take a look at the following code sample:

Code Sample:

Forms/Demos/hello-who-get.html
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="../../static/styles/normalize.css">
<link rel="stylesheet" href="../../static/styles/styles.css">
<title>Greeting Form - GET Method</title>
</head>
<body>
<main>
  <p>Please fill out the form below.</p>
  <form method="get" action="hello-who-get.php">
    <label for="first-name">First Name:</label>
    <input name="first-name" id="first-name">
    <label for="greeting">Greeting:</label>
    <input name="greeting" id="greeting">
    <button class="wide">Submit</button>
  </form>
</main>
</body>
</html>

Code Explanation

  1. Notice that the form uses the get method.
  2. Navigate to http://localhost:8888/Webucator/php/Forms/Demos/hello-who-get.html in Google Chrome.
  3. Make sure Chrome DevTools is open to the Network tab.
  4. Fill out and submit the form.
  5. Notice in the Network tab that the Request Method is GET and that you can see the Query String Parameters at the bottom of the Headers section:hello-who-get.php

The important takeaway here is that PHP doesn't know that hello-who-get.php was requested via a form. It would return the exact same page if someone entered the following URL directly in the location bar of the browser:

http://localhost:8888/Webucator/php/Forms/Demos/hello-who-get.php?first-name=Nat&greeting=Howdy

And that is why these pages are bookmarkable and shareable.

Post Method

When the post method is used, data is sent to the server in name-value pairs behind the scenes. Take a look at the code below:

Code Sample:

Forms/Demos/hello-who-post.html
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="../../static/styles/normalize.css">
<link rel="stylesheet" href="../../static/styles/styles.css">
<title>Greeting Form - POST Method</title>
</head>
<body>
<main>
  <p>Please fill out the form below.</p>
  <form method="post" action="hello-who-post.php">
    <label for="first-name">First Name:</label>
    <input name="first-name" id="first-name">
    <label for="greeting">Greeting:</label>
    <input name="greeting" id="greeting">
    <button class="wide">Submit</button>
  </form>
</main>
</body>
</html>

Code Explanation

  1. The only difference between this form and the last one is that this one uses the post method.
  2. Navigate to http://localhost:8888/Webucator/php/Forms/Demos/hello-who-post.html in Google Chrome.
  3. Make sure Chrome DevTools is open to the Network tab.
  4. Fill out and submit the form.
  5. Notice in the Network tab that the Request Method is POST and that you can see the Form Data at the bottom of the Headers section:hello-who-post.php

The action pages for the get and post forms are almost identical. The only difference is that one gets the $_GET parameters and the other gets the $_POST parameters:

Code Sample:

Forms/Demos/hello-who-get.php
<?php
  $firstName = $_GET['first-name'];
  $greeting = $_GET['greeting'];
?>
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="../../static/styles/normalize.css">
<link rel="stylesheet" href="../../static/styles/styles.css">
<title><?= $greeting . ', ' . $firstName ?>!</title>
</head>
<body>
<main>
<?php
  echo "<p>$greeting, $firstName!</p>";
?>
</main>
</body>
</html>

Code Explanation

Code Sample:

Forms/Demos/hello-who-post.php
<?php
  $firstName = $_POST['first-name'];
  $greeting = $_POST['greeting'];
?>
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="../../static/styles/normalize.css">
<link rel="stylesheet" href="../../static/styles/styles.css">
<title><?= $greeting . ', ' . $firstName ?>!</title>
</head>
<body>
<main>
<?php
  echo "<p>$greeting, $firstName!</p>";
?>
</main>
</body>
</html>

Use Post for Most Forms

The two major advantages of the post method are:

  • The name-value pairs are not visible in the location bar, so sensitive data such as passwords are not displayed on the screen.
  • Files, such as images and Office documents, can be uploaded via the form.

The major disadvantage is that the resulting page cannot be bookmarked.

As a general rule, you should use post unless you want the user to be able to bookmark or share (e.g., via email) the resulting web page.

A Note for Node Users

If you have used Node, you might be used to a routing system that doesn't have a one-to-one relationship between files and URLs (not including the query string). For example, you might navigate to http://localhost:8080/hello-world.html and see a web page, even though there is no hello-world.html file on the server. Likewise, you could have a form that submits to "/process", without any corresponding file named process on the server.

While it's possible to set your web server up to behave in a similar way for sites running on PHP, it is not the norm. Typically, with sites running PHP, when you make requests for pages (e.g., hello-world.html or process.php), there are actual files with those names on the server.

Form Submissions

The table below describes how data from different types of form fields are sent to the server when a form is submitted. Note that the table assumes that all the fields are enabled. Disabled form fields will not get sent to the server.

How HTML Form Data is Submitted
Field On Form Submission
text-like and textarea Always sent. If left blank, an empty string is sent.
select Always sent.
radio buttons Only sent if a selection is made.
checkbox If checked, the default value of "on" is sent unless otherwise set in the HTML. If not checked, variable is not sent.
submit button This applies to both input elements of type submit and button elements of type submit (the default). If a submit button has a name, it will be sent along with its value. Note that if there are multiple submit buttons, only the one that is clicked will get sent. If the user submits the form by pressing the Enter key while focus is on a text-like field, the first submit button in the form will be used to submit the form.

Sanitizing Form Data

When form data is submitted, the data needs to be sanitized (cleaned).

The first step to sanitize text entered through a form is to remove any unwanted whitespace before and after the entry. This is done with the trim() function. For example:

$firstName = trim($_POST['first-name']);

This way, if the user enters ' Bruce ' with spaces before and after the name, $firstName will get 'Bruce' without the spaces.

Depending on what you plan to do with the data, that may be all you need to do. However, if you plan to output the data to the browser, you must further sanitize it first. The following demo illustrates why:

Code Sample:

Forms/Demos/not-sanitized.php
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="../../static/styles/normalize.css">
<link rel="stylesheet" href="../../static/styles/styles.css">
<title>Not Sanitized</title>
</head>
<body>
  <main>
    <h1>Not Sanitized</h1>
    <?php
      $q = $_GET['q'] ?? '';
      if ($q) {
        echo "<p>You searched for <strong>$q</strong>.</p>";
      }
    ?>
    <form>
      <label for="q">Search:</label>
      <input name="q" id="q" value="<?= $q ?>">
      <button class="wide">SEARCH</button>
    </form>
  </main>
</body>
</html>

Code Explanation

This page simply outputs the user input back to the text field. This is a common practice when the user submission contains errors. Rather than making the user fill out the form from scratch, we put the user's original entries back into the form. To see this:

  1. Navigate to http://localhost:8888/Webucator/php/Forms/Demos/not-sanitized.php.
  2. Enter a search query and press SEARCH.
  3. Your query will show up in the search box, like this: Not Sanitize Safe Search
  4. That looks fine. Now enter x" onfocus="alert(1) like this: XSS Attempt And press SEARCH.
  5. The PHP page will process the following:
    <input name="q" id="q" value="<?= $q ?>">
    ... resulting in:
    <input name="q" id="q" value="x" onfocus="alert(1)">
    Notice that JavaScript has been written into the page. Although this JavaScript is relatively harmless, more nefarious hackers could attempt to inject something more dangerous that uses cross-site scripting (XSS) to do something more dangerous.
  6. Some modern browsers, like Google Chrome, will refuse to show the resulting page. They will recognize that unsafe data from the form is being used in the code. Safari will show the page, but will not execute the JavaScript. But you, as the developer, should not rely on the browsers to catch these attempts for you. You need to sanitize your data on the server-side to prevent that code getting returned to the browser.

PHP provides many options for sanitizing data. A few of the most useful are listed below:

htmlspecialchars()

The htmlspecialchars() function converts <, >, &, and " to &lt;, &gt;, &amp;, and &quot;, respectively. Notably, it does not convert an apostrophe (') to an HTML entity by default. However, you can force it to do so by passing in the built-in constant ENT_QUOTES as a second argument:

htmlspecialchars("'Hello, world!'", ENT_QUOTES);
// &#039;Hello, world!&#039;

htmlentities()

The htmlentities() function is the same as htmlspecialchars() except that it also converts characters that have equivalent HTML entities to their entity equivalents. For example, it converts © to &copy;. Like with htmlspecialchars(), htmlentities() does not convert an apostrophe (') to an HTML entity by default. However, you can force it to do so by passing in the built-in constant ENT_QUOTES as a second argument:

htmlentities("'Hello, world!'", ENT_QUOTES);
// &#039;Hello, world!&#039;

filter_var()

The filter_var() function is used to sanitize and validate data. There are many built-in constants for sanitizing and validating different types of data. For making strings safe to output to the browser, the most useful of these is FILTER_SANITIZE_STRING. Note that this will completely remove less than and greater than signs, which may or may not be what you want.

$q = filter_var($_GET['q'], FILTER_SANITIZE_STRING);

If q does not exist in the $_GET array, the code above will generate an Undefined index notice. This won't break anything and, depending on your error-display settings, may go completely unnoticed, but it's better practice not to reference variables unless they have been defined. As such, you may want to nest this in a condition:

if (isset($_GET['q']) {
  $q = filter_var($_GET['q'], FILTER_SANITIZE_STRING);
}

filter_input()

The filter_input() function is similar to filter_var(), but it is specifically used with the built-in superglobals, most commonly with $_GET and $_POST. For example, the code below would assign a sanitized value of $_GET['q'] to $q. Like filter_var(), this will completely remove less than and greater than signs, which may or may not be what you want.

$q = filter_input(INPUT_GET, 'q', FILTER_SANITIZE_STRING);

Unlike filter_var(), if q does not exist in the $_GET array, filter_input() will not generate a notice. Instead it will return NULL, which conveniently converts to an empty string when treated as a string.

The table below shows what these functions will output if passed $_GET['q'] when it contains the following string: © " ' < > &

Function Output
htmlspecialchars($_GET['q']) © &quot; ' &lt; &gt;
htmlentities($_GET['q']) &copy; &quot; ' &lt; &gt;
htmlentities($_GET['q'], ENT_QUOTES) &copy; &quot; &#039; &lt; &gt;
filter_var($_GET['q'], FILTER_SANITIZE_STRING) © &#34; &#39;
filter_input(INPUT_GET, 'q', FILTER_SANITIZE_STRING) © &#34; &#39;

The option you choose depends on what you intend to do with the sanitized data. If you're just outputting the data to the form fields, htmlentities($_GET['q'], ENT_QUOTES) is a good option. Here is the code we saw earlier, but this time we sanitize the submitted value:

Code Sample:

Forms/Demos/sanitized.php
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="../../static/styles/normalize.css">
<link rel="stylesheet" href="../../static/styles/styles.css">
<title>Sanitized</title>
</head>
<body>
  <main>
    <h1>Sanitized</h1>
    <?php
      $q = $_GET['q'] ?? '';
      $q = htmlentities($q, ENT_QUOTES);
      if ($q) {
        echo "<p>You searched for <strong>$q</strong>.</p>";
      }
    ?>
    <form>
      <label for="q">Search:</label>
      <input name="q" id="q" value="<?= $q ?>">
      <button class="wide">SEARCH</button>
    </form>
  </main>
</body>
</html>

Code Explanation

Visit http://localhost:8888/Webucator/php/Forms/Demos/sanitized.php and search for x" onfocus="alert(1) again:Sanitized SearchThis time, it replaces the quotation marks with the &quot; entity. This is easier to see if you view the source of the page:Sanitized Search Source

Validating Form Data

After sanitizing the form data, the next step is to validate it. A common practice is to validate all the fields that require validation and store any errors found in an array. To do so, start by creating an empty $errors array:

$errors = [];

Was the Field Filled In?

Often you will simply want to validate that the user entered a value. There are a variety of ways to do this.

Treating the Variable as a Boolean

If you know the variable exists, you can simply treat it as a boolean:

if (!$q) {...

If you have already assigned a value to $q then this technique is fine. One approach, which we will use in the upcoming exercise, is to store sanitized values submitted via the form in a new array, like this:

$f = []; // f for form
$f['q'] = trim($_POST['q'] ?? '');

Notice that this uses the null coalescing operator (??) to assign an empty string to $f['q'] if $_POST['q'] does not exist.

Once we have done this, we can then safely treat $f['q'] as a boolean:

if (!$f['q']) {
  $errors[] = 'You must include a search value.';
}

When finished checking all the fields, you can then check to see if there are any errors by treating $errors as a boolean. Empty arrays are falsy:

if ($errors) {
  // Show form errors
}

Is the Entered Value an Integer?

In addition to sanitizing data the filter_var() and filter_input() functions can validate data using the FILTER_VALIDATE constants. Both functions will return false if the validation fails and will return the filtered value of the variable if it succeeds.

The FILTER_VALIDATE_INT filter will check to see if the variable represents an integer (e.g., "1", which is a string, but can be converted to an integer). If it does represent an integer, it will convert the value to an integer type and return it. If it is not an integer, it will return false, which, like NULL, conveniently converts to an empty string when treated as a string.

$f['age'] = filter_input(INPUT_POST, 'age', FILTER_VALIDATE_INT);

After running the code above, you can just treat $f['age'] as a boolean:

if (!$f['age']) {
  $errors[] = 'You must include a valid age.';
}

There is a gotcha here though! Remember that 0 is falsy, meaning that it converts to false when treated as a boolean. If 0 is an acceptable value for $age, the code above will not work correctly. Also, presumably negative ages would not be valid. As such, you should do something like this:

if (!is_int($i) || $i < 0) {
  $errors[] = 'You must include a valid age.';
}

is_int()

See https://www.php.net/is_int for details on the is_int() function.

Is it an Email?

We can use the filter_var() and filter_input() functions to check for a valid email just as we did to check for a valid integer above:

$f['email'] = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);

However, if you want to return one error if the email is missing and a different error if the email is present but invalid, you can take the following approach:

  1. Save the submitted email in the $f array:
    $f['email'] = trim($_POST['email'] ?? '');
  2. Check if the email is falsy, most likely meaning that nothing was entered in the form field, and then check if it the email is valid:
    if (!$f['email']) {
      $errors[] = 'Email is required.';
    } elseif (!filter_var($f['email'], FILTER_VALIDATE_EMAIL)) {
      $errors[] = 'Email is not valid.';
    }

Is it a Valid Password and Do the Passwords Match?

You should not trim or filter passwords. You should use exactly what the user entered. And, for security reasons, you should never return the password to the browser.

Websites use all sorts of different algorithms for checking passwords, but for starters, you want to make sure the password is a minimum length and that the two passwords entered when registering for a site are equal. The following check will do the trick:

if (!$_POST['password-1']) {
  $errors[] = 'Password is required.';
} elseif (strlen($_POST['password-1']) < 8) {
  $errors[] = 'Password must be at least 8 characters.';
} elseif ($_POST['password-1'] !== $_POST['password-2']) {
  $errors[] = 'Passwords do not match.';
}

Do the Combined Values Create a Valid Date?

Often forms use separate fields for month, day, and year values. In this case, each should be an integer and should be checked separately using the technique we described above. After checking that they are all integers, you can also use PHP's checkdate() function to see if they combine to make a valid date:

if (!is_int($f['birth-day'])
    || !is_int($f['birth-month'])
    || !is_int($f['birth-year'])) {
  $errors[] = 'A full birth date is required.';
} elseif ( !checkdate($f['birth-month'],
                      $f['birth-day'],
                      $f['birth-year']) ) {
  $errors[] = 'The birth date is not valid.';
}

Did the User Check the Box?

The name-value pair associated with a checkbox only gets submitted if the checkbox is checked. So, to check if a user checked a checkbox, you just need to check whether the key exists is $_POST:

if (!isset($_POST['terms']) {
  errors[] = 'You must agree to our terms of use.';
}

Processing Form Input

Duration: 60 to 90 minutes.

Reviewing the Solution

We review the solution to this exercise over several videos. If you have trouble, you can rate the exercise to move on to the next presentation, watch the video explanation, and then come back to the exercise to continue your work.

In this exercise, you will create a page that sanitizes and validates form data. The form entry code is already complete:

Code Sample:

Forms/Exercises/add-employee.php
<?php
  ini_set('display_errors', '1');

  // Used to populate birth-month and hire-month fields
  $months = [
    'January', 'February', 'March', 'April', 'May', 'June', 'July',
    'August', 'September', 'October', 'November', 'December'
  ];

  // Used to populate courtesy-title field
  $courtesyTitles = [ 'Dr.', 'Mr.', 'Mrs.', 'Ms.' ];
?>
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="../../static/styles/normalize.css">
<link rel="stylesheet" href="../../static/styles/styles.css">
<title>Add Employee</title>
</head>
<body>
<main>
  <h1>Add Employee</h1>
  <form method="post" action="add-employee.php" novalidate>
    <label for="first-name">First Name:</label>
    <input name="first-name" id="first-name">
    <label for="last-name">Last Name:</label>
    <input name="last-name" id="last-name">
    <label for="email">Email:</label>
    <input type="email" name="email" id="email">
    <fieldset>
      <legend>Password:</legend>
      <input type="password" placeholder="Password"
            name="password-1" id="password-1">
      <input type="password" placeholder="Repeat Password"
            name="password-2" id="password-2">
    </fieldset>
    <label for="title">Title:</label>
    <input name="title" id="title">
    <fieldset>
      <legend>Title of Courtesy:</legend>
      <?php
        foreach ($courtesyTitles as $title) {
          echo "<label>
          <input type='radio' name='courtesy-title' value='$title'>
          $title</label>";
        }
      ?>
    </fieldset>
    <fieldset>
      <legend>Birth date:</legend>
      <select name="birth-month" id="birth-month">
        <option value="0">--Select Month--</option>
        <?php
          for ($i=1; $i<=12; $i++) {
            echo "<option value='$i'>" . $months[$i-1] . "</option>";
          }
        ?>
      </select>
      <input name="birth-day" type="number" min="1" max="31"
        placeholder="day">
      <input name="birth-year" type="number"
        placeholder="year">
    </fieldset>
    <fieldset>
      <legend>Hire date:</legend>
      <select name="hire-month">
        <option value="0">--Select Month--</option>
        <?php
          for ($i=1; $i<=12; $i++) {
            echo "<option value='$i'>" . $months[$i-1] . "</option>";
          }
        ?>
      </select>
      <input name="hire-day" type="number" min="1" max="31"
        placeholder="day">
      <input name="hire-year" type="number"
        placeholder="year">
    </fieldset>
    <label for="cell-phone">Cell Phone:</label>
    <input type="tel" name="cell-phone" id="cell-phone">
    <label for="notes">Notes:</label>
    <textarea name="notes" id="notes"></textarea>
    <button name="add-employee" class="wide">Add Employee</button>
  </form>
</main>
</body>
</html>

Code Explanation

The code outputs an HTML form for adding a new employee. Its action page is add-employee.php, which means the page will submit to itself. The filled out form looks like this:Add Employee Form - Filled 1 Add Employee Form - Filled 2

Visit http://localhost:8888/Webucator/php/Forms/Exercises/add-employee.php to see the page.

If everything is filled out correctly, the page should display as follows:Process Filled Employee Form

If all fields are left blank, errors should show up at the top of the page: Process Empty Employee Form

  1. Open Forms/Exercises/add-employee.php in your editor.
  2. Below the declaration of $courtesyTitles, declare $f and assign it an empty array. This will hold our cleaned-up form variables.
  3. Trim and assign all the text-like form entries to values within the $f array. Use the null coalescing operator to assign an empty string if the form variable doesn't exist. For example:
    $f['first-name'] = trim($_POST['first-name'] ?? '');
  4. Use filter_input() to validate that all the date parts represent integers and to convert them from strings to integers. For example:
    $f['birth-day'] = filter_input(INPUT_POST, 'birth-day',
    FILTER_VALIDATE_INT);
  5. If the form has been submitted, add-employee (the name of the submit button) will be present in the $_POST array. Write an if condition to check for this. Within this if block, you'll do your form validation.
    1. Declare $errors and assign it an empty array and populate $errors with error messages:
    2. If the email isn't filled out, add: 'Email is required.'
    3. If the email isn't valid, add: 'Email is not valid.'
    4. If the password isn't filled out, add: 'Password is required.'
    5. If the password is shorter than 8 characters, add: 'Password must be at least 8 characters.'
    6. If the passwords do not match, add: 'Passwords do not match.'
    7. For each of the following fields that is not filled out, add 'XYZ is required.' For example, 'First name is required.'
      1. First name
      2. Last name
      3. Title
      4. Title of courtesy
      5. Cell phone
    8. Add code to check that the birth date and hire date fields are all filled out and create valid dates.
    9. If the notes field is longer than 100 characters, add 'Notes cannot be longer than 100 characters.'
  6. Below the "Add Employee" h1 element, create a PHP block.
    1. If there are any errors, output <h3>Please correct the following errors:</h3> followed by an ordered list of error messages:Add Employee Error Messages
    2. If there are no errors and the form has been submitted, output <h3>Form Data</h3> followed by an ordered list of the data:Add Employee Form DataWe would normally insert the data into a database, but for our purposes right now displaying the data in the browser is enough.
    3. If there are no errors and the form has been submitted, output the form. You should end your PHP block with the if statement and then add another PHP block after the form to close the if statement:
      <?php
        }
      ?>
  7. Add code to all the text-like fields so that they "remember" values submitted through the form. For example:
    <input name="first-name" id="first-name" value="<?= $f['first-name'] ?>">
  8. Add code to the for loops for the select menus (using the selected attribute) and radio buttons (using the checked attribute) to "remember" values submitted through the form.
  9. Navigate to http://localhost:8888/Webucator/php/Forms/Exercises/add-employee.php in the browser to test your solution:
    1. Submit it without entering any data.
    2. Submit it with invalid data (e.g., an invalid email address, passwords that are too short or don't match, a date that isn't real like February 31).
    3. Submit it with valid data.

Solution:

Forms/Solutions/add-employee.php
<?php
  ini_set('display_errors', '1');

  // Used to populate birth-month and hire-month fields
  $months = [
    'January', 'February', 'March', 'April', 'May', 'June', 'July',
    'August', 'September', 'October', 'November', 'December'
  ];

  // Used to populate courtesy-title field
  $courtesyTitles = [ 'Dr.', 'Mr.', 'Mrs.', 'Ms.' ];

  $f = [];

  // Trim and Assign Form Entries
  $f['first-name'] = trim($_POST['first-name'] ?? '');
  $f['last-name'] = trim($_POST['last-name'] ?? '');
  $f['email'] = trim($_POST['email'] ?? '');
  $f['title'] = trim($_POST['title'] ?? '');
  $f['courtesy-title'] = trim($_POST['courtesy-title'] ?? '');
  $f['birth-month'] = filter_input(INPUT_POST, 'birth-month',
    FILTER_VALIDATE_INT);
  $f['birth-day'] = filter_input(INPUT_POST, 'birth-day',
    FILTER_VALIDATE_INT);
  $f['birth-year'] = filter_input(INPUT_POST, 'birth-year',
    FILTER_VALIDATE_INT);
  $f['hire-month'] = filter_input(INPUT_POST, 'hire-month',
    FILTER_VALIDATE_INT);
  $f['hire-day'] = filter_input(INPUT_POST, 'hire-day',
    FILTER_VALIDATE_INT);
  $f['hire-year'] = filter_input(INPUT_POST, 'hire-year',
    FILTER_VALIDATE_INT);
  $f['cell-phone'] = trim($_POST['cell-phone'] ?? '');
  $f['notes'] = trim($_POST['notes'] ?? '');

  if (isset( $_POST['add-employee'])) {
    $errors = [];

    if (!$f['email']) {
      $errors[] = 'Email is required.';
    } elseif (!filter_var($f['email'], FILTER_VALIDATE_EMAIL)) {
      $errors[] = 'Email is not valid.';
    }
  
    if (!$_POST['password-1']) {
      $errors[] = 'Password is required.';
    } elseif (strlen($_POST['password-1']) < 8) {
      $errors[] = 'Password must be at least 8 characters.';
    } elseif ($_POST['password-1'] !== $_POST['password-2']) {
      $errors[] = 'Passwords do not match.';
    }

    if (!$f['first-name']) {
      $errors[] = 'First name is required.';
    }

    if (!$f['last-name']) {
      $errors[] = 'Last name is required.';
    }

    if (!$f['title']) {
      $errors[] = 'Title is required.';
    }

    if (!$f['courtesy-title']) {
      $errors[] = 'Title of Courtesy is required.';
    }

    if (!$f['birth-day'] || !$f['birth-month'] || !$f['birth-year']) {
      $errors[] = 'A full birth date is required.';
    } elseif ( !checkdate($f['birth-month'],
                          $f['birth-day'],
                          $f['birth-year']) ) {
      $errors[] = 'The birth date must be a valid date.';
    }

    if (!$f['hire-day'] || !$f['hire-month'] || !$f['hire-year']) {
      $errors[] = 'A full hire date is required.';
    } elseif ( !checkdate($f['hire-month'],
                          $f['hire-day'],
                          $f['hire-year']) ) {
      $errors[] = 'The hire date must be a valid date.';
    }

    if (!$f['cell-phone']) {
      $errors[] = 'Cell phone is required.';
    }
  
    if (strlen($f['notes']) > 100) {
      $errors[] = 'Notes cannot be longer than 100 characters.';
    }
  }
?>
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="../../static/styles/normalize.css">
<link rel="stylesheet" href="../../static/styles/styles.css">
<title>Add Employee</title>
</head>
<body>
<main>
  <h1>Add Employee</h1>
  <?php
    if (!empty($errors)) {
      // Show form errors
      echo '<h3>Please correct the following errors:</h3>
      <ol class="error">';
      foreach ($errors as $error) {
        echo "<li>$error</li>";
      }
      echo '</ol>';
    } elseif (isset( $_POST['add-employee'])) {
      // We'd normally insert the data into a database here,
      // but we will just show the form data.
      echo '<h3>Form Data</h3>';
      echo '<ol>';
      echo '<li><strong>Name:</strong> ' . $f['courtesy-title'] .
            ' ' . $f['first-name'] . ' ' . $f['last-name'] . '</li>';
      echo '<li><strong>Title:</strong> ' . $f['title']. '</li>';
      echo '<li><strong>Cell phone:</strong> ' . 
            $f['cell-phone'] . '</li>';
      echo '<li><strong>Notes:</strong> ' . $f['notes']. '</li>';
      
      $birthDate = mktime(0, 0, 0, $f['birth-month'],
                          $f['birth-day'], $f['birth-year']);
      $sBirthDate = date("F j, Y", $birthDate); 
      echo "<li><strong>Born:</strong> $sBirthDate.</li>";

      $hireDate = mktime(0, 0, 0, $f['hire-month'], 
                        $f['hire-day'], $f['hire-year']);
      $sHireDate = date("F j, Y", $hireDate); 
      echo "<li><strong>Start Date:</strong> $sHireDate.</li>";
      echo '</ol>';
    }

    if (!empty($errors) || !isset( $_POST['add-employee'])) {
      // Show form
  ?>
  <form method="post" action="add-employee.php" novalidate>
    <label for="first-name">First Name:</label>
    <input name="first-name" id="first-name" 
      value="<?= $f['first-name'] ?>">
    <label for="last-name">Last Name:</label>
    <input name="last-name" id="last-name" 
      value="<?= $f['last-name'] ?>">
    <label for="email">Email:</label>
    <input type="email" name="email" id="email" 
      value="<?= $f['email'] ?>">
    <fieldset>
      <legend>Password:</legend>
      <input type="password" placeholder="Password"
            name="password-1" id="password-1">
      <input type="password" placeholder="Repeat Password"
            name="password-2" id="password-2">
    </fieldset>
    <label for="title">Title:</label>
    <input name="title" id="title" value="<?= $f['title'] ?>">
    <fieldset>
      <legend>Title of Courtesy:</legend>
      <?php
        foreach ($courtesyTitles as $cTitle) {
          echo "<label>
          <input type='radio' name='courtesy-title' value='$cTitle'";
          if ($cTitle === $f['courtesy-title']) {
            echo ' checked';
          }
          echo ">$cTitle</label>";
        }
      ?>
    </fieldset>
    <fieldset>
      <legend>Birth date:</legend>
      <select name="birth-month" id="birth-month">
        <option value="0">--Select Month--</option>
        <?php
          for ($i=1; $i<=12; $i++) {
            echo "<option value='$i'";
            if ( $f['birth-month'] === $i) {
              echo " selected";
            }
            echo ">" . $months[$i-1] . "</option>";
          }
        ?>
      </select>
      <input name="birth-day" type="number" min="1" max="31"
        placeholder="day" value="<?= $f['birth-day'] ?>">
      <input name="birth-year" type="number"
        placeholder="year" value="<?= $f['birth-year'] ?>">
    </fieldset>
    <fieldset>
      <legend>Hire date:</legend>
      <select name="hire-month">
        <option value="0">--Select Month--</option>
        <?php
          for ($i=1; $i<=12; $i++) {
            echo "<option value='$i'";
            if ( $f['hire-month'] === $i) {
              echo " selected";
            }
            echo ">" . $months[$i-1] . "</option>";
          }
        ?>
      </select>
      <input name="hire-day" type="number" min="1" max="31"
        placeholder="day" value="<?= $f['hire-day'] ?>">
      <input name="hire-year" type="number"
        placeholder="year" value="<?= $f['hire-year'] ?>">
    </fieldset>
    <label for="cell-phone">Cell Phone:</label>
    <input type="tel" name="cell-phone" id="cell-phone"
        value="<?= $f['cell-phone'] ?>">
    <label for="notes">Notes:</label>
    <textarea name="notes" id="notes"><?= $f['notes'] ?></textarea>
    <button name="add-employee" class="wide">Add Employee</button>
  </form>
  <?php
    }
  ?>
</main>
</body>
</html>

Code Explanation

There is a significant amount of code here. If you're having trouble understanding any of it, go back through the exercise instructions slowly and methodically, matching each instruction with the relevant code in the solution. Then watch the video that follows.