Chapter 12

Building Safer, Better PHP Programs

Programs fail, and users are not always friendly. Handle failure deliberately, and protect each boundary with the control that fits it.

Chapter 12, Building Safer, Better PHP Programs

What you will learn

  • Use try/catch and throw for expected exceptional conditions.
  • Split reusable code across files with require_once.
  • Hash passwords with PHP’s password API rather than inventing cryptography.
  • Use PDO prepared statements for database values and keep SQL safety separate from HTML escaping.

The code from this chapter

Type these programs yourself. The predictions below are the exercises; the explanations are in the book.

12.1 Exceptions and Defensive Programming

Type this into a file, predict the output, then run it.

PHP
<?php

declare(strict_types=1);

try {
    echo "Before" . PHP_EOL;
    throw new InvalidArgumentException("Percentage must be between 0 and 100.");
    echo "After" . PHP_EOL;
} catch (InvalidArgumentException $error) {
    echo "Rejected: " . $error->getMessage() . PHP_EOL;
}

12.2 Organizing Code Across Files

Type this into a file, predict the output, then run it.

PHP
<?php

declare(strict_types=1);

function kmToMiles(float $km): float
{
    return $km * 0.621371;
}

Type this one as well, and predict it before you run it.

PHP
<?php

declare(strict_types=1);

require_once __DIR__ . "/conversions.php";

echo kmToMiles(10) . PHP_EOL;

12.3 Security Is Context-Specific

Type this into a file, predict the output, then run it.

PHP
<?php

declare(strict_types=1);

$hash = password_hash("correct horse", PASSWORD_DEFAULT);

var_dump(password_verify("correct horse", $hash));
var_dump(password_verify("wrong guess", $hash));

TPRM Lab 12.1: Throw and Catch a Domain Error

Type this into a file, predict the output, then run it.

PHP
<?php

declare(strict_types=1);

function withdraw(float $balance, float $amount): float
{
    if ($amount < 0) {
        throw new InvalidArgumentException("Amount cannot be negative.");
    }

    if ($amount > $balance) {
        throw new RuntimeException("Insufficient funds.");
    }

    return $balance - $amount;
}

try {
    echo withdraw(100, 30) . PHP_EOL;
} catch (InvalidArgumentException | RuntimeException $error) {
    echo "Transaction failed: " . $error->getMessage() . PHP_EOL;
}

TPRM Lab 12.2: Use a PDO Prepared Statement

Type this into a file, predict the output, then run it.

PHP
<?php

declare(strict_types=1);

$pdo = new PDO("sqlite:" . __DIR__ . "/app.db");
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

$pdo->exec(
    "CREATE TABLE IF NOT EXISTS tasks (
        id INTEGER PRIMARY KEY,
        title TEXT NOT NULL,
        done INTEGER NOT NULL DEFAULT 0
    )"
);

$title = "Practice prepared statements";

$stmt = $pdo->prepare(
    "INSERT INTO tasks (title, done) VALUES (:title, :done)"
);

$stmt->execute([
    ":title" => $title,
    ":done" => 0
]);

echo "Inserted id: " . $pdo->lastInsertId() . PHP_EOL;

$rows = $pdo->query("SELECT id, title FROM tasks")->fetchAll(PDO::FETCH_ASSOC);

foreach ($rows as $row) {
    echo $row["id"] . ": " . $row["title"] . PHP_EOL;
}

Back to PHP the TPRM Way