Development Tips

Modern PHP Practices for 2026: What I Actually Use on Production Sites

By Ken Key May 9, 2025 4 min read Updated Oct 16, 2026

Modern PHP Practices for 2026: What I Actually Use on Production Sites
The short answer

In 2026 I run PHP 8.3 or newer on every production site, pin it in composer.json, and add declare(stricttypes=1) to each file. I use constructor promotion, readonly properties, and enums daily. Anything below 8.2 is a security liability, not a style choice.

Part of the complete web development guide.

On this page

Most PHP advice floating around the web was written for PHP 5.6 and never updated. Meanwhile PHP 8.4 shipped in late 2024, 8.5 landed in November 2025, and the language has quietly become something you can write clean, typed, fast code in. Here’s what I actually reach for on client projects in 2026, and the stuff I’ve stopped doing.

Pin your PHP version and stop guessing

If you’re still running 7.4, you’re on a version with no security support since November 2022. That’s not a style preference, it’s a liability. As of 2026, supported versions are 8.2, 8.3, 8.4, and 8.5. If your host won’t let you pick, that’s your answer about the host.

Put the version in composer.json so nothing drifts:

{
 "require": {
 "php": ">=8.3",
 "ext-pdo": "*"
 }
}

Then run composer check-platform-reqs in your deploy script. It fails the build if the server is on the wrong version. Takes ten seconds to set up, saves you from the classic “works on my machine” phone call.

Turn on strict types. Every file.

PHP’s default type juggling is generous in ways that bite you later. declare(strict_types=1); at the top of every file makes the engine stop guessing.

<?php
declare(strict_types=1);

final class InvoiceTotal
{
 public function __construct(
 private readonly int $cents,
 private readonly string $currency = 'USD',
 ) {}

 public function add(int $cents): self
 {
 return new self($this->cents + $cents, $this->currency);
 }
}

Readonly promoted properties, constructor promotion, and enums (all stable since 8.1) cut a ton of boilerplate. I write more small value objects now and fewer arrays-of-mystery-keys, and the code is easier to read six months later.

Database access: PDO with prepared statements, always

String-concatenated SQL is still the number one way PHP apps get owned. There is no reason to do it in 2026.

$stmt = $pdo->prepare(
 'SELECT id, email FROM users WHERE status = :status AND created_at > :since'
);
$stmt->execute([
 'status' => 'active',
 'since' => $since,
]);
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);

A couple of settings that matter on the connection itself:

$pdo = new PDO($dsn, $user, $pass, [
 PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
 PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
 PDO::ATTR_EMULATE_PREPARES => false,
]);

That last one forces real server-side prepared statements instead of PDO faking it. It closes off a class of edge-case injection and makes integer binding behave the way you’d expect.

Composer hygiene

  • Commit composer.lock. Always. It’s what makes your local build match production.
  • Deploy with composer install --no-dev --optimize-autoloader, not composer update. Update is a decision you make on purpose, in a branch, with tests.
  • Run composer audit in CI. It checks your dependency tree against known advisories and fails the build if something’s flagged.
  • Keep dev-only tools (PHPUnit, PHPStan, Pint) in require-dev so they don’t ship to the server.

Catch real bugs before they hit production

Two tools do most of the heavy lifting for me:

# Static analysis, level 6 is a reasonable starting point
vendor/bin/phpstan analyse src --level=6

# Code style, so diffs stay about logic not spacing
vendor/bin/pint --test

PHPStan won’t catch everything, but it catches the dumb stuff (typos in property names, wrong return types, nulls you forgot about) before a customer does. Pair it with a real error tracker in production. error_log() to a file nobody reads is not monitoring.

Caching and opcache

Opcache is non-negotiable. It caches compiled bytecode so PHP isn’t re-parsing every request. In php.ini:

opcache.enable=1
opcache.memory_consumption=256
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0 ; production only, restart PHP-FPM on deploy

That validate_timestamps=0 line means PHP stops checking file mtimes on every request, which is a real speedup. The tradeoff is you must reload PHP-FPM when you deploy, otherwise you serve stale code. Most deploy scripts handle this with a systemctl reload php8.4-fpm or equivalent. Just don’t forget it.

For anything beyond that, reach for a real cache layer (Redis or APCu) rather than rolling your own file-based cache. And if your app is doing heavy work on every page load, that’s often a sign you need custom web development work rather than another plugin.

Things I’ve stopped doing

  • Suppressing errors with @. Fix the cause or handle the exception.
  • Using mysql_* or mysqli for new code. PDO is the sane default.
  • Building giant utility classes. Small, focused classes with typed inputs are easier to test and reason about.
  • Ignoring composer audit warnings. That’s how you end up patching on a Friday.

FAQ

Do I need to upgrade to PHP 8.4 or 8.5 right now?

You need to be on a supported version (8.2 or newer). If you’re on 8.2, you have runway, but plan the move to 8.3+ this year. Running an unsupported version means no security patches, full stop.

Is strict_types worth the friction on an existing codebase?

Add it file by file as you touch them, not all at once. New files get it from day one. Old files get it when you’re already editing them for another reason. Trying to flip the whole app at once usually turns into a week of type errors.

What’s the single highest-impact change most sites are missing?

Usually opcache tuning plus a proper deploy that reloads PHP-FPM. It’s a small config change that speeds up every request, and it’s the one people skip because it’s invisible.

Have a project like this?

I do custom web development for Long Island businesses. If you need this done right, let’s talk.