Test Yourself!

These ten questions run the length of the chapter, and every one of them turns on behavior that catches working PHP developers out. Treat each numbered snippet as a separate file that begins with <?php, run by the PHP 8.5 command-line interpreter with Ubuntu 225 's stock php.ini. Run nothing: write down exactly what each snippet prints or throws, and why. The answers, with the reasoning and the section each comes from, are in Appendix H.

Questions

Questions 1-3: comparisons, match and strict typesShell
// 1. PHP 8.0 changed how numbers meet strings. Which of the twelve are true?
echo json_encode([0 == 'a', 0 == '', '1' == '01', '10' == '1e1', 100 == '1e2',
    null == false]), "\n";
echo json_encode(['abc' == 0, null == 0, [] == false, in_array('abc', [0]),
    in_array(0, ['a']), '0e123' == '0e456']), "\n";
// 2. switch, match and a backed enum. What prints before the uncaught error?
enum Status: string { case Paid = 'paid'; case Shipped = 'shipped'; }
$code = '2';
switch ($code) { case 2: echo 'int '; case '2': echo 'string '; break; default: echo 'none'; }
echo match ($code) { 2 => 'int', '2' => 'string', default => 'other' }, ' ';
echo match (true) { $code == 2 => 'loose', default => 'strict' }, "\n";
foreach (['paid', 'PAID'] as $in)
    echo match (Status::tryFrom($in)) { Status::Paid => 'charge', null => 'reject' }, ' ';
echo match (Status::from('shipped')) { Status::Paid => 'charge' }, "\n";
// 3. Which values are coerced, and which of the three calls in the loop throw?
declare(strict_types=1);
function half(float $n): float { return $n / 2; }
function label(int $qty): string { return "qty $qty"; }
echo half(5), ' ', implode(',', array_map(fn(int $n): int => $n * 2, ['1', '2'])), "\n";
foreach ([fn() => label('3'), fn() => strlen(12345), fn() => label(3.0)] as $call) {
    try { echo $call(), "\n"; } catch (TypeError) { echo "TypeError\n"; }
}
Questions 4-6: references, array finders and multibyte textPHP
// 4. Two loops over one array, then a copy of an array that holds an object.
$prices = [10, 20, 30];
foreach ($prices as &$p) { $p *= 2; }
foreach ($prices as $p) { echo $p, ' '; }
echo '| ', implode(',', $prices), "\n";
$a = ['n' => 1, 'cart' => new ArrayObject(['BK-1'])];
$b = $a;  $b['n']++;  $b['cart'][] = 'BK-2';
echo $a['n'], ' ', count($a['cart']), "\n";
// 5. The PHP 8.4 and 8.5 finders on an array whose keys are out of order.
$stock = [3 => 'Odes', 1 => 'Dune', 2 => null];
var_dump(array_first($stock), array_find($stock, fn($v) => $v === null),
    array_find_key($stock, fn($v) => $v === null), array_find($stock, fn($v, $k) => $k < 2));
var_dump(array_first([]) ?? 'empty', array_find([0, '', false], fn($v) => !$v),
    array_all([], fn($v) => false));
// 6. Bytes, code points and the pipe operator.
$s = " Zoë Ñúñez ";
echo strlen($s), ' ', mb_strlen($s), ' ', strlen(trim($s)), "\n";
echo $s |> trim(...) |> strtoupper(...), ' | ', $s |> trim(...) |> mb_strtoupper(...), "\n";
echo 2 + 3 |> (fn($n) => $n * 10), ' ', var_export(mb_check_encoding(substr('Zoë', 0, 3)),
    true), ' ', str_pad('Zoë', 5, '.'), '|', mb_str_pad('Zoë', 5, '.'), "|\n";
Questions 7-8: property hooks, clone with and daylight saving timeCSS
// 7. Property hooks, then a readonly class with a clone-with "wither".
final class Price {
    public int $cents = 0 { set => max(0, $value); }
    public string $label { get => sprintf('$%.2f', $this->cents / 100); }
}
$p = new Price();  $p->cents = -250;  echo $p->label, ' ';
$p->cents = 1999;  echo $p->label, "\n";
try { $p->label = 'free'; } catch (Error $e) { echo $e->getMessage(), "\n"; }
final readonly class Money {
    public function __construct(public int $cents) {}
    public function with(int $cents): static { return clone($this, ['cents' => $cents]); }
}
$m = new Money(500);
echo $m->with(700)->cents, ' ', $m->cents, ' ', var_export($m->with(500) == $m, true), "\n";
try { clone($m, ['cents' => 1]); } catch (Error $e) { echo $e->getMessage(), "\n"; }
// 8. New York springs forward at 02:00 on Sunday 8 March 2026. Mind the discarded result.
$ny = new DateTimeZone('America/New_York');
$start = new DateTimeImmutable('2026-03-07 09:00', $ny);
$start->modify('+1 day');
echo $start->format('D H:i T'), ' | ', $start->modify('+1 day')->format('D H:i T'), ' | ',
    $start->add(new DateInterval('PT24H'))->format('D H:i T'), "\n";
$noon = new DateTimeImmutable('@1772971200', $ny);           // 12:00 UTC that Sunday
echo new DateTimeImmutable('2026-03-08 02:30', $ny)->format('H:i T'), ' | ',
    $noon->format('H:i e'), ' | ', $noon->setTimezone($ny)->format('H:i T'), "\n";
Questions 9-10: finally and a generator driven by send()JavaScript
// 9. Three functions and three finally blocks. In what order does everything print?
function a(): string {
    $log = 'try';
    try { return $log; } finally { $log .= '+finally'; echo "[$log] "; }
}
function b(): int {
    try { throw new RuntimeException('lost'); } catch (RuntimeException) { return 1; }
    finally { return 2; }
}
function c(): void { try { throw new LogicException('boom'); } finally { echo 'cleanup '; } }
echo a(), ' ', b(), "\n";
try { c(); } catch (Exception $e) { echo $e::class, ': ', $e->getMessage(), "\n"; }
// 10. When does "start" appear, and what happens to the final foreach?
function acc(): Generator {
    echo "start\n";  $total = 0;
    while (($in = yield $total) !== null) $total += $in;
    return $total;
}
$g = acc();  echo "created\n";
echo $g->send(5), ' ', $g->send(10), ' ', $g->current(), "\n";
$g->next();  var_dump($g->valid(), $g->getReturn());
try { foreach ($g as $v) {} } catch (Exception $e) { echo $e->getMessage(), "\n"; }