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
PDOprepared 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
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
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
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
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
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
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;
}