Week 2: Arrays, Strings, Forms & Superglobals

Week 1 ended with one hardcoded page proving PHP's setup works end to end. This week turns that into something that actually reacts to a user — PHP's array toolkit, string handling, and the superglobals that carry form data, query strings and server information into your script.

Module 2 of 28 Week 2 of 32 ~3–4 Hours Hands-on Exercise Included

By the end of this week, you'll be able to

  • Work fluently with indexed, associative & multidimensional arrays
  • Format and sanitize strings with PHP's built-in string functions
  • Read $_GET, $_POST & $_SERVER, and handle a real HTML form

1. Arrays: Indexed, Associative & Multidimensional

PHP has one array type that covers what other languages split into lists, dictionaries and tuples. An indexed array uses sequential integer keys; an associative array uses string keys you choose:

arrays.php
<?php
declare(strict_types=1);

// Indexed
$colors = ["red", "green", "blue"];
echo $colors[1]; // "green"

// Associative
$product = [
    "name" => "Mechanical Keyboard",
    "price" => 89.99,
    "inStock" => true,
];
echo $product["name"]; // "Mechanical Keyboard"

// Multidimensional -- an array of associative arrays
$cart = [
    ["name" => "Keyboard", "qty" => 1],
    ["name" => "Mouse", "qty" => 2],
];
echo $cart[1]["name"]; // "Mouse"

Under the hood, PHP arrays are ordered maps — even a "list" with integer keys is really a map from 0, 1, 2, ... to values, which is why associative and indexed arrays are the same underlying type. Iterate any array with foreach:

iterate.php
<?php
foreach ($cart as $index => $item) {
    echo "{$index}: {$item['name']} x{$item['qty']}\n";
}

// Common array functions
$names = array_column($cart, 'name');           // ["Keyboard", "Mouse"]
$totalQty = array_sum(array_column($cart, 'qty')); // 3
$inStock = array_filter([$product], fn($p) => $p['inStock']);
$prices = array_map(fn($p) => $p['price'] * 1.18, [$product]); // with tax
array_map, array_filter & array_reduce

These three cover most transformations you'll reach for: array_map transforms every element, array_filter keeps only elements matching a predicate, and array_reduce folds the array down to a single value. Reaching for these instead of hand-rolled loops keeps intent obvious at a glance.

2. String Functions & Formatting

PHP's string library is large and mostly flat functions rather than methods — strtoupper($s), not $s.toUpperCase(). The ones you'll use constantly:

strings.php
<?php
$title = "  Mechanical Keyboard  ";

trim($title);                          // "Mechanical Keyboard"
strtolower($title);                    // "  mechanical keyboard  "
str_contains($title, "Keyboard");      // true
str_replace("Keyboard", "Mouse", $title); // "  Mechanical Mouse  "
substr($title, 2, 10);                 // "Mechanical"
strlen(trim($title));                  // 19
explode(" ", trim($title));            // ["Mechanical", "Keyboard"]
implode("-", ["a", "b", "c"]);         // "a-b-c"

// Interpolation -- variables inside double-quoted strings are expanded;
// braces are required for array/property access inside the string
$qty = 3;
echo "You have {$qty} items in your cart.";

// sprintf for precise formatting
sprintf("Total: $%.2f", 89.99 * 1.18); // "Total: $106.19"

Single-quoted strings 'like this' never interpolate variables or escape sequences (except \' and \\) — use them for literal text and double-quoted strings when you need interpolation, so the distinction itself signals intent.

3. Superglobals

PHP automatically populates several arrays with request data — they're called superglobals because they're available in every scope without needing to be passed in or declared global:

  • $_GET — query string parameters (?id=42)
  • $_POST — form body data from a POST request
  • $_SERVER — request metadata: method, URI, headers, IP
  • $_SESSION — per-visitor data persisted across requests (Week 5)
  • $_FILES — uploaded file metadata (Week 5)
superglobals.php
<?php declare(strict_types=1);

// GET /search.php?q=keyboard&page=2
$query = $_GET['q'] ?? '';
$page = (int) ($_GET['page'] ?? 1);

// Request metadata
$method = $_SERVER['REQUEST_METHOD']; // "GET"
$path = $_SERVER['REQUEST_URI'];      // "/search.php?q=keyboard&page=2"
$ip = $_SERVER['REMOTE_ADDR'];

The ?? null coalescing operator is the idiomatic way to read superglobal keys that might not be set — $_GET['q'] ?? '' reads as "the q parameter, or an empty string if it's missing," avoiding an "undefined array key" warning.

4. Building an HTML Form Handler

A form's method attribute determines which superglobal receives its data. Here's a complete contact form and the script that handles it — both in one file, branching on the request method:

contact.php
<?php
declare(strict_types=1);

$errors = [];
$submitted = false;

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $name = trim($_POST['name'] ?? '');
    $email = trim($_POST['email'] ?? '');
    $message = trim($_POST['message'] ?? '');

    if ($name === '') {
        $errors[] = 'Name is required.';
    }
    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        $errors[] = 'A valid email address is required.';
    }
    if (strlen($message) < 10) {
        $errors[] = 'Message must be at least 10 characters.';
    }

    if (empty($errors)) {
        $submitted = true;
        // In Week 7 this becomes an INSERT into MySQL instead.
    }
}
?>
<!DOCTYPE html>
<html>
<body>
  <?php if ($submitted): ?>
    <p>Thanks, <?= htmlspecialchars($name) ?>! We'll be in touch.</p>
  <?php else: ?>
    <?php foreach ($errors as $error): ?>
      <p class="error"><?= htmlspecialchars($error) ?></p>
    <?php endforeach; ?>
    <form method="post" action="contact.php">
      <input name="name" placeholder="Name" value="<?= htmlspecialchars($_POST['name'] ?? '') ?>">
      <input name="email" placeholder="Email" value="<?= htmlspecialchars($_POST['email'] ?? '') ?>">
      <textarea name="message"><?= htmlspecialchars($_POST['message'] ?? '') ?></textarea>
      <button type="submit">Send</button>
    </form>
  <?php endif; ?>
</body>
</html>

Notice the form re-fills each field's value from $_POST on a failed submission — a small detail that makes validation errors far less annoying for the person filling out the form.

5. Sanitizing & Validating Input

PHP's filter_var() function validates and sanitizes common data shapes without writing manual regex for every case:

filter-var.php
<?php
filter_var("ada@example.com", FILTER_VALIDATE_EMAIL);  // "ada@example.com"
filter_var("not-an-email", FILTER_VALIDATE_EMAIL);     // false
filter_var("42", FILTER_VALIDATE_INT);                 // 42
filter_var("https://example.com", FILTER_VALIDATE_URL); // the URL, or false
Validation ≠ sanitization ≠ escaping

Validating rejects bad input. Sanitizing strips or transforms it into a safer shape. Escaping (like htmlspecialchars()) makes it safe for a specific output context — HTML here, but SQL and shell commands each need their own escaping mechanism, covered in Weeks 7 and 8. All three matter, and none substitutes for the others.

6. Hands-on Exercise

Hands-on

Build a validated newsletter signup form

Apply this week's arrays, strings and superglobals to a complete form-handling script.

Requirements:

  1. Create signup.php with a form (name, email, and a <select> for interest with at least 3 options) that POSTs to itself.
  2. On submit, validate: name non-empty, email passes FILTER_VALIDATE_EMAIL, interest is one of the allowed options (check with in_array).
  3. Collect errors into an indexed array and display each one if validation fails, re-filling the form's previous values.
  4. On success, append the validated data as an associative array to a $signups array (in-memory is fine — persistence arrives in Week 7), and display a confirmation listing every field back using htmlspecialchars().
  5. Add a summary line below the form using sprintf() that reads like "3 people have signed up so far."
Hint

Since PHP re-runs the script from scratch on every request, an in-memory $signups array won't persist between submissions yet — that's expected. Focus on getting the validation, re-population and array handling right; real persistence is Week 7's job.

7. Knowledge Check

Four quick questions. Expand each to check your answer.

Q1

What's the actual difference between an "indexed" and an "associative" PHP array?

Nothing at the type level — PHP has a single array type, which is really an ordered map from keys to values. An "indexed" array is simply one whose keys happen to be sequential integers starting at 0; an "associative" array uses string (or non-sequential integer) keys you choose. Both are the exact same underlying structure.

Q2

Why use $_GET['q'] ?? '' instead of just $_GET['q']?

If q wasn't in the query string, $_GET['q'] raises an "undefined array key" warning and evaluates to null. The ?? null coalescing operator returns the right-hand fallback whenever the left side is unset or null, so $_GET['q'] ?? '' safely reads "the value, or an empty string" in one expression with no warning.

Q3

Does a form with method="get" populate $_POST?

No — a GET form appends its fields to the URL's query string, which lands in $_GET, not $_POST. Only a form with method="post" sends its fields in the request body, which PHP parses into $_POST. Checking $_SERVER['REQUEST_METHOD'] is how the handler script tells which case it's in.

Q4

If filter_var($email, FILTER_VALIDATE_EMAIL) confirms an address is well-formed, is it now safe to print directly into HTML?

No — validation and output escaping are separate concerns. Email validation only confirms the string is shaped like an address; it doesn't guarantee the value is free of characters that are dangerous in an HTML context. Always pass user-controlled values through htmlspecialchars() (or the right escaping function for the output context) regardless of what validation already ran.