A language tour
Written off a hundred times. Still running 77% of the web. PHP grew up in public — and the modern version is worth a second look.
01 — Where It All Started
PHP's original insight was radical in its simplicity: put the logic inside the page. No framework, no build step — just a file the server executes. That frictionless beginning is why PHP spread everywhere.
"PHP is a minor web programming language whose name is a recursive acronym for PHP: Hypertext Preprocessor. The joke is that it preprocesses itself."
— The internet, fondly<?php $user = [ 'name' => 'Ada Lovelace', 'joined' => 2019, 'posts' => 142, ]; $greeting = date('H') < 12 ? 'Good morning' : 'Hello'; ?> <h1><?= htmlspecialchars($greeting . ', ' . $user['name']) ?></h1> <p>Member since <?= $user['joined'] ?> — <?= $user['posts'] ?> posts</p> <?php foreach ($user['posts'] as $post): ?> <article><?= htmlspecialchars($post['title']) ?></article> <?php endforeach; ?>
<?= is shorthand for <?php echo — a small kindness that kept countless templates readable.
02 — PHP 8
PHP 8 arrived with match expressions, named arguments, the nullsafe operator, and union types. The awkward teenager became a capable adult — concise where it used to be verbose, safe where it used to be fragile.
<?php // match — like switch but strict, exhaustive, and an expression $status = 404; $message = match($status) { 200 => 'OK', 301, 302 => 'Redirect', 404 => 'Not Found', 500 => 'Server Error', default => 'Unknown', }; // Named arguments — order doesn't matter, intent is clear array_slice( array: $items, offset: 2, length: 5, preserve_keys: true, ); // Nullsafe operator — chain through null without crashing $city = $user?->getAddress()?->getCity()?->getName(); // First-class callables (PHP 8.1) $fn = strlen(...); $lengths = array_map($fn, ['hello', 'world']); // → [5, 5]
The nullsafe ?-> operator short-circuits the whole chain the moment it hits null — no nested isset() required.
03 — Types & Enums
PHP 8 brought a real type system: union types, intersection types, readonly properties, and proper enums. Code that once relied entirely on convention now has the compiler on its side.
<?php // Backed enum — each case carries a value enum Suit: string { case Hearts = 'H'; case Diamonds = 'D'; case Clubs = 'C'; case Spades = 'S'; public function label(): string { return match($this) { Suit::Hearts => '♥ Hearts', Suit::Diamonds => '♦ Diamonds', Suit::Clubs => '♣ Clubs', Suit::Spades => '♠ Spades', }; } } // Readonly properties — set once, immutable forever class Card { public function __construct( public readonly Suit $suit, public readonly int $value, public readonly string $label, ) {} } $card = new Card(Suit::Hearts, 14, 'Ace'); echo $card->suit->label(); // → ♥ Hearts
Constructor promotion — public readonly string $label in the parameter list — declares, assigns, and locks the property in one stroke.
04 — Closures & Collections
PHP closures and arrow functions bring functional patterns to the language. Combined with array_map, array_filter, and array_reduce, you can build expressive data pipelines without a library.
<?php $orders = [ ['id' => 1, 'total' => 120.00, 'paid' => true], ['id' => 2, 'total' => 45.00, 'paid' => false], ['id' => 3, 'total' => 230.00, 'paid' => true], ]; // Arrow functions capture outer scope implicitly $minPaid = 100.0; $revenue = array_sum( array_column( array_filter( $orders, fn($o) => $o['paid'] && $o['total'] >= $minPaid ), 'total' ) ); // → 350.00 // array_reduce — fold a collection into a single value $summary = array_reduce($orders, fn($carry, $o) => $carry + ($o['paid'] ? $o['total'] : 0), 0.0 ); // → 350.00 // Spread operator — unpack any array as arguments function total(float ...$amounts): float { return array_sum($amounts); } $prices = [9.99, 24.99, 4.50]; echo total(...$prices); // → 39.48
Arrow functions with fn() => capture surrounding variables automatically — no use (&$var) boilerplate needed.
05 — Fibers
PHP 8.1 introduced Fibers — suspendable execution contexts that let you write async-style code that reads synchronously. Frameworks like Laravel and ReactPHP use them under the hood to handle thousands of connections without multi-threading complexity.
<?php // A Fiber is a function that can pause itself mid-execution $fiber = new Fiber(function (): void { $value = Fiber::suspend('first yield'); echo "Fiber received: " . $value . "\n"; Fiber::suspend('second yield'); echo "Fiber finishing.\n"; }); // Start the fiber — runs until first suspend() $val1 = $fiber->start(); echo $val1 . "\n"; // → first yield // Resume with a value — the fiber sees it as the return of suspend() $val2 = $fiber->resume('hello back'); // → Fiber received: hello back echo $val2 . "\n"; // → second yield $fiber->resume(); // → Fiber finishing.
A Fiber yields control back to its caller mid-execution and resumes exactly where it left off — the foundation for non-blocking I/O without threads.
06 — The Whole Picture
WordPress, Wikipedia, Facebook (still), Slack's backend. More production traffic flows through PHP than almost any other language.
Eloquent ORM, Blade templates, Artisan CLI, queues, broadcasting — Laravel turned PHP into one of the most productive web frameworks anywhere.
declare(strict_types=1) turns PHP's type coercion off entirely. Opt in to strictness wherever you want it.
Packagist hosts over 400,000 packages. composer require is fast, reproducible, and the model other ecosystems copied.
A Just-In-Time compiler landed in PHP 8, bringing significant performance gains for CPU-bound workloads — a long time coming.
30 years of production use means the edge cases are known, the security model is documented, and the community has seen everything.