Practice

PHP exercises

Make a change, predict what it will do, and compare the result.

  1. 1

    PHP Basics

    Print a value from a PHP script.

    Adapt the lesson example. Change one value or element, predict the result, and explain why it changes.

    Show starting example
    PHP
    <?php
    $name = "Ada";
    echo "Hello, $name\n";
  2. 2

    PHP Arrays

    Loop through an indexed array.

    Adapt the lesson example. Change one value or element, predict the result, and explain why it changes.

    Show starting example
    PHP
    <?php
    $scores = [82, 91, 76];
    foreach ($scores as $score) {
        echo "$score\n";
    }
  3. 3

    PHP Functions

    Declare typed parameters and return values.

    Predict average([10, 20, 31]).

    Reveal answer and reasoning

    The sum is 61 and 61 / 3 is 20.333333333333, so that is what echo prints.

    Show starting example
    PHP
    <?php
    function average(array $values): float {
        return array_sum($values) / count($values);
    }
    
    echo average([82, 91, 76, 95]), "\n";
    echo round(average([1, 2]), 1), "\n";
  4. 4

    PHP strings, sprintf, and regular expressions

    Format text with sprintf, split and join strings, and match patterns with preg functions.

    What does sprintf("%03d", 5) return?

    Reveal answer and reasoning

    "005": the integer is zero-padded to a width of three digits.

    Show starting example
    PHP
    <?php
    $name = "Programonic";
    echo strlen($name), " ", substr($name, 0, 7), " ", strpos($name, "m"), "\n";
    echo sprintf("%05.1f|%-8s|%'*8s|%x", 3.14159, "left", "right", 255), "\n";
    
    $parts = explode(",", "a,b,,c");
    echo count($parts), " ", implode("-", $parts), "\n";
    echo str_contains("haystack", "st") ? "yes" : "no", " ", ucfirst("php"), " ", strrev("abc"), "\n";
    
    if (preg_match('/^(\d{4})-(\d{2})-(\d{2})$/', "2024-03-15", $m)) {
        echo $m[1], "/", $m[2], "/", $m[3], "\n";
    }
    echo preg_replace('/\s+/', ' ', "too    many   spaces"), "\n";
    echo str_pad("7", 3, "0", STR_PAD_LEFT), "\n";
  5. 5

    PHP strict types, match, and the nullsafe operator

    Use union types, a match expression, constructor promotion, and ?-> to write safer code.

    How does match differ from switch?

    Reveal answer and reasoning

    match uses strict comparison, returns a value, needs no break statements, and throws an error when no arm matches.

    Show starting example
    PHP
    <?php
    declare(strict_types=1);
    
    function describe(int|string|null $value): string
    {
        return match (true) {
            $value === null => "nothing",
            is_int($value) && $value < 0 => "negative int",
            is_int($value) => "int $value",
            default => "string '$value'",
        };
    }
    
    echo describe(5), "\n", describe(-2), "\n", describe("hi"), "\n", describe(null), "\n";
    
    class Address
    {
        public function __construct(public ?string $city = null) {}
    }
    
    class User
    {
        public function __construct(public string $name, public ?Address $address = null) {}
    }
    
    $users = [new User("Ada", new Address("London")), new User("Bo")];
    foreach ($users as $user) {
        echo $user->name, ": ", $user->address?->city ?? "unknown", "\n";
    }
    
    try {
        echo strlen(123);
    } catch (TypeError $e) {
        echo "TypeError caught\n";
    }
  6. 6

    PHP closures, array functions, and generators

    Transform arrays with map, filter, and reduce, capture variables in closures, and produce values lazily with yield.

    How would you take the first 5 even squares lazily?

    Reveal answer and reasoning

    Write a generator that yields squares one at a time, filter inside the loop, and break after collecting 5 values.

    Show starting example
    PHP
    <?php
    $numbers = [1, 2, 3, 4, 5, 6];
    
    $squares = array_map(fn($n) => $n * $n, $numbers);
    $evens = array_filter($squares, fn($n) => $n % 2 === 0);
    $sum = array_reduce($evens, fn($carry, $n) => $carry + $n, 0);
    echo implode(",", $squares), " | ", implode(",", $evens), " | ", $sum, "\n";
    
    $rate = 3;
    $multiply = function (int $n) use ($rate) {
        return $n * $rate;
    };
    echo $multiply(5), "\n";
    
    $total = 0;
    array_walk($numbers, function ($n) use (&$total) {
        $total += $n;
    });
    echo $total, "\n";
    
    function fibonacci(): Generator
    {
        [$a, $b] = [0, 1];
        while (true) {
            yield $a;
            [$a, $b] = [$b, $a + $b];
        }
    }
    
    $first = [];
    foreach (fibonacci() as $value) {
        if (count($first) === 10) {
            break;
        }
        $first[] = $value;
    }
    echo implode(" ", $first), "\n";
  7. 7

    PHP Classes

    Hide a property and change it only through methods.

    Add a decrement method.

    Reveal answer and reasoning

    public function decrement(): void { $this->value--; } lowers the value by one.

    Show starting example
    PHP
    <?php
    class Counter {
        private int $value = 0;
    
        public function increment(): void {
            $this->value++;
        }
    
        public function value(): int {
            return $this->value;
        }
    }
    
    $counter = new Counter();
    $counter->increment();
    $counter->increment();
    echo $counter->value(), "\n";
  8. 8

    PHP interfaces, traits, and abstract classes

    Share a contract with an interface, share code with a trait, and share both with an abstract base.

    Where would you declare a method that all shapes must implement?

    Reveal answer and reasoning

    In the Shape interface. Every class that implements it must then provide the method.

    Show starting example
    PHP
    <?php
    interface Shape
    {
        public function area(): float;
    }
    
    trait Describes
    {
        public function describe(): string
        {
            return static::class . " with area " . number_format($this->area(), 2);
        }
    }
    
    abstract class Base implements Shape
    {
        use Describes;
    }
    
    final class Circle extends Base
    {
        public function __construct(private float $radius) {}
    
        public function area(): float
        {
            return M_PI * $this->radius ** 2;
        }
    }
    
    final class Rect extends Base
    {
        public function __construct(private float $width, private float $height) {}
    
        public function area(): float
        {
            return $this->width * $this->height;
        }
    }
    
    foreach ([new Circle(1.5), new Rect(2, 3.5)] as $shape) {
        echo $shape->describe(), "\n";
    }
    echo (new Circle(1)) instanceof Shape ? "a Circle is a Shape" : "not a Shape", "\n";
  9. 9

    PHP Associative Arrays

    Map names to values and loop over keys and values.

    Remove Grace before the loop.

    Reveal answer and reasoning

    Call unset($scores["Grace"]); the loop then prints two lines and count gives 2.

    Show starting example
    PHP
    <?php
    $scores = ["Ada" => 91, "Grace" => 88];
    $scores["Linus"] = 79;
    foreach ($scores as $name => $score) {
        echo "$name: $score\n";
    }
    echo count($scores), "\n";
  10. 10

    PHP sorting, usort, and the spaceship operator

    Sort arrays by value or by custom rules, and combine several sort keys with <=>.

    How would you sort the people by age descending?

    Reveal answer and reasoning

    Swap the operands: usort($people, fn($a, $b) => $b[1] <=> $a[1]);

    Show starting example
    PHP
    <?php
    $numbers = [5, 2, 9, 1, 5, 6];
    sort($numbers);
    echo implode(" ", $numbers), "\n";
    rsort($numbers);
    echo implode(" ", $numbers), "\n";
    
    $people = [["Ada", 30], ["Bo", 25], ["Cy", 30], ["Di", 25]];
    usort($people, fn($a, $b) => $a[1] <=> $b[1] ?: strcmp($a[0], $b[0]));
    echo implode(",", array_column($people, 0)), "\n";
    
    $words = ["pear", "apple", "Fig"];
    sort($words);
    echo implode(",", $words), "\n";
    sort($words, SORT_FLAG_CASE | SORT_STRING);
    echo implode(",", $words), "\n";
    
    $scores = ["Ada" => 90, "Bo" => 85, "Cy" => 90];
    arsort($scores);
    echo implode(",", array_keys($scores)), "\n";
  11. 11

    PHP SPL data structures: stack, queue, and heap

    Use the standard SplStack, SplQueue, SplMinHeap, and SplPriorityQueue classes.

    Which structure would you use for a to-do list processed in arrival order?

    Reveal answer and reasoning

    SplQueue: the first task added is the first one processed.

    Show starting example
    PHP
    <?php
    $stack = new SplStack();
    $stack->push("a");
    $stack->push("b");
    $stack->push("c");
    echo $stack->pop(), " ", $stack->top(), " ", count($stack), "\n";
    
    $queue = new SplQueue();
    $queue->enqueue(1);
    $queue->enqueue(2);
    $queue->enqueue(3);
    echo $queue->dequeue(), " ", $queue->dequeue(), " ", count($queue), "\n";
    
    $heap = new SplMinHeap();
    foreach ([5, 1, 8, 3] as $value) {
        $heap->insert($value);
    }
    echo $heap->extract(), " ", $heap->top(), "\n";
    
    $tasks = new SplPriorityQueue();
    $tasks->insert("low", 1);
    $tasks->insert("high", 10);
    $tasks->insert("medium", 5);
    $order = [];
    while (!$tasks->isEmpty()) {
        $order[] = $tasks->extract();
    }
    echo implode(" > ", $order), "\n";
  12. 12

    PHP unit tests with closures

    Write a tiny test runner with named test closures and check normal values and expected errors.

    What does the runner do when a test throws a TypeError?

    Reveal answer and reasoning

    The TypeError is not an AssertionFailure, so it is not caught by the loop and ends the script. A more robust runner would catch Throwable and report it as an error.

    Show starting example
    PHP
    <?php
    class AssertionFailure extends Exception {}
    
    function assertSame(mixed $expected, mixed $actual): void
    {
        if ($expected !== $actual) {
            throw new AssertionFailure("expected " . var_export($expected, true) . " but got " . var_export($actual, true));
        }
    }
    
    function assertThrows(string $class, callable $code): void
    {
        try {
            $code();
        } catch (Throwable $e) {
            if ($e instanceof $class) {
                return;
            }
            throw new AssertionFailure("expected $class but got " . get_class($e));
        }
        throw new AssertionFailure("expected $class but nothing was thrown");
    }
    
    function clamp(int $value, int $low, int $high): int
    {
        if ($low > $high) {
            throw new InvalidArgumentException("low > high");
        }
        return max($low, min($high, $value));
    }
    
    $tests = [
        "inside the range" => fn() => assertSame(5, clamp(5, 0, 10)),
        "below the range" => fn() => assertSame(0, clamp(-3, 0, 10)),
        "above the range" => fn() => assertSame(10, clamp(99, 0, 10)),
        "bad bounds throw" => fn() => assertThrows(InvalidArgumentException::class, fn() => clamp(1, 5, 2)),
        "division by zero" => fn() => assertThrows(DivisionByZeroError::class, fn() => intdiv(1, 0)),
        "a failing check" => fn() => assertSame(6, clamp(7, 0, 5)),
    ];
    
    $failed = 0;
    foreach ($tests as $name => $test) {
        try {
            $test();
            echo "PASS $name\n";
        } catch (AssertionFailure $e) {
            $failed++;
            echo "FAIL $name: ", $e->getMessage(), "\n";
        }
    }
    echo count($tests) - $failed, " passed, $failed failed\n";
  13. 13

    PHP password_hash, password_verify, and hash_hmac

    Store passwords with bcrypt through the built-in API and sign messages with HMAC.

    When should password_needs_rehash be called?

    Reveal answer and reasoning

    After a successful login, using the stored hash. If it returns true, hash the password again with the current settings and save the new hash.

    Show starting example
    PHP
    <?php
    $hash = password_hash("correct horse", PASSWORD_DEFAULT);
    echo "algorithm: ", password_get_info($hash)["algoName"], "\n";
    echo "right password: ", password_verify("correct horse", $hash) ? "accepted" : "rejected", "\n";
    echo "wrong password: ", password_verify("wrong guess", $hash) ? "accepted" : "rejected", "\n";
    echo "hashes differ for equal passwords: ",
        password_hash("correct horse", PASSWORD_DEFAULT) !== $hash ? "yes" : "no", "\n";
    
    $tag = hash_hmac("sha256", "what do ya want for nothing?", "Jefe");
    echo $tag, "\n";
    $again = hash_hmac("sha256", "what do ya want for nothing?", "Jefe");
    echo "valid: ", hash_equals($tag, $again) ? "yes" : "no", "\n";
    $forged = hash_hmac("sha256", "what do ya want for something?", "Jefe");
    echo "forged valid: ", hash_equals($tag, $forged) ? "yes" : "no", "\n";
  14. 14

    PHP preventing XSS with htmlspecialchars

    Encode user text before printing it into HTML, and escape for the right context.

    Why is the single quote encoded as &#039;?

    Reveal answer and reasoning

    So that a value placed inside a single-quoted HTML attribute cannot end the attribute early and add new attributes such as onclick.

    Show starting example
    PHP
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES | ENT_SUBSTITUTE, "UTF-8");
    }
    
    $comment = '<script>alert("x")</script>';
    echo "<p>" . $comment . "</p>", "\n";
    echo "<p>" . e($comment) . "</p>", "\n";
    
    $name = "O'Brien & \"Sons\"";
    echo '<input value="' . e($name) . '">', "\n";
    echo "q=" . urlencode("a b&c=d"), "\n";
    echo json_encode(["note" => "</script>"], JSON_HEX_TAG), "\n";