Every time the subject comes up, someone says the same thing: "right, PHP?" — with that small drop in tone at the end.
I get it. I have heard the jokes and most of them were fair. The thing is, they were fair in 2012.
The criticism I hear today is not about the language I use. It is about a language that no longer exists.
What has actually changed
I do not mean "it improved". I mean the things I used to envy in other languages, it now has.
Enums since 8.1. Readonly classes since 8.2. Typed class constants since 8.3. Constructor promotion, named arguments, match, union and intersection types, first-class callables — all from 8.0 onward.
8.4 brought two that genuinely changed how I write objects.
Property hooks — computed properties without getter/setter boilerplate:
class User
{
public string $fullName {
get => $this->first . ' ' . $this->last;
}
public function __construct(
private string $first,
private string $last,
) {}
}
Asymmetric visibility — public to read, private to write, in one line:
class Order
{
public private(set) string $status = 'pending';
}
Together, these two mean you can finally write real value objects without forty lines of ceremony.
8.5, released in November 2025, brought the pipe operator:
$slug = $title
|> trim(...)
|> strtolower(...)
|> fn($s) => str_replace(' ', '-', $s);
Alongside it: clone with for changing properties as you clone, array_first() and array_last(), stack traces on fatal errors, and #[\NoDiscard] for functions it makes no sense to call while ignoring the result.
And in seven weeks, on 19 November, 8.6 arrives with partial function application — the RFC passed 33 votes to none.
The three accusations, one by one
"It has no types"
Native types have existed since 7.0 and have become fairly strict. But the real answer is not the language — it is PHPStan.
PHPStan 2.0 reached level 10, which treats mixed with full strictness. It understands generics, union and intersection types, conditional return types, list types.
I will be honest, because the alternative is advertising: level 10 is where most teams give up. The practical sweet spot is 8. And generics live in docblocks, not in the language — the engine does not see them, only the analyzer does.
It is not TypeScript. But it is close enough to catch entire classes of bugs that tests do not.
"It is slow"
That was true in the 5.x era. 8.x is a different thing, and for the kind of work I do the bottleneck is almost always the database, not the language.
"It dies on every request"
This one is the most interesting, because it was the biggest architectural weakness: every request rebuilt everything from zero.
That is no longer true, unless you want it to be. With worker mode — FrankenPHP, RoadRunner, Swoole — the application boots once and serves thousands of requests. Symfony does not rebuild the container, the metadata and the listeners on every click.
In published Symfony benchmarks, the difference runs roughly:
| Setup | Requests/sec | Latency |
|---|---|---|
| Nginx + PHP-FPM | ~1,240 | 45 ms |
| FrankenPHP worker mode | ~3,850 | 8 ms |
And the honest footnote: worker mode is not free. In long-lived workers, reference cycles accumulate and memory grows linearly if you do not watch it. Suddenly you have to think about things PHP had trained you never to think about.
What has not been solved
So this does not turn into a brochure:
- Generics still live in comments. They work, but only because everyone agreed to pretend they exist.
- The standard library is still inconsistent. Argument order, naming, return values — thirty years of history do not get erased.
- The average PHP code out there is bad. That is not the language's problem, but it becomes yours the day you inherit something.
- You do not share types with the frontend. If that matters to you, TypeScript across the stack is a clean win.
Why I choose it
Not out of habit, and not because it is "boring and safe".
I choose it because the distance from "I thought of it" to "it is running in production" is the shortest I know. I run my own servers. I upload and it works — no build step, no process manager, no deployment ritual.
When you maintain ten sites at once, every piece of infrastructure you do not have to think about is pure profit.
And there is something else, more boring and more important: the code I wrote three years ago still runs. PHP breaks backward compatibility carefully and warns years in advance. I have never woken up to find the ecosystem decided something different while I was asleep.
The conclusion
I am not telling you PHP is the best language. There is no such thing, and anyone claiming otherwise is selling something.
I am telling you that if your opinion of PHP formed before 2020, you are talking about something else — and it is worth a second look before you dismiss it.
The best technology is usually the one you know well enough to make behave. For me that is this one. If for you it is something else, you are probably right too — as long as you chose it, rather than inherited it.
Sources: the PHP 8.5 release announcement, the partial function application RFC (PHP 8.6), PHPStan 2.0 documentation, and published FrankenPHP/PHP-FPM benchmarks on Symfony. Performance numbers depend entirely on the workload — measure your own. The elePHPant is a design by Vincent Pontier, available under GPLv2+.
