Skip to content
← Posts

register_globals and the whole catastrophe

I have written PHP since 1999, on version 4. Twenty-seven years of watching confident practice become embarrassing is a specific kind of training.

6 min read

I've written PHP since 1999, on version 4. I am currently on PHP 8.4 and Laravel 13, and I have been on Laravel since the version 4 beta. Twenty-seven years of watching confident practice become embarrassing is a specific kind of training. It left me less interested in certainty and more interested in the judgment behind a decision. I have already said that. This post is the receipt.

Code often outlives the confidence with which it was written. After shipping through several generations of PHP best practice that later became a finding, I trust current consensus less than I used to. Not because consensus is always wrong. Because I have watched it be locally right, then expensive, then a joke, without the people inside it noticing the cost had moved.

The catastrophe, itemized

register_globals was on by default in PHP before 4.2. Incoming request data became global variables in the script. It felt like a gift. You named a form field and the value was just there. magic_quotes_gpc was the matching courtesy. The language would escape GET, POST, and cookie data for you so you would not have to think about it.

I wrote production PHP under those defaults. I am not going to pretend I was the person in the room explaining why they were a trap. They were the field's confident practice. The reasoning was locally correct. PHP was trying to be the language a person could learn on cheap hosting without a computer science degree. Convenience was the product. The constraint was developer time and a beginner audience. If the language did not meet you halfway, you used something else.

Then the consensus flipped. register_globals went off by default in 4.2, was deprecated later, and was removed in 5.4. Magic quotes went with it in 5.4. Code that had been normal became a vulnerability report. Not because we had been stupid. Because the cost of being sloppy had become visible, and the language stopped carrying the old habit.

I watched that flip as a working PHP 4 developer, not as a historian. The embarrassing part is not that the defaults were wrong in hindsight. The embarrassing part is how complete they felt at the time. You did not experience them as a gamble. You experienced them as how PHP worked.

The pattern that repeats

A practice is correct under a cost structure. The cost structure moves. The practice becomes a joke, and the people who still remember why it was correct sound like they are making excuses.

I have now watched this happen more than once. I will not invent a dated confession I cannot document. I did not write a blog post defending magic quotes. I did ship production under those defaults, and under the frameworks that came after, and I watched the room change its mind without always noticing it had.

Laravel is the long example I actually have. I have been on it since the version 4 beta through 13. That is a lot of years of "this is how we do it" turning into "we do not do it that way anymore," without throwing the career out and starting over. The never-rewrite rule sat underneath a lot of that. Don't start over. Migrate. Keep the system that is making money. The rule was economic, not moral. A rewrite cost a year of salaries and usually failed. "Never rewrite" was a cost structure wearing a principle's clothes.

We did rewrite once when I was in the chair. At Life Happens I led a ground-up rebuild of the agent-facing Laravel SaaS platform. It was driven by SEC compliance as client organizations got larger, including an event-sourced architecture that could reconstruct the complete state of any agent's account at any point in time, built so that FINRA requirements were covered by construction. That was a hard deadline of a different kind. Not taste. A regulator. The never-rewrite rule bent when the constraint was reconstructability, not elegance.

That is the pattern again. The rule held until the underlying cost changed. Then it looked obvious that we should have done the thing the rule forbade. People who only saw the second cost structure will think the first group was timid. People who only saw the first will think the second group was reckless. Both groups are describing a price.

Legacy code is mostly that record. Code that outlived the assumptions it was written under. I have been writing PHP since version 4, so I have made a lot of it. Confidence is not a property of the code. It is a property of the moment.

Applying it to now

A lot of current certainty is cost-dependent rather than principled. Generated code is cheap, so we talk as if volume is free and verification will catch up. "Don't rewrite" is still recited as morals. I think both will look different when the costs move again. We are stocking repositories with comprehension debt: code that exists in git and not in anyone's head. Technical debt is code you understand and chose not to fix. Comprehension debt is code nobody on the team can explain, including the name on the commit. Paying it down still requires someone to read at human speed. Generation did not change that speed.

I do not know which current practice will be the next register_globals. If I named one with confidence I would already be repeating the original mistake. The candidates are obvious enough to be uninteresting: trusting tests we did not read, treating a generated diff as reviewed because it compiled, keeping the never-rewrite rule after the rewrite got cheaper, throwing away the junior work that used to produce seniors. I can list those without knowing which one will be the finding in a decade. Listing them is not the same as having judged them.

What will I be embarrassed about in 2038? I don't know. If I could see it from here I would already be hedging. Posing a confident prediction would be a costume of the same error. The honest position is that some of what I am sure about this year is cost-dependent, and I will not see which part until the cost moves.

Why this is worth saying out loud

The field is currently full of people who have only seen one cost structure. They are not wrong about the structure they are in. They are under-equipped for the next flip, because a flip does not announce itself as a debate. It announces itself as a default that later gets removed.

Longevity is not wisdom. It is a dataset. Twenty-seven years of PHP is not an argument that I am right now. It is a record of having been right in a way that later became indefensible, in public, in production, with the language itself as the witness. I would like that record to make me slower to treat the current defaults as physics.

I am still figuring out which of my current certainties are the same kind of locally correct mistake. I assume at least one of them is. I will not know which one until it is already a little late.

Related: Legacy code, Comprehension debt, Hard deadlines