Writing Modern PHP in 2026: A Practical Guide for Real Projects

Write modern PHP by starting every file with strict types, using readonly properties and constructor promotion for value objects, and leaning on typed signatures so the engine catches mistakes early. PHP 8.5 in 2026 is fast, strongly typed, and fully capable of clean, maintainable production code.
Part of the complete web development guide.
On this page
- Start with strict types and stop guessing
- Use readonly properties and constructor promotion
- Enums instead of magic strings
- Let exceptions be exceptions
- Stop recompiling the same work
- Profile before you optimize anything
- Testing you will actually keep up
- A short checklist I run on every codebase
- FAQ
- Is PHP still worth learning in 2026?
- What PHP version should I be running?
- Do I need a framework like Laravel or Symfony?
PHP has been declared dead roughly every year since I started writing it, and yet here we are in 2026 with PHP 8.5 shipping, strong typing everywhere, and a toolchain that rivals anything else on the web. The problem was never the language. It was the pile of copy-pasted, untyped, error-suppressing code that gave it a bad name. This is how I write PHP now.

Start with strict types and stop guessing
If you take one thing from this article, make it this. Every file starts with declare(strict_types=1);. Without it, PHP quietly coerces values and you spend a Friday night hunting a bug that a type error would have caught in two seconds.
<?php
declare(strict_types=1);
function formatPrice(int $cents, string $currency = 'USD'): string
{
return number_format($cents / 100, 2) . ' ' . $currency;
}
formatPrice('1200'); // TypeError, and that is the point
That TypeError is not an annoyance. It is the language doing your code review for you. Turn it on and leave it on.

Use readonly properties and constructor promotion
Value objects used to be twenty lines of boilerplate. Now they are three. If a class holds data that should not change after construction, mark it readonly and let the engine enforce it.
<?php
declare(strict_types=1);
final class InvoiceLine
{
public function __construct(
public readonly string $sku,
public readonly int $quantity,
public readonly int $unitPriceCents,
) {}
public function subtotalCents(): int
{
return $this->quantity * $this->unitPriceCents;
}
}
Immutable by default means fewer “why did this value change” bugs. The only code that can change it is code you wrote on purpose.
Enums instead of magic strings
Order status, user roles, payment states. These were string constants for a decade and they caused endless typos. PHP enums give you a real type with real cases, and you can attach behavior.
<?php
declare(strict_types=1);
enum OrderStatus: string
{
case Pending = 'pending';
case Paid = 'paid';
case Shipped = 'shipped';
case Cancelled = 'cancelled';
public function isFinal(): bool
{
return match ($this) {
self::Shipped, self::Cancelled => true,
default => false,
};
}
}
Now a function that takes OrderStatus cannot be handed "payed". The bug is impossible before it is written.
Let exceptions be exceptions
The @ error suppression operator is a red flag in any codebase. So is a function that returns false on failure with no explanation. Throw a typed exception and let something upstream decide what to do. The match expression is also a clean replacement for long switch blocks.
<?php
declare(strict_types=1);
final class PaymentFailed extends RuntimeException {}
function charge(int $cents): string
{
if ($cents <= 0) {
throw new PaymentFailed('Charge amount must be positive.');
}
return match (true) {
$cents < 500 => 'micro',
$cents < 50000 => 'standard',
default => 'large',
};
}
Stop recompiling the same work
Performance in PHP is usually about avoiding repeated work, not micro-tuning loops. Two things I check on almost every project I touch:
- OPcache. In production, enable it, set a sane
opcache.memory_consumption, and setopcache.validate_timestamps=0so PHP stops checking file mtimes on every request. Deploy and reload. - Autoloading. Run
composer dump-autoload --optimize --classmap-authoritativeon deploy. It builds a class map so the autoloader does not hit the filesystem on every class load.
Neither of those is clever. Both routinely cut response times on real sites more than any code rewrite.
Profile before you optimize anything
I have watched developers rewrite a perfectly fine function because it “looked slow.” Measure first. Xdebug’s profiler or a sampling profiler will tell you where the time actually goes. Nine times out of ten it is a database query in a loop, not the loop itself.
<?php
// Bad: N+1 queries hiding in plain sight
foreach ($orders as $order) {
$customer = $db->query('SELECT * FROM customers WHERE id = ?', [$order->customerId]);
}
// Better: one query, then map in memory
$ids = array_unique(array_column($orders, 'customerId'));
$customers = $db->query(
'SELECT * FROM customers WHERE id IN (' . implode(',', array_fill(0, count($ids), '?')) . ')',
$ids
);
That single change is worth more than a week of chasing milliseconds inside a foreach.
Testing you will actually keep up
Pest has become the default for a lot of new projects because the syntax is pleasant enough that people keep writing tests. Whatever you pick, the goal is the same: cover the logic that would hurt if it broke. Payment math, permission checks, anything with a date. If you are building a site for a business where downtime costs money, tests are cheaper than apologies. That is true whether the work is a small local project or larger custom web development for a company that depends on it.
A short checklist I run on every codebase
declare(strict_types=1);in every file- Typed properties and return types, no exceptions
- Enums for any fixed set of values
- Exceptions over silent
false - OPcache and optimized autoloading in production
- Profile before optimizing
- Tests on the code that would hurt if it broke
None of this is exotic. It is just the difference between code that survives a year of changes and code that gets rewritten every spring.
FAQ
Is PHP still worth learning in 2026?
Yes. It powers a huge share of the web, PHP 8.5 is fast and well-typed, and there is more paid work in it than most newer languages. The demand is for developers who write it well, not developers who avoid it.
What PHP version should I be running?
PHP 8.3 at minimum, ideally 8.4 or 8.5. Every version below 8.1 is end of life and no longer getting security patches, which is a real risk if the site handles any customer data.
Do I need a framework like Laravel or Symfony?
For anything with logins, payments, or an admin panel, yes. A framework handles routing, security, and the boring parts so you can focus on the actual product. For a small script or a single endpoint, plain PHP with Composer is fine.


