PHP 8 Features Worth Using on Real Projects

A practical tour of PHP 8.x features that improve real business code: typed properties, named arguments, match, attributes, readonly, enums, nullsafe operator and more — with examples.

Share LinkedIn X Facebook Email

PHP 8 is not a fashion release. It is a collection of language features that remove entire categories of bugs and boilerplate from everyday business code — portals, invoice tools, WordPress-adjacent apps and custom APIs. You do not need to adopt every shiny thing on day one. You should adopt the features that make failures loud and intent obvious.

This guide focuses on features I actually reach for on client work, with examples you can drop into modern PHP 8.1+ codebases.

Abstract geometric PHP architecture blocks in a modern server room glow
Modern PHP rewards clear types and small, honest functions.

Constructor property promotion

Less boilerplate, same clarity:

final class InvoiceService
{
    public function __construct(
        private PDO $db,
        private Mailer $mailer,
        private string $fromEmail,
    ) {}
}

Prefer final on services unless you have a real extension story. Prefer explicit types on every constructor argument.

Named arguments

Named arguments document call sites — especially functions with boolean flags:

render_quote_pdf(
    quote: $quote,
    client: $client,
    includeVat: true,
    watermark: 'DRAFT',
);

They also let you skip optional parameters without counting commas. Use them at boundaries; do not make every internal call noisy.

match over sprawling switch

function invoice_label(string $status): string
{
    return match ($status) {
        'draft' => 'Draft',
        'sent' => 'Awaiting payment',
        'paid' => 'Paid',
        'void' => 'Void',
        default => throw new InvalidArgumentException('Unknown status: ' . $status),
    };
}

match is expression-based, does strict comparisons, and can throw on unexpected values — better than silent fall-through bugs.

Abstract typed PHP code blocks connected with neon arrows
Enums and match expressions make domain states explicit.

Enums for domain states

enum InvoiceStatus: string
{
    case Draft = 'draft';
    case Sent = 'sent';
    case Paid = 'paid';
    case Void = 'void';

    public function isOpen(): bool
    {
        return $this === self::Draft || $this === self::Sent;
    }
}

$status = InvoiceStatus::from($row['status']);
if ($status->isOpen()) {
    // show pay button
}

Backed enums map cleanly to database strings. Invalid DB values throw early instead of corrupting UI logic.

Nullsafe operator

$city = $client?->billingAddress?->city;
$label = $user?->profile?->displayName() ?? 'Guest';

Use when absence is normal. Do not use it to hide bugs you should fix with better invariants.

Readonly properties and classes

readonly class Money
{
    public function __construct(
        public int $amountPence,
        public string $currency = 'GBP',
    ) {
        if ($this->amountPence < 0) {
            throw new InvalidArgumentException('Amount must be >= 0');
        }
    }
}

Readonly value objects prevent accidental mutation of money, IDs and config bags — a frequent source of subtle bugs.

Union types, intersection types and mixed honesty

function cache_get(string $key): array|string|null
{
    // ...
}

function handle(Request&JsonRequest $req): Response
{
    // intersection: must satisfy both contracts
}

Prefer precise types over mixed. If you need mixed, you probably need a boundary sanitiser that narrows the type quickly.

Attributes instead of docblock magic

#[Attribute(Attribute::TARGET_METHOD)]
final class Route
{
    public function __construct(
        public string $path,
        public string $method = 'GET',
    ) {}
}

final class QuoteController
{
    #[Route('/api/quotes', method: 'POST')]
    public function create(): void {}
}

Attributes are real PHP values — ideal for lightweight routers, admin permissions and validation metadata without a giant framework.

Fibers and async — optional, not mandatory

Fibers power modern async libraries, but most business apps still win with clear synchronous request cycles, queues and cron. Do not rewrite a billing portal into async PHP for fashion. Do use queues for email, image processing and webhook fan-out.

Error handling that production can live with

try {
    $pdo->beginTransaction();
    // writes...
    $pdo->commit();
} catch (Throwable $e) {
    if ($pdo->inTransaction()) {
        $pdo->rollBack();
    }
    error_log('[invoice.create] ' . $e->getMessage());
    throw $e; // or map to a user-safe response
}

In PHP 8+, Throwable is the safe top type for catch-all boundaries. Never display raw exceptions to end users.

Str functions you should actually use

if (str_starts_with($path, '/preview/admin')) { /* ... */ }
if (str_ends_with($file, '.pdf')) { /* ... */ }
if (str_contains($haystack, $needle)) { /* ... */ }

Clearer than strpos(...) !== false noise.

A pragmatic adoption path

  1. Run PHP 8.2/8.3 on staging with the same extensions as production
  2. Turn on stricter typing in new files (declare(strict_types=1);)
  3. Convert arrays-of-strings statuses to enums when you touch those modules
  4. Replace nested ternary / switch status maps with match
  5. Introduce readonly value objects for money and IDs
  6. Leave micro-optimisations for measured bottlenecks

First-class callables and cleaner arrays

// First-class callable syntax (PHP 8.1+)
$names = array_map(strtoupper(...), $names);

// array_is_list helps distinguish JSON objects vs lists
if (!array_is_list($payload)) {
    throw new InvalidArgumentException('Expected a list');
}

Small helpers reduce noise in mapping layers between SQL rows and API responses.

Deprecations and dynamic properties

PHP 8.2 tightened dynamic properties on classes. That surfaces messy “magic bag” objects that silently accepted typos. Prefer explicit DTOs:

final class ClientDTO
{
    public function __construct(
        public string $id,
        public string $name,
        public ?string $email,
    ) {}

    public static function fromRow(array $row): self
    {
        return new self(
            id: (string) $row['id'],
            name: (string) $row['name'],
            email: isset($row['email']) ? (string) $row['email'] : null,
        );
    }
}

When upgrading legacy code, fix deprecations on staging with logging before they become hard errors on a future release.

Performance reality check

PHP 8 brought meaningful engine improvements, but the slow part of most business apps is still:

  • N+1 database queries
  • Remote HTTP calls in the request cycle
  • Uncached expensive reports
  • Giant autoloaded plugin stacks (WordPress)

Profile before rewriting syntax for speed. Typed code helps maintainability first; queues and indexes help latency.

// Bad: query inside a loop
foreach ($ids as $id) {
    $stmt = $pdo->prepare('SELECT * FROM items WHERE id = ?');
    $stmt->execute([$id]);
}

// Better: one query
$in = implode(',', array_fill(0, count($ids), '?'));
$stmt = $pdo->prepare("SELECT * FROM items WHERE id IN ($in)");
$stmt->execute($ids);

WordPress note

WordPress itself continues to evolve PHP support. Custom plugins and mu-plugins you control can use modern PHP even when you keep a conservative WP core stance — just align the minimum PHP version with your host and plugin ecosystem. Do not ship PHP 8.3-only syntax to a client still on 7.4 hosting (they should upgrade — help them).

Modern PHP is less about cleverness and more about making illegal states harder to represent.

FAQ

Is PHP still relevant in 2026?
Yes for web delivery, CMS ecosystems and pragmatic business apps. Relevance is measured in shipped value, not Twitter trends.

Should I upgrade major versions casually?
Upgrade deliberately: staging, test suite or smoke checklist, backups, then production.

Do I need a framework to use PHP 8 well?
No. Frameworks help large apps. Clear structure and types help every app.

Need a custom PHP portal or modernisation pass?

I build and tidy PHP systems for UK businesses — invoices, client areas, integrations — with code you can maintain.

Get a free quote →

Share this guide LinkedIn X Email
Need a site built? Free call · UK studio Book a free call