Contents
- 1 What Is New in PHP 8
- 1.1 Named Parameters: Arguments, but make them readable
- 1.2 Attributes: Metadata without the mess
- 1.3 Constructor Property Promotion: Value objects, simplified
- 1.4 Match Expressions: Switch, but smarter
- 1.5 Union Types and Mixed: Types that flex
- 1.6 JIT Compiler: Speed you can feel
- 1.7 Nullsafe Operator and Throw Expressions
- 1.8 String Helpers: No more strpos gymnastics
- 1.9 Stringable Interface and ::class on Objects
- 1.10 WeakMaps: Garbage collection's friend
- 1.11 New Functions Worth Bookmarking
- 1.12 Error Handling Glow-Up
- 1.13 Other Syntax Sweets
- 1.14 Breaking Changes: Tread Carefully
- 1.15 Why PHP 8 Matters Now
What Is New in PHP 8
Hey, fellow PHP developers. Picture this: it's one of those late nights, screen glowing in a dim room, coffee gone cold beside the keyboard. You're knee-deep in refactoring some legacy code, and suddenly you hit that sweet spot—a cleaner syntax, a type check that actually enforces what you mean, performance that doesn't stutter under load. That's PHP 8 for you. Released back in late 2020, it wasn't just an update; it felt like PHP growing up, shedding some old skin while keeping its soul intact. If you've been on 7.4 or earlier, this is your wake-up call. Let's dive in, not as a dry list, but as a conversation about why these changes hit different.
I've been riding PHP waves since the 5.x days, and PHP 8? It sparked something. Less boilerplate, more intent. Bugs that used to sneak by? They're louder now, in a good way. And performance—oh man, JIT alone can make your apps hum. But it's the little things, the syntax sugar, that make daily coding feel less like wrestling a bear.
Named Parameters: Arguments, but make them readable
Ever called a function with a dozen params and forgot which was which? Named parameters in PHP 8 let you skip the positional dance. Pass user: $data['name'], age: 30 instead of counting fingers.
function makeUser(string $name, int $age, string $email) { /* ... */ }
// Old way: makeUser('Alice', 30, 'alice@example.com');
makeUser(name: 'Alice', age: 30, email: 'alice@example.com');
Child's play for optional args too—they're skippable by name. Readability skyrockets, especially in teams. I remember debugging a payment gateway call; one swapped arg, hours lost. Never again.
Have you tried it yet? It feels liberating, like finally labeling your cables.
Attributes: Metadata without the mess
Attributes—think annotations, but native. Slap #[Deprecated] on a method, and Reflection grabs it at runtime. No more docblock hacks.
#[Route('/users/{id}')]
class UserController {
#[Required]
public function show(int $id) { /* ... */ }
}
Frameworks like Symfony pounced on this. It's metadata that works, fetchable via ReflectionAttribute. I used it last week for API validation—clean, no pollution.
Constructor Property Promotion: Value objects, simplified
This one's my secret weapon for DTOs. Declare props right in the constructor, with types and visibility. PHP auto-generates the class properties.
// Boilerplate hell
class User {
public function __construct(
public string $name,
public int $age,
private string $email
) {}
}
No more public $name; __construct($name) { $this->name = $name; }. Cuts lines by half. Perfect for those data bags we all hoard. I built a quick config class yesterday—felt like cheating.
Match Expressions: Switch, but smarter
switch had its charms, but match is exhaustive, type-safe, returns values, no break needed.
$status = match($code) {
200 => 'OK',
404 => 'Not Found',
default => 'Error',
};
Guarantees a match or throws. Handles arrays too: in [1,2,3]. I swapped a giant switch in an order processor—code shrank, bugs fled.
Union Types and Mixed: Types that flex
Union types: int|float|string. Native, enforced. mixed covers anything. Before, nullable was our only union trick.
function process(mixed $data): string|false { /* ... */ }
Inheritance checks them too—Liskov approved. Types in sigs mean fewer runtime oops. My API handlers? Bulletproof now.
JIT Compiler: Speed you can feel
Just-In-Time compilation, via OPcache. Compiles PHP to machine code on-the-fly. Not magic, but benchmarks show 20-50% gains on CPU-heavy tasks. Enable with opcache.jit=tracing.
I profiled a data cruncher—loops flew. Web? Gains subtler, but hello microservices.
Question for you: still on OPcache-only? JIT's your next gear.
Nullsafe Operator and Throw Expressions
?-> chains without null explosions: $user?->address?->street. Clean.
throw as expression: if (!isset($bar['offset'])) throw new Error();.
No more temp vars. Subtle, but they stack up in real code.
String Helpers: No more strpos gymnastics
str_contains('Foobar', 'Foo')—true. Beats strpos() !== false. Add str_starts_with, str_ends_with. Self-explanatory, less error-prone.
if (str_ends_with($file, '.php')) { /* process */ }
I've refactored dozens of these. Readability win, every time.
Stringable Interface and ::class on Objects
Classes with __toString() auto-implement Stringable. Type as string|Stringable.
$obj::class works now. Ditch get_class($obj).
Tiny? Yes. Annoying before? Absolutely.
WeakMaps: Garbage collection's friend
WeakMap holds object refs without blocking GC. ORMs love it for caches—entities die when unused.
$cache = new WeakMap();
$cache[$user] = $data; // $user GC-able
Solved a memory leak in my entity loader. Game-changer for long-runners.
New Functions Worth Bookmarking
fdiv(): Float division, IEEE-754 safe. Handles zero gracefully (INF, NaN).get_debug_type($var): Precise types, likearrayorDateTime.get_resource_id($res): Resource internals.preg_last_error_msg(): Regex errors in English.
| Function | What it does | Why you'll use it |
|---|---|---|
str_contains |
Checks substring presence | Ditch strpos !== false |
fdiv |
Safe float div | No more div-by-zero panics |
get_debug_type |
Exact var type | Debug like a pro |
Error Handling Glow-Up
ValueErrorexception.@doesn't suppress fatals.- PDO defaults to exceptions.
- Internal funcs throw
TypeError/ValueError. catch (TypeError $e)—non-capturing.
Default E_ALL. Bugs scream earlier.
Other Syntax Sweets
- Trailing commas in params/closures.
staticreturn type.DateTime::createFromInterface().pin date() for UTC.
Resources to objects (CurlHandle, etc.)—consistent.
Breaking Changes: Tread Carefully
Upgrading? Test.
- Non-static methods called statically? Fatal.
- String/number compares: no auto-cast.
- Arithmetic: stricter.
- Private method inheritance relaxed (private stays private).
Deprecations from 7.x bit us less if you're current.
I migrated a monolith last year—static calls hurt, but tools like Rector smoothed it.
Why PHP 8 Matters Now
PHP 8 isn't yesterday's news; it's the baseline. Laravel 9+, Symfony 6 demand it. Performance, types, DX—all pull you forward. That late-night glow? Brighter with less friction.
Friends, if you're stalling, spin up a branch. Feel the shift. It's PHP saying, "We've listened." Code that reads like thoughts, runs like wind. Quietly powerful, just like the best tools.
There's a certain calm in code that trusts itself.