Chris Collins

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.

Code blocks on this page are editable — change them and press Run.

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