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";

Output text.

 "Hello, World!\n";

Every variable name starts with this character.

total = 42;

Join two strings.

$full = $first  " " . $last;

Show a value's type and contents for debugging.

($value);

Use a default when a value is null or missing.

$name = $_GET['name']  'guest';

Compare value and type.

if ($input  '0') { /* ... */ }

Integer division that discards the remainder.

$boxes = ($items, 12);

Define a constant at the top level of a file.

 VAT_RATE = 0.2;

Control flow and strings

A match arm for any other value.

$label = match ($code) { 200 => 'OK',  => 'Other' };

Loop over keys and values.

foreach ($prices as $item  $price) { /* ... */ }

Leave two nested loops at once.

;

Check whether a string contains another (PHP 8).

if (($email, '@')) { /* ... */ }

Split a string into an array.

$parts = (',', $csvLine);

Format a number with two decimals.

printf("Total: \n", $total);

Count characters, not bytes, in UTF-8 text.

$length = ($name);

Test a string against a regular expression.

if (('/^\d{4}$/', $pin)) { /* ... */ }

Arrays and functions

Add elements to the end of an array with a function.

($items, 'notebook', 'pen');

The number of elements in an array.

$n = ($items);

Transform every element.

$doubled = (fn($n) => $n * 2, $numbers);

Keep only some elements.

$adults = ($people, fn($p) => $p['age'] >= 18);

Extract one field from every row.

$names = ($rows, 'name');

Sort with a custom comparison.

($products, fn($a, $b) => $a['price'] <=> $b['price']);

The operator that returns -1, 0 or 1.

return $a  $b;

Accept any number of arguments.

function total(float $amounts): float { return array_sum($amounts); }

Make a parameter accept a string or null.

function greet(string $name): string { /* ... */ }

A short function that captures outside variables automatically.

$withTax = ($p) => $p * (1 + $rate);

Object-oriented PHP

Refer to the current object inside a method.

return ->name;

The constructor method's name.

public function (public string $name) {}

A property that can only be set once.

public  float $price;

Readable by everyone, writable only inside the class (PHP 8.4).

public  float $balance = 0;

Inherit from a parent class.

class Car  Vehicle { }

Promise to provide an interface's methods.

class Circle  Shape { }

Include a trait's methods in a class.

 HasTimestamps;

Turn a stored string into an enum case, or null if invalid.

$status = Status::($row['status']);

Web, data and errors

Escape a value before printing it into HTML.

<p><?= ($comment) ?></p>

Start (or resume) the session.

();

Redirect the browser.

('Location: /thanks.php');
exit;

Prepare a query with a placeholder.

$stmt = $pdo->('SELECT * FROM users WHERE email = ?');

Hash a password for storage.

$hash = ($password, PASSWORD_DEFAULT);

Make json_decode throw on invalid JSON.

$data = json_decode($json, true, flags: );

Code that always runs after try/catch.

try { /* ... */ } catch (Exception $e) { /* ... */ }  { fclose($handle); }

Produce values lazily from a function.

 $line;

Load all Composer-installed classes.

require __DIR__ . '/vendor/';

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.

Try it yourself · main.php
Edit the code, copy it into main.php, then run it with php main.php (PHP 8.5). How to install PHP
Show a model solution
PHP
<?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";
}
Output
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.

Try it yourself · main.php
Edit the code, copy it into main.php, then run it with php main.php (PHP 8.5). How to install PHP
Show a model solution
PHP
<?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";
Output
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.

Try it yourself · main.php
Edit the code, copy it into main.php, then run it with php main.php (PHP 8.5). How to install PHP
Show a model solution
PHP
<?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());
}
Output
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.

Try it yourself · main.php
Edit the code, copy it into main.php, then run it with php main.php (PHP 8.5). How to install PHP
Show a model solution
PHP
<?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";
}
Output
'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.

Try it yourself · main.php
Edit the code, copy it into main.php, then run it with php main.php (PHP 8.5). How to install PHP
Show a model solution
TXT grades.csv
Ana,82,91,77
Ben,65,abc,70
Cleo,95,88,99
Dev,54,61,58
PHP
<?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);
Output
Ana    83.3  B
Cleo   94.0  A
Dev    57.7  F
Best: Cleo; skipped 1 line(s)