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