PHP Exercises
Practise what you've learned. Type the missing piece into each highlighted gap and press Check. Further down are programming challenges with editors and model solutions — copy them into a file and run them with php.
Fill in the blanks
Basics
The tag that starts PHP code.
echo "Hello";
(lesson 03)
Output text.
"Hello, World!\n";
print also works (lesson 03).
Every variable name starts with this character.
total = 42;
(lesson 06)
Join two strings.
$full = $first " " . $last;
The dot is PHP's concatenation operator (lesson 03).
Show a value's type and contents for debugging.
($value);
(lesson 03)
Use a default when a value is null or missing.
$name = $_GET['name'] 'guest';
The null-coalescing operator (lesson 10).
Compare value and type.
if ($input '0') { /* ... */ }
Strict comparison (lesson 09).
Integer division that discards the remainder.
$boxes = ($items, 12);
(lesson 08)
Define a constant at the top level of a file.
VAT_RATE = 0.2;
(lesson 11)
Control flow and strings
A match arm for any other value.
$label = match ($code) { 200 => 'OK', => 'Other' };
(lesson 13)
Loop over keys and values.
foreach ($prices as $item $price) { /* ... */ }
(lesson 15)
Leave two nested loops at once.
;
(lesson 15)
Check whether a string contains another (PHP 8).
if (($email, '@')) { /* ... */ }
(lesson 17)
Split a string into an array.
$parts = (',', $csvLine);
implode does the reverse (lesson 17).
Format a number with two decimals.
printf("Total: \n", $total);
(lesson 18)
Count characters, not bytes, in UTF-8 text.
$length = ($name);
(lesson 19)
Test a string against a regular expression.
if (('/^\d{4}$/', $pin)) { /* ... */ }
(lesson 20)
Arrays and functions
Add elements to the end of an array with a function.
($items, 'notebook', 'pen');
$items[] = 'notebook'; appends one element (lesson 21).
The number of elements in an array.
$n = ($items);
(lesson 21)
Transform every element.
$doubled = (fn($n) => $n * 2, $numbers);
(lesson 23)
Keep only some elements.
$adults = ($people, fn($p) => $p['age'] >= 18);
(lesson 23)
Extract one field from every row.
$names = ($rows, 'name');
(lesson 23)
Sort with a custom comparison.
($products, fn($a, $b) => $a['price'] <=> $b['price']);
(lesson 24)
The operator that returns -1, 0 or 1.
return $a $b;
The spaceship operator (lesson 10).
Accept any number of arguments.
function total(float $amounts): float { return array_sum($amounts); }
A variadic parameter (lesson 28).
Make a parameter accept a string or null.
function greet(string $name): string { /* ... */ }
A nullable type (lesson 29).
A short function that captures outside variables automatically.
$withTax = ($p) => $p * (1 + $rate);
An arrow function (lesson 31).
Object-oriented PHP
Refer to the current object inside a method.
return ->name;
(lesson 33)
The constructor method's name.
public function (public string $name) {}
(lesson 34)
A property that can only be set once.
public float $price;
(lesson 41)
Readable by everyone, writable only inside the class (PHP 8.4).
public float $balance = 0;
Asymmetric visibility (lesson 35).
Inherit from a parent class.
class Car Vehicle { }
(lesson 37)
Promise to provide an interface's methods.
class Circle Shape { }
(lesson 38)
Include a trait's methods in a class.
HasTimestamps;
(lesson 39)
Turn a stored string into an enum case, or null if invalid.
$status = Status::($row['status']);
(lesson 40)
Web, data and errors
Escape a value before printing it into HTML.
<p><?= ($comment) ?></p>
(lesson 47)
Start (or resume) the session.
();
(lesson 48)
Redirect the browser.
('Location: /thanks.php');
exit;
(lesson 50)
Prepare a query with a placeholder.
$stmt = $pdo->('SELECT * FROM users WHERE email = ?');
(lesson 58)
Hash a password for storage.
$hash = ($password, PASSWORD_DEFAULT);
Check it later with password_verify (lesson 59).
Make json_decode throw on invalid JSON.
$data = json_decode($json, true, flags: );
(lesson 55)
Code that always runs after try/catch.
try { /* ... */ } catch (Exception $e) { /* ... */ } { fclose($handle); }
(lesson 44)
Produce values lazily from a function.
$line;
A generator (lesson 45).
Load all Composer-installed classes.
require __DIR__ . '/vendor/';
(lesson 60)
Programming challenges
1. Word frequencies
Count how often each word appears in a text, ignoring case and punctuation, and print the words with their counts — most frequent first, alphabetical for ties.
Show a model solution
<?php $text = "The cat and the hat. The bat and the cat!"; $words = str_word_count(strtolower($text), 1); $counts = array_count_values($words); uksort($counts, fn($a, $b) => [$counts[$b], $a] <=> [$counts[$a], $b]); foreach ($counts as $word => $n) { echo str_pad($word, 4), $n, "\n"; }
the 4 and 2 cat 2 bat 1 hat 1
2. A bank account class
Write a BankAccount class with an owner, a balance that's readable but can't be set from outside, deposit and withdraw methods that reject invalid amounts, and a transaction history.
Show a model solution
<?php declare(strict_types=1); final class BankAccount { public private(set) float $balance = 0; public private(set) array $history = []; public function __construct(public readonly string $owner) {} public function deposit(float $amount): void { if ($amount <= 0) { throw new InvalidArgumentException('Deposit must be positive'); } $this->balance += $amount; $this->history[] = +$amount; } public function withdraw(float $amount): void { if ($amount <= 0) { throw new InvalidArgumentException('Withdrawal must be positive'); } if ($amount > $this->balance) { throw new RuntimeException(sprintf('Insufficient funds: balance is %.2f', $this->balance)); } $this->balance -= $amount; $this->history[] = -$amount; } } $account = new BankAccount('Ana'); $account->deposit(100); $account->withdraw(30); try { $account->withdraw(500); } catch (RuntimeException $e) { echo $e->getMessage(), "\n"; } printf("%s: %.2f\n", $account->owner, $account->balance); echo implode(', ', $account->history), "\n";
Insufficient funds: balance is 70.00 Ana: 70.00 100, -30
3. Shapes with an interface
Define a Shape interface with area() and perimeter(), implement it for a circle, a rectangle and a right triangle, and print them sorted by area.
Show a model solution
<?php interface Shape { public function area(): float; public function perimeter(): float; } final readonly class Circle implements Shape { public function __construct(private float $radius) {} public function area(): float { return M_PI * $this->radius ** 2; } public function perimeter(): float { return 2 * M_PI * $this->radius; } } final readonly class Rectangle implements Shape { public function __construct(private float $width, private float $height) {} public function area(): float { return $this->width * $this->height; } public function perimeter(): float { return 2 * ($this->width + $this->height); } } final readonly class RightTriangle implements Shape { public function __construct(private float $a, private float $b) {} public function area(): float { return $this->a * $this->b / 2; } public function perimeter(): float { return $this->a + $this->b + hypot($this->a, $this->b); } } $shapes = [new Circle(1.5), new Rectangle(2, 5), new RightTriangle(3, 4)]; usort($shapes, fn(Shape $x, Shape $y) => $x->area() <=> $y->area()); foreach ($shapes as $s) { printf("%-14s area %6.2f perimeter %6.2f\n", $s::class, $s->area(), $s->perimeter()); }
RightTriangle area 6.00 perimeter 12.00 Circle area 7.07 perimeter 9.42 Rectangle area 10.00 perimeter 14.00
4. A safe product search
Create an in-memory SQLite table of products and write a search() function that finds products by name and maximum price with a prepared statement. Make sure an injection attempt finds nothing.
Show a model solution
<?php $pdo = new PDO('sqlite::memory:', options: [PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]); $pdo->exec('CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, price REAL)'); $insert = $pdo->prepare('INSERT INTO products (name, price) VALUES (?, ?)'); foreach ([['Desk lamp', 34.5], ['Standing desk', 399], ['Lamp shade', 12], ['Desk mat', 19.9]] as $row) { $insert->execute($row); } function search(PDO $pdo, string $term, float $maxPrice): array { $stmt = $pdo->prepare('SELECT name, price FROM products WHERE name LIKE ? AND price <= ? ORDER BY price'); $stmt->execute(['%' . $term . '%', $maxPrice]); return $stmt->fetchAll(); } foreach ([['desk', 50], ['lamp', 1000], ["x' OR '1'='1", 1000]] as [$term, $max]) { $found = array_map(fn($p) => "{$p['name']} ({$p['price']})", search($pdo, $term, $max)); echo "'$term' under $max: ", $found ? implode(', ', $found) : 'nothing', "\n"; }
'desk' under 50: Desk mat (19.9), Desk lamp (34.5) 'lamp' under 1000: Lamp shade (12), Desk lamp (34.5) 'x' OR '1'='1' under 1000: nothing
5. Grade report from a CSV file
Read a CSV of student scores, print each student's average and grade, the best student, and how many lines were skipped because they were invalid.
Show a model solution
Ana,82,91,77 Ben,65,abc,70 Cleo,95,88,99 Dev,54,61,58
<?php $students = []; $skipped = 0; $handle = fopen('grades.csv', 'r'); while (($row = fgetcsv($handle, escape: '')) !== false) { [$name, $scores] = [$row[0], array_slice($row, 1)]; $valid = array_filter($scores, fn($s) => filter_var($s, FILTER_VALIDATE_INT) !== false); if ($scores === [] || count($valid) !== count($scores)) { $skipped++; continue; } $students[$name] = array_sum($scores) / count($scores); } fclose($handle); foreach ($students as $name => $avg) { $grade = match (true) { $avg >= 90 => 'A', $avg >= 80 => 'B', $avg >= 65 => 'C', default => 'F' }; printf("%-5s %5.1f %s\n", $name, $avg, $grade); } arsort($students); printf("Best: %s; skipped %d line(s)\n", array_key_first($students), $skipped);
Ana 83.3 B Cleo 94.0 A Dev 57.7 F Best: Cleo; skipped 1 line(s)