Lab · Playground
PHP playground
PHP 8.4 running as WebAssembly in your browser — its do-everything arrays, the string library, match, enums, readonly classes, and generators.
PHP began in 1994 as Rasmus Lerdorf’s "Personal Home Page" tools, a few CGI programs for his own
site, and became the language much of the web is still written in (WordPress, Wikipedia,
Facebook’s early years). Modern PHP is a different animal from the PHP of the 2000s: since 7.0 it
has had scalar types and a fast engine, and the 8.x releases added match, enums, readonly
properties, and named arguments.
These blocks run PHP 8.4 compiled to WebAssembly (php-wasm)
in a Web Worker. The first Run downloads about 12 MB; each run gets a fresh interpreter. The
opening <?php is optional.
One array type that is a list, a map, and ordered
<?php
$stock = ['apples' => 12, 'pears' => 0, 'plums' => 31, 'figs' => 7];
$stock['quinces'] = 4; // appended, and keys stay in insertion order
arsort($stock); // sort by value, keeping the keys
print_r($stock);
$inStock = array_filter($stock); // with no callback: drop "empty" values (0)
echo implode(', ', array_keys($inStock)), "\n";
echo 'total: ', array_sum($stock), "\n";
[$first, $second] = array_keys($stock); // destructuring
echo "most: $first, then $second\n";
$list = [3, 1, 4, 1, 5, 9, 2, 6];
echo json_encode(array_values(array_unique($list))), "\n";
echo json_encode(array_count_values($list)), "\n";
The string library
<?php
$title = "the quick brown fox jumps over the lazy dog";
echo ucwords($title), "\n";
echo str_word_count($title), " words, ", strlen($title), " bytes\n";
echo wordwrap(ucfirst($title), 16, "\n", true), "\n";
echo str_pad('42', 8, '0', STR_PAD_LEFT), ' | ', str_pad('right', 10, '.'), "|\n";
printf("%-8s|%6.2f|%'*10d\n", 'price', 3.14159, 42);
// Fuzzy matching built in
foreach (['colour', 'kolor', 'cooler'] as $w) {
printf("%-7s levenshtein %d similar %d%% soundex %s\n",
$w, levenshtein('color', $w), similar_text('color', $w, $pct) ? $pct : 0, soundex($w));
}
echo str_contains($title, 'fox') ? "found the fox\n" : "no fox\n";
echo strrev('stressed'), ' ', strtoupper(substr($title, 4, 5)), "\n";
match and enums (PHP 8.0 and 8.1)
<?php
enum Suit: string {
case Hearts = '♥'; case Diamonds = '♦'; case Clubs = '♣'; case Spades = '♠';
public function colour(): string {
return match ($this) {
Suit::Hearts, Suit::Diamonds => 'red',
Suit::Clubs, Suit::Spades => 'black',
};
}
}
foreach (Suit::cases() as $s) {
echo str_pad($s->name, 9), $s->value, ' ', $s->colour(), "\n";
}
echo Suit::from('♣')->name, "\n";
var_dump(Suit::tryFrom('★')); // null rather than an error
function grade(int $score): string {
return match (true) { // match compares strictly and returns a value
$score >= 90 => 'A',
$score >= 75 => 'B',
$score >= 50 => 'C',
default => 'fail',
};
}
echo implode(' ', array_map('grade', [95, 80, 51, 12])), "\n";
Classes: promoted constructors, readonly, named arguments
<?php
final class Money {
public function __construct(
public readonly int $cents,
public readonly string $currency = 'USD',
) {}
public function add(Money $other): static {
if ($other->currency !== $this->currency) throw new InvalidArgumentException('currency mismatch');
return new static($this->cents + $other->cents, $this->currency);
}
public function __toString(): string {
return sprintf('%s %s', $this->currency, number_format($this->cents / 100, 2));
}
}
$a = new Money(1999);
$b = new Money(currency: 'USD', cents: 501); // named arguments, any order
echo $a->add($b), "\n";
try {
$a->cents = 0;
} catch (Error $e) {
echo get_class($e), ': ', $e->getMessage(), "\n";
}
try {
$a->add(new Money(100, 'EUR'));
} catch (InvalidArgumentException $e) {
echo 'refused: ', $e->getMessage(), "\n";
}
Closures, arrow functions, and sorting with <⇒
<?php
$people = [
['name' => 'Ada', 'born' => 1815, 'field' => 'maths'],
['name' => 'Grace', 'born' => 1906, 'field' => 'computing'],
['name' => 'Alan', 'born' => 1912, 'field' => 'computing'],
['name' => 'Emmy', 'born' => 1882, 'field' => 'maths'],
];
usort($people, fn($a, $b) => [$a['field'], $a['born']] <=> [$b['field'], $b['born']]);
foreach ($people as $p) echo "{$p['field']}: {$p['name']} ({$p['born']})\n";
$olderThan = fn(int $year) => fn(array $p) => $p['born'] < $year; // arrow functions capture $year
echo implode(', ', array_column(array_filter($people, $olderThan(1900)), 'name')), "\n";
$counter = function () { static $n = 0; return ++$n; };
$counter(); $counter();
echo 'called ', $counter(), " times\n";
Generators: lazy sequences with yield
<?php
function primes(): Generator {
$found = [];
for ($n = 2; ; $n++) {
foreach ($found as $p) {
if ($p * $p > $n) break;
if ($n % $p === 0) continue 2;
}
$found[] = $n;
yield $n;
}
}
function take(iterable $it, int $n): array {
$out = [];
foreach ($it as $v) { if (count($out) === $n) break; $out[] = $v; }
return $out;
}
echo implode(' ', take(primes(), 20)), "\n";
function wordsIn(string $text): Generator {
foreach (preg_split('/\W+/u', strtolower($text), -1, PREG_SPLIT_NO_EMPTY) as $i => $w) yield $i => $w;
}
foreach (wordsIn("To be, or not to be: that is the question.") as $i => $w) echo "$i:$w ";
echo "\n";