User Registration

First Steps (Part 1)

Getting the User In

This lesson requires the completion of Project Setup
  • Our first step is to register users

    • Utilizing previous lessons we’ll build a simple registration form

  • Switch to qa and ensure it’s up to date (git pull origin qa):

    • git checkout -b Milestone1

    • Push to GitHub: git push origin Milestone1

    • In the future, if you want to test Milestone1 as a whole, you can manually deploy it to Render.com

  • Next, we’ll make a feature branch

    • git checkout -b Feat-MS1-UserRegistration

Remember, we’ll be doing most of Milestone1 together, but there will be some items left for you to solve/add on your own.

Unrelated homework branches should come from qa, not Milestone1.

Example Part 1

<h3>Register</h3>
<form onsubmit="return validate(this)" method="POST">
    <div>
        <label for="email">Email</label>
        <input id="email" type="email" name="email" required />
    </div>
    <div>
        <label for="pw">Password</label>
        <input type="password" id="pw" name="password" required minlength="8" />
    </div>
    <div>
        <label for="confirm">Confirm</label>
        <input type="password" name="confirm" required minlength="8" />
    </div>
    <input type="submit" value="Register" />
</form>
<script>
    function validate(form) {
        //TODO 1: implement JavaScript validation (you'll do this on your own towards the end of Milestone1)
        //ensure it returns false for an error and true for success

        return true;
    }
</script>
<?php
 //TODO 2: add PHP Code
?>
  • Create a file called register.php in your project folder

  • Add the above code to register.php

Receiving Form Data

  • We’ll need to check when $_POST has been populated

    • Use if(count($_POST) > 0) or if(isset($_POST["someKey"]))

      • More precise: use isset() for specific keys

    • Avoid using only $_SERVER["REQUEST_METHOD"] for GET/POST detection

Regarding $_SERVER["REQUEST_METHOD"], "REQUEST_METHOD"` is the key; you don’t replace it with GET/POST

Getting Submission Data

  • Edit "TODO 2" in register.php

  • Typically use isset() to ensure certain properties are present

    • count() would be a more lazy use and wouldn’t guarantee the desired properties exist

    • If you attempt to use a key for a property that doesn’t exist an error will occur

  • Alternatively, we can use other array functions like array_key_exists(), but isset() is more common

<?php
// TODO 2: add PHP Code

if (isset($_POST["email"], $_POST["password"], $_POST["confirm"])) {

    $email = $_POST["email"];
    $password = $_POST["password"];
    $confirm = $_POST["confirm"];
    // TODO 3: validate/use
}
?>
isset() can either be chained using && or passed as multiple parameters, as shown above. This is a common practice to ensure all required fields are present before proceeding.

Setting up functions.php

  • We’ll create a functions.php file to hold our utility functions

  • This file will bootstrap our project and provide reusable functions

  • Inside the lib folder of your repository, create the following files:

    • functions.php - for aggregating functions

    • safer_echo.php - for safely echoing values

  • We’ll go through each in the next steps

functions.php Overview

  • The functions.php file will contain utility functions that can be reused across different parts of the project

  • Add the following code to functions.php:

<?php
//TODO 1: require db.php

//require safer_echo.php
require(__DIR__ . "/safer_echo.php");
//TODO 2: filter helpers

//TODO 3: User helpers

//TODO 4: Flash Message Helpers
?>
  • This file will require() the individual functions or function groups we’ll create

  • Don’t define functions directly in this file, instead write them in separate files and include (require()) them here

This is one of the design decisions we’ll make to keep our code organized and maintainable. It allows us to easily add or modify functions without cluttering the main file.

safer_echo.php Overview

  • The safer_echo.php file will contain a function to safely echo values from arrays, objects, or other data structures (helps prevent errors when accessing keys that may not exist)

  • Add the following code to safer_echo.php:

<?php
/**
 * Safe Echo Function
 * - If given an array/object and a key, returns the value at that key (or $default if not set).
 * - If given a scalar, returns it directly (or $default if not set).
 * - By default, passes the result through htmlspecialchars() for XSS safety.
 * - If $isEcho is true, echoes the value; otherwise, returns it.
 * - If $raw is true, skips htmlspecialchars() (WARNING: this can expose your app to XSS if used with untrusted data).
 *
 * @param mixed $v Value, array, or object
 * @param mixed $k Key (optional)
 * @param mixed $default Default value if key not found
 * @param bool $isEcho Whether to echo (true) or return (false)
 * @param bool $raw If true, do NOT escape output (default: false)
 * @return mixed|null
 */
function se($v, $k = null, $default = "", $isEcho = true, $raw = false) {
    if (is_array($v) && !is_null($k) && array_key_exists($k, $v)) {
        $returnValue = $v[$k];
    } else if (is_object($v) && !is_null($k) && isset($v->$k)) {
        $returnValue = $v->$k;
    } else {
        $returnValue = $v;
        if (is_array($returnValue) || is_object($returnValue)) {
            $returnValue = $default;
        }
    }
    if (!isset($returnValue)) {
        $returnValue = $default;
    }
    $safeValue = $raw ? $returnValue : htmlspecialchars($returnValue, ENT_QUOTES);
    if ($isEcho) {
        echo $safeValue;
    } else {
        return $safeValue;
    }
}

function safer_echo($v, $k = null, $default = "", $isEcho = true, $raw = false){
    return se($v, $k, $default, $isEcho, $raw);
}
  • se()/safer_echo() safely outputs values from arrays, objects, or scalars

  • Prevents errors when keys are missing; uses a default value if not found

  • Escapes output with htmlspecialchars() to protect against XSS (unless $raw is true)

  • Can echo or return the value based on $isEcho

  • Use for all user-facing output to ensure safety and consistency

Update register.php

  • At the top include/require functions.php

    • Note: When a file path is incorrect

      • include() throws a warning but proceeds

      • require() throws an error and stops

<?php
require(__DIR__."/../../lib/functions.php");
?>
The _DIR_ (double underscores on each side) constant provides the directory of the current file, ensuring the path is correct regardless of where the script is executed from
  • Under TODO 2, update the assignment of the variables to use se() instead of direct access to $_POST

<?php
// TODO 2: add PHP Code
 if (isset($_POST["email"], $_POST["password"], $_POST["confirm"])) {

    $email = se($_POST, "email", "", false);
    $password = se($_POST, "password", "", false);
    $confirm = se($_POST, "confirm", "", false);
    // TODO 3: validate/use
}
?>

Server-side Validation

  • Update TODO 3 in register.php to validate user inputs

<?php
$hasError = false;

if (empty($email)) {
    echo "Email must not be empty<br>";
    $hasError = true;
}

if (empty($password)) {
    echo "Password must not be empty<br>";
    $hasError = true;
}

if (empty($confirm)) {
    echo "Confirm password must not be empty<br>";
    $hasError = true;
}

if (strlen($password) < 8) {
    echo "Password too short<br>";
    $hasError = true;
}

if ($password !== $confirm) {
    echo "Passwords must match<br>";
    $hasError = true;
}

if (!$hasError) {
    echo "Success<br>";
}
?>
  • Uses a $hasError variable to track validation status

  • Validates that email, password, and confirm fields are filled

  • Checks if password and confirm match

  • Each presents a user-friendly message

  • If validation succeeds, it echoes a success message

Always validate all user inputs at once and display all validation errors together, rather than stopping at the first error. This approach provides a better user experience by allowing users to correct all issues in a single submission.

To test server-side checks, remove client-side validation temporarily. This includes HTML properties like required, min, max, maxlength, minlength, and types.
Shortcut: Apply noValidate to the form tag, which disables all HTML5 validation. This can be done via dev tools temporarily.

Advanced Validation: PHP Filters

// Sanitize and validate email
$email = filter_var($email, FILTER_SANITIZE_EMAIL);
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
    echo "Invalid email address<br>";
    $hasError = true;
}

Summary

  • We’re not done yet, but that completes the first part

  • At this point we have a basic registration form with simple client-side and server-side validation

  • We learned about filters (filter_var()) for better server-side validation

    • Remember: Server-side validation is more important than client-side validation

    • Client-side validation is for a better UX and to aid in offloading invalid requests

  • It’s a good idea to add/commit to the Feat-MS1-UserRegistration branch at this point

    • git add .

    • git commit -m "Registration form with basic validation"

    • It’s up to you if you want to push this branch to GitHub now or in the next lesson

  • Checkpoint: https://github.com/MattToegel/IT202-2025/tree/Module04-Registration-Part1

    • Note: My branch name differs from yours so I can isolate the lesson content