Modern PHP Techniques That Still Matter in 2026

The PHP techniques that still matter most in 2026 are being on a supported release (8.3 or 8.4), readonly properties with constructor promotion, enums, typed properties, and Composer with PSR-4 autoloading. Skip dynamic properties and anything relying on deprecated 7.x behavior.
Part of the complete web development guide.
On this page
- Start with the version you’re on
- Readonly properties and constructor promotion
- Enums instead of class constants
- Match over switch, and strict types everywhere
- Stop passing arrays around
- Dependency injection without the ceremony
- Composer hygiene
- Static analysis is not optional anymore
- Testing the parts that break
- Where this fits in a real project
- FAQ
- Do I need to upgrade to PHP 8.4 right away?
- Is static analysis worth the setup time on a messy codebase?
- Can I use these techniques in WordPress plugins?
Most of the PHP running in production right now was written years ago, and a lot of it still works fine. But “works fine” and “works well” are different things, especially when you’re maintaining a codebase that’s grown past a few thousand lines. Here’s what I actually reach for in 2026, and what I’ve stopped doing.
Start with the version you’re on
The single biggest performance and security win isn’t a technique. It’s being on a supported PHP release. PHP 8.4 is the current stable line, and 8.3 still gets security fixes. If you’re on 7.4 or 8.0, you’re running software that no longer receives security patches, and that’s the thing to fix before anything else on this list.
Upgrading is usually less scary than people expect. Run your test suite, check your Composer dependencies for minimum PHP constraints, and watch for removed dynamic properties (deprecated in 8.2, gone in future versions). Tools like Rector can automate a good chunk of the mechanical changes.
Readonly properties and constructor promotion
Value objects used to be a wall of boilerplate. Not anymore.
final class Money
{
public function __construct(
public readonly int $amountInCents,
public readonly string $currency,
) {}
public function add(self $other): self
{
if ($this->currency !== $other->currency) {
throw new InvalidArgumentException('Currency mismatch');
}
return new self($this->amountInCents + $other->amountInCents, $this->currency);
}
}
Readonly kills a whole class of bugs where something mutates a value object six call frames later. Promotion removes the assignment noise. Together they make small immutable classes cheap to write, which means you’ll actually write them.
Enums instead of class constants
If you’re still doing const STATUS_ACTIVE = 'active', backed enums are strictly better. They’re type-safe, they work in match expressions, and they can carry methods.
enum OrderStatus: string
{
case Pending = 'pending';
case Shipped = 'shipped';
case Cancelled = 'cancelled';
public function isFinal(): bool
{
return match($this) {
self::Shipped, self::Cancelled => true,
self::Pending => false,
};
}
}
The compiler catches typos that string constants let through. That alone is worth the refactor.
Match over switch, and strict types everywhere
match returns a value, uses strict comparison, and throws on an unhandled case. That last part matters: a switch with a missing branch silently falls through to nothing, while match fails loudly. Loud failures are cheaper than silent ones.
And put declare(strict_types=1); at the top of every file. Without it, PHP will happily coerce "5" into 5 and you’ll spend an afternoon wondering why your validation didn’t catch the string.
Stop passing arrays around
This is the pattern I see most in older code: a function takes array $data, and you have no idea what keys it expects until you read the whole body. Then it returns an array with different keys, and now you’re tracking two undocumented shapes.
Use a typed object for anything with more than two fields. It costs you one class and saves hours of debugging. If you need to build one from request input, a small static factory with explicit validation beats extract() every time.
Dependency injection without the ceremony
You don’t need a framework to do DI. A container is just a map from interface to factory. What you actually need is to stop calling new Database() inside your business logic, because that’s what makes code untestable.
interface Clock
{
public function now(): DateTimeImmutable;
}
final class TrialChecker
{
public function __construct(private Clock $clock) {}
public function isExpired(Account $account): bool
{
return $this->clock->now() > $account->trialEndsAt;
}
}
Now you can test trial expiration without touching the system clock. That one interface removes a category of flaky tests permanently.
Composer hygiene
- Commit
composer.lockfor applications. Always. - Run
composer auditin CI. It checks your installed packages against known vulnerability databases. - Keep dev dependencies out of production with
composer install --no-dev --optimize-autoloader. - Pin your platform requirement so local and production resolve the same versions.
Static analysis is not optional anymore
PHPStan or Psalm at a reasonable level will catch null dereferences, wrong argument types, and dead code before your users do. Start at level 5, fix what it finds, then move up. On a legacy codebase, add a baseline file so you’re only blocking new problems while you work through the old ones.
Pair that with a formatter like PHP CS Fixer or Pint and you stop arguing about style in code review entirely.
Testing the parts that break
You don’t need 100% coverage. You need coverage on the code that has logic in it: pricing, permissions, date math, state transitions. Controllers and view templates can stay lightly tested. Pest has become the default for a lot of new projects because the syntax is shorter, but PHPUnit is still fine and better documented.
For anything touching the database, use transactions that roll back between tests rather than rebuilding the schema each time. It’s dramatically faster.
Where this fits in a real project
If you’re running a WordPress site or a custom PHP application and none of this is in place, the order matters. Security patches first, then static analysis and tests around the risky code, then incremental refactors. Trying to modernize everything at once is how projects stall.
That kind of incremental work is most of what I do for clients around Commack and the rest of Long Island, and it’s the same whether you’re a small business or an agency. If you’d rather hand it off than chip away at it yourself, custom web development is where I go into more detail on how I approach it.
FAQ
Do I need to upgrade to PHP 8.4 right away?
No, but you need to be on a supported version. PHP 8.3 and 8.4 both receive security updates. Anything older does not, and that’s the real risk.
Is static analysis worth the setup time on a messy codebase?
Yes, if you use a baseline. Run it at a low level, generate a baseline to ignore existing issues, and let it block new ones. You get value on day one without a rewrite.
Can I use these techniques in WordPress plugins?
Mostly. Enums, readonly properties, and typed arguments all work if you declare a minimum PHP version in your plugin header. Just check what your host actually runs before you ship.


