Tutorials, WordPress

Patching abandoned WordPress themes for PHP 8: the widget constructor fix

Laptop displaying a code editor with class hierarchy panel and method documentation tooltip — typical view when reading inherited legacy code (photo: DKomov / Pexels)

You upgrade an OpenLiteSpeed box from PHP 7.4 to PHP 8.2, the four sites running mainstream themes come up clean, and the fifth — running a 2013-era premium theme that hasn’t been touched by its author since 2018 — throws this on every page load:

Fatal error: Uncaught ArgumentCountError: Too few arguments to function
WP_Widget::__construct(), 0 passed in wp-includes/class-wp-widget-factory.php
on line 62 and at least 2 expected
in wp-includes/class-wp-widget.php on line 163

This is the most common PHP 8 fatal in older WordPress themes. The fix is mechanical, takes about three lines of edits per widget, and once you’ve done it once you can do the next one in 90 seconds.

What changed

PHP 4 let you define a constructor by giving a method the same name as its class:

class MyWidget extends WP_Widget {
    function MyWidget() {
        $this->WP_Widget('my_widget', 'My Widget', $widget_ops, $control_ops);
    }
}

That’s the pattern almost every theme widget written before 2015 uses. PHP 7 deprecated it with a warning. PHP 8 removed it entirely — same-named methods are now just regular methods, and the class has no constructor at all. So when WordPress’s WP_Widget_Factory::register() calls new MyWidget(), it falls through to the parent WP_Widget::__construct() with zero arguments, which now requires $id_base and $name. Fatal error.

The same applies to the body of that constructor. $this->WP_Widget(...) is the PHP 4 way to call the parent constructor. PHP 8 doesn’t recognize that pattern either — you’d get an “undefined method” error if execution ever reached that line.

The patch

Two textual changes per widget:

  1. Rename the class-named method to __construct.
  2. Replace $this->WP_Widget(...) with parent::__construct(...) — same arguments, just routed through the modern parent-call mechanism.

So this:

function tz_FLICKR_Widget() {
    $widget_ops = array(
        'classname'   => 'tz_flickr_widget',
        'description' => __('A widget that displays your Flickr photos.', 'framework'),
    );
    $control_ops = array('width' => 300, 'height' => 350, 'id_base' => 'tz_flickr_widget');
    $this->WP_Widget('tz_flickr_widget', __('Custom Flickr Photos', 'framework'),
                     $widget_ops, $control_ops);
}

Becomes this:

function __construct() {
    $widget_ops = array(
        'classname'   => 'tz_flickr_widget',
        'description' => __('A widget that displays your Flickr photos.', 'framework'),
    );
    $control_ops = array('width' => 300, 'height' => 350, 'id_base' => 'tz_flickr_widget');
    parent::__construct('tz_flickr_widget', __('Custom Flickr Photos', 'framework'),
                        $widget_ops, $control_ops);
}

That’s the entire fix. Behavior is identical — modern PHP just routes the same call through the canonical mechanism.

Find every instance in a theme in one shot

Most themes have one or two widgets, but if the theme is feature-heavy (the kind that ships its own Flickr, Twitter, Vimeo, Recent Comments, Custom Posts widgets) you’ll want a list before you start editing:

# Where does this theme have widget classes?
grep -rln "extends WP_Widget" /usr/local/lsws/site/html/wp-content/themes/your-theme/

# For each result, show the constructor line and the parent call
for f in $(grep -rl "extends WP_Widget" \
            /usr/local/lsws/site/html/wp-content/themes/your-theme/); do
  echo "--- $f ---"
  grep -nE "class\s+\w+\s+extends|function\s+\w+\s*\(\s*\)\s*\{|\$this->WP_Widget" "$f"
done

The output gives you the class name and the line of the legacy constructor for every widget in one pass. Edit each, save, refresh the site.

Other PHP 8 fatals you’ll hit in old themes

The widget-constructor issue is the headliner. Once that’s resolved you’ll uncover the next layer:

  • create_function() — removed in PHP 8.0. Replace with an anonymous function() use ($vars) { ... }. Most themes use it once or twice, often for sorting callbacks. Mechanical.
  • each() — removed in PHP 8.0. Replace the while (list($k, $v) = each($arr)) pattern with a foreach ($arr as $k => $v).
  • Optional parameters before required ones. PHP 8.1 emits a deprecation; PHP 8.4 will likely make it fatal. Re-order the parameters or default the required one explicitly.
  • ${var} string interpolation — deprecated in 8.2, fatal eventually. Switch to {$var}. Search-and-replace job.

Most of these are deprecations, not fatals, on 8.2 — but if you upgrade WordPress’s WP_DEBUG flag at the same time as the PHP version, the deprecations will spam the screen and look like errors. Disable WP_DEBUG_DISPLAY until the patches land.

Should you patch or migrate?

If the theme is custom-built, has a working maintenance handoff, or has a few thousand lines of theme code that “Just Work” once the constructors are fixed — patch it. The patches don’t require theme-author understanding; they’re mechanical translations of obsolete syntax.

If the theme is from a marketplace, the author has been silent for 5+ years, and the codebase reads like 2008-era WordPress with embedded TimThumb and PHP-included CSS — migrate. Those themes typically also have unpatched XSS holes. Compatibility surgery only buys you another year before the next breaking PHP release surfaces another set of issues.

For everything in between, the constructor patch is a 5-minute fix that buys you another major-version PHP cycle. Worth it.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.