0x55aa
← Back to Blog

PHP 8.x Features You're Ignoring (and Why Your Code Suffers) 😤

7 min read

PHP 8.x Features You're Ignoring (and Why Your Code Suffers) 😤

I'll be honest. A few years back, I inherited a Laravel codebase at Cubet that was running on PHP 8.1 but had code clearly written by someone who stopped reading PHP docs circa 2013. Nested ternaries. array_map everywhere. isset() chains that looked like a ransom note.

The app worked. But every time I opened a file, a tiny piece of my soul died.

PHP 8.x brought us some genuinely life-changing features. And most developers I talk to are using maybe 20% of them. Let's fix that.


1. Named Arguments: Stop Playing Guess-the-Parameter 🎯

Before (every developer's nightmare):

// What does true mean here? And false? Anyone?
$result = array_slice($items, 0, 5, true);

After (your future self says thank you):

$result = array_slice(array: $items, offset: 0, length: 5, preserve_keys: true);

In production systems I've built, this alone has saved hours of "wait, which argument is which?" confusion during code reviews. Named arguments work great with complex constructors too — especially when creating Value Objects in your Laravel domain layer.


2. Match Expressions: switch/case Finally Grew Up 🧓➡️💪

The old switch statement is the drunk uncle of PHP control flow. match is the responsible adult.

// Before: The classic switch mess
$discount = 0;
switch ($user->role) {
    case 'vip':
        $discount = 30;
        break;
    case 'member':
        $discount = 10;
        break;
    default:
        $discount = 0;
}

// After: Clean, strict, returns a value
$discount = match($user->role) {
    'vip'    => 30,
    'member' => 10,
    default  => 0,
};

Pro Tip: match uses strict comparison (===), so match('1') won't accidentally match 1. No more sneaky type coercion bugs!

Real Talk: I used match to replace a 60-line switch in an e-commerce pricing engine. It went from a wall of spaghetti to 8 lines that were actually readable in a code review.


3. Null-Safe Operator: The ?-> That Saved My Sanity 🧠

How many times have you written this?

// The old "null check dance"
$city = null;
if ($order !== null) {
    if ($order->user !== null) {
        if ($order->user->address !== null) {
            $city = $order->user->address->city;
        }
    }
}

Compare that to:

// PHP 8.0+ null-safe operator
$city = $order?->user?->address?->city;

One line. If anything in the chain is null, the whole expression returns null. No exceptions thrown, no nested hell.

As a Technical Lead, I've learned that the most dangerous bugs are the ones hiding inside defensive null-check pyramids. The null-safe operator makes the intent crystal clear — and it's impossible to accidentally miss a check.


4. Readonly Properties: Immutability Without the Drama 🔒

This one changed how I model data in Laravel. For Value Objects and DTOs, you no longer need private properties + getters:

// Before: Boilerplate city
class OrderSummary {
    private string $orderId;
    private float $total;

    public function __construct(string $orderId, float $total) {
        $this->orderId = $orderId;
        $this->total = $total;
    }

    public function getOrderId(): string { return $this->orderId; }
    public function getTotal(): float { return $this->total; }
}

// After: Clean, immutable, beautiful
class OrderSummary {
    public function __construct(
        public readonly string $orderId,
        public readonly float $total,
    ) {}
}

Bonus: PHP 8.2 introduced readonly classes — slap readonly on the class declaration and every property becomes readonly automatically. Chef's kiss. 👨‍🍳💋


5. Enums: No More Magic String Roulette 🎲

A pattern that saved us in a real project: we had an order status system with strings like 'pending', 'processing', 'shipped'. Someone on the team typo'd 'proccessing' (two c's). The bug took two hours to find.

PHP 8.1 Enums to the rescue:

enum OrderStatus: string {
    case Pending    = 'pending';
    case Processing = 'processing';
    case Shipped    = 'shipped';
    case Delivered  = 'delivered';
}

// Laravel Eloquent plays nice with this
protected $casts = [
    'status' => OrderStatus::class,
];

// Now your code is bulletproof
$order->status = OrderStatus::Processing; // IDE autocomplete, type safety, no typos

You also get OrderStatus::from('pending') and OrderStatus::tryFrom('garbage') (returns null instead of throwing). Enums even implement cases() to get all values — perfect for dropdowns.


6. First-Class Callables: Pass Functions Like a Pro 🏆

// Before: Wrapping in anonymous function just to pass it
$names = array_map(fn($user) => strtoupper($user->name), $users);

// After: Pass the function directly with the ... syntax
$names = array_map(strtoupper(...), $users->pluck('name')->all());

// Works with methods too
$names = array_map($this->formatName(...), $users);

In production systems I've built with complex data pipelines, this makes collection transformations read like English. Combine with Laravel Collections and you've got a very readable data pipeline.


7. Fibers: The Secret Weapon for Laravel Octane 🔥

This one's more advanced, but worth knowing. PHP 8.1 Fibers bring cooperative multitasking to PHP — and they're the foundation of how Laravel Octane handles concurrent requests so efficiently.

You probably won't write Fibers directly day-to-day, but understanding they exist explains a lot about why Octane is so fast.

$fiber = new Fiber(function(): void {
    $value = Fiber::suspend('first suspend');
    echo "Got: " . $value . PHP_EOL;
});

$value = $fiber->start();           // Returns 'first suspend'
$fiber->resume('hello from main');  // Passes 'hello from main' in

Pro Tip: If you're using Laravel Reverb or Octane, Fibers are doing the heavy lifting under the hood. No callback hell, no promise chains — just clean cooperative concurrency.


The "Am I Writing Modern PHP?" Checklist ✅

Before your next PR, ask yourself:

  • Did I use match instead of switch?
  • Did I use ?-> instead of nested null checks?
  • Are my status/type fields backed by Enums (not magic strings)?
  • Are my DTO/Value Object properties readonly?
  • Did I use named arguments for complex function calls?
  • Am I using array_is_list() instead of checking array_keys?

If you're checking less than 4 boxes, there's gold to mine here.


Bonus Tips 🎁

Intersection Types (PHP 8.1): When you need multiple interface constraints:

function process(Serializable&JsonSerializable $data): void { ... }

Never Return Type: For methods that always throw:

function fail(string $message): never {
    throw new RuntimeException($message);
}

str_contains(), str_starts_with(), str_ends_with(): PHP finally has these built-in. Stop using strpos() !== false like it's 2010.


Real Talk 💬

"My app already works fine with old PHP patterns."

It does. But "works fine" and "easy to maintain in 6 months" are different things. The developer who inherits your code (possibly future-you) will appreciate modern PHP.

"I don't know all these features."

Nobody does day one. Pick one — start with the null-safe operator or match. Use it in your next PR. Build the habit feature by feature.

"My team uses older patterns."

Time for a team lunch-and-learn. Seriously. Show them the readonly + constructor promotion combo and watch minds get blown.


The Bottom Line

PHP 8.x is genuinely great. It's not the "write-once, debug-forever" PHP of 2008 — it has real type safety, immutability tools, and expressive syntax. Laravel itself uses these features throughout its internals.

As a Technical Lead, my job isn't just to ship features — it's to make sure the codebase doesn't become a monster that eats junior developers for breakfast. Modern PHP features are a huge part of that.

Stop writing PHP 5 in your PHP 8 project. Your future self will buy you a coffee. ☕


Want to dig deeper? PHP's official migration guides are surprisingly readable. And Laravel's own source code is a masterclass in modern PHP — seriously, browse it sometime.

Got a modern PHP feature that changed how you code? Find me on LinkedIn — I love these conversations.

Now go modernize that codebase. One match at a time. 🚀