Hosting Autopsy / The PHP upgrade that broke the checkout

The PHP upgrade that broke the checkout

HOSTING AUTOPSY

6 min read · 1,324 words

This is a composite case written by the editors. It is built from patterns that come up often in support work and is not the account of a particular named person or company.

The owner of a small online shop selling handmade ceramics received an email from her host: PHP 7.4 had reached end of life and would be retired from the servers at the end of the month. She opened the control panel, found the PHP version selector, changed 7.4 to 8.2, reloaded the homepage and saw the usual photographs. She went to bed pleased with herself, and with some justification, since the upgrade is a sound thing to do.

The homepage did load. The checkout did not, and nobody told her for two days.

What broke, and why nobody saw it

The checkout depended on an older payment extension, written for a time when PHP 7.x was current. Somewhere in its code was a call to a function that PHP 8 had removed. When a customer clicked Pay, the extension called it, PHP found nothing by that name, and the request stopped with a fatal error.

A fatal error means PHP gives up on the request. What the visitor sees depends on a setting called display_errors. On a live site it should be off, so that visitors never see file paths and code fragments, which is correct. The consequence is that the browser receives an empty page, usually with a status of 500, and the details go to the error log. The customers saw a blank page after clicking Pay. Some tried again, some emailed, and most gave up and left.

PHP Fatal error:  Uncaught Error: Call to undefined function create_function()
in /home/shop/public_html/wp-content/plugins/legacy-pay/gateway.php:212

The line above is the kind of entry that was sitting in the log for two days. It is not mysterious. It names the function, the file and the line.

The timeline

Day 1Switch to 8.2Homepage ok Card checkout fails, unseen Day 3Owner spotsorder drop Day 3Roll backto 7.4 Day 8New extension,tested on 8.2
The gap between the upgrade and the discovery is where the orders were lost.

On day one the upgrade was made late in the evening. On day two orders trickled in at about a third of the usual rate, which on a quiet midweek day looked like nothing worse than a slow day. The orders that did arrive came from customers using a bank-transfer option that did not touch the old extension, so the shop never looked empty. On day three a customer wrote to say she had tried three times to pay and got a white page. The owner tried it herself, and the blank page appeared on her own screen.

Wrong turns

Her first guess was that the payment provider had a problem. She logged in to the provider's dashboard, which showed no failed payments, because no request had ever reached it. The provider's status page showed no incident. The silence from the provider was the clue, though it took a while to read it that way.

The second guess was a caching issue, and she cleared the shop's cache and the browser's cache, to no effect. A phone call to the host's support desk produced the useful question: had anything changed recently? The PHP version change was mentioned almost as an afterthought. Support opened the error log, found the fatal error within a minute and read it out.

The lesson in the sequence is that the log had the answer from the first failed payment. Nobody had looked, because the screen showed nothing and the habit of reading logs was not there.

The way out

The host allowed her to switch back to 7.4 for a limited time while the end-of-life date was still some weeks away. That brought the checkout back within a minute. She then had a deadline instead of an emergency.

The repair had three steps. First, she identified the extension and found that its author had stopped releasing updates years earlier. Second, she chose a maintained replacement for the payment gateway. Third, she copied the whole shop to a staging address, switched that copy to 8.2, installed the new extension and ran test orders through the payment provider's sandbox mode. Only after a full test purchase, including a refund and the confirmation email, did she repeat the change on the live shop.

The total cost was two days of reduced orders, some apologetic emails, and a good amount of her week.

Why the front page proves so little

A homepage touches a small part of the code: templates, a theme, a few widgets. A checkout touches almost everything at once, including the cart session, the shipping calculation, the tax rules, the payment gateway, the order database and the outgoing email. Each of those is a place where an old function call may be hiding.

PageCode it exercisesShows a PHP 8 problem?
HomepageTheme, a few queriesRarely
Product pageTheme, gallery, stock checkSometimes
CartSessions, pricing rulesOften
Checkout and paymentGateway, orders, emailMost often
Admin order screenReports, exportsOften

PHP 8 is stricter than 7.4 in several ways. Some functions were removed, some behaviours that used to produce a warning now throw an error, and mixing types in comparisons gives different results. Code that has been maintained handles all of this. Code that has not been touched since 2019 may not.

Commands worth running

Before changing the version, find out what is installed and read the compatibility notes for each plugin or extension. Then follow a routine on a staging copy:

  1. Create the copy and switch only the copy to the new PHP version.
  2. Turn on error logging, with display off, and note where the log lives (often error_log in the site folder or under ~/logs/).
  3. Walk the full purchase: add to cart, apply a discount code, check out, pay with each method, receive the email.
  4. Open the log and read it, even if everything looked fine.
  5. Only then change the live site, at a quiet time, and repeat the test purchase.
tail -n 50 ~/logs/php_error.log
grep -i "fatal" ~/logs/php_error.log | tail

If your host allows several PHP versions side by side, switching back is a one-minute fix, but do not rely on the old version staying available. The troubleshooting guide has more on reading logs.

30Before 10Two days after 30After repair Orders per day (illustrative)
Illustrative orders per day: the drop is steep enough to notice only if someone is looking at the numbers.

What the host could and could not do

The host's support team had done what hosts do: announced the end of life months ahead, sent two reminders and kept the older version available until the deadline. What they could not do was test her particular shop. A host sees thousands of sites, and the only code it knows is the code in front of it when someone asks.

That division of responsibility is worth stating plainly. The host owns the platform: the PHP versions, the server, the security patches to the operating system. The site owner owns what runs on top: the theme, the plugins, the extensions and whether they work together. A version switch in the control panel is a one-click action, but its consequences belong to the person who clicked. Good hosts will help read a log, and some offer a staging tool, but the decision to test is yours.

She now keeps a one-page note beside the shop's admin password: the PHP version, the list of active extensions with the date each was last updated, and the exact steps of a test purchase. It takes ten minutes to follow, and she follows it after every significant change.

What would have caught it

PreviousThe sitemap that listed forty thousand junk URLsNextThe CDN that served one customer's basket to another

More from Hosting Autopsy

Autopsy

The HSTS setting that broke the intranet

After a security review, an administrator enabled HSTS on the company's main domain with the option that...

Autopsy

The mailbox that filled up and bounced the best client

A consultant used a single mailbox on her hosting plan for several years and never deleted anything....

Autopsy

The auto-reply that answered itself eleven thousand times

A company set up an out-of-office reply on a shared mailbox. Separately, a helpdesk system sent an automatic...