Skip to content
← All posts
phpperformancemigrationarchitecture

The Evolution of PHP: From Personal Home Pages to Modern Enterprise

3 min read

Fifteen years shipping PHP, including the PHP 7 migration. What the Zend Engine rewrite actually did to get 2x throughput at half the memory, and what maintaining backward compatibility at that scale costs.

I wrote PHP professionally for about fifteen years, starting in the early 2000s when it was genuinely a mess. No type hints, no namespaces, function names that couldn’t agree on argument order within the same standard library.

It also let you drop a file on shared hosting and have a working site in minutes, which is why it ate the web. Low friction beats elegance at adoption time, every time.

What I find worth retelling isn’t that PHP improved. It’s how it improved. The PHP 7 engine rewrite is one of the better case studies in optimizing a language runtime without breaking the world.

What PHP 7 actually did

The headline was roughly 2x throughput at half the memory versus 5.6. That’s a big enough jump that people assumed marketing. It wasn’t, and the mechanism generalizes past PHP.

The zval got restructured. PHP’s fundamental value struct was 24 bytes and heap-allocated for essentially everything, including integers. PHP 7 shrank it to 16 bytes and made simple scalars live inline rather than behind a pointer. When your interpreter allocates a value for every operation, removing an allocation from the hot path compounds across the entire runtime.

Hashtables got packed. PHP arrays are hashtables, and PHP code uses arrays for everything. The old implementation stored buckets as a linked structure with pointer chasing on every access. PHP 7 packs entries into a contiguous block, so iterating an array walks memory linearly instead of following pointers all over the heap. On modern hardware that’s not a small constant-factor win, it’s the difference between hitting cache and missing it on every element.

Both of those are the same lesson: the biggest wins in a mature runtime usually come from memory layout, not from algorithmic cleverness. That’s held true for every performance problem I’ve worked on since, including the JavaScript numerics work where the fix was also fundamentally about layout.

The part that impressed me more than the performance

They shipped it without breaking the ecosystem.

A 2x runtime improvement is an engineering achievement. Delivering it to a language running a double-digit percentage of the web, where most of the code is unmaintained and nobody can be asked to migrate, is a different achievement and a harder one.

I ran several PHP 5 to 7 migrations. The breaking changes were real but they were surgically chosen: the ones they kept were the ones where the old behavior was indefensible, and they left almost everything else alone. That restraint is what made the upgrade path survivable at scale.

Having done migrations in ecosystems that made the opposite choice, I have a lot of respect for it. The temptation when you’re rewriting an engine is to fix everything you’ve hated for a decade. Resisting that is what separates a language that survives from one that forks its community.

PHP 8 and where I actually landed

JIT, union types, named arguments, attributes. The type system got strong enough to write genuinely safe code without leaving the language, and Laravel and Symfony matured into frameworks that hold their own against anything in Node or Python.

I moved my primary stack to Node around 2014, for the architectural reasons I wrote about separately, mainly that a long-lived process lets you hold state that PHP’s process-per-request model can’t. That was the right call for what I build now and it isn’t a verdict on the language.

Developers still dismiss PHP based on 2010. That’s the same error as judging JavaScript by pre-ES6. It stopped being true a long time ago, and the engineering that made it stop being true is worth studying regardless of whether you ever write another line of it.