WordPress and it’s Broken Core

When I develop with PHP (7.2+) I develop with strict mode and xdebug enabled. I do this on purpose because notices and warnings are both important to your quality of code. Constantly I am running into one issue with WordPress core that is quite frankly annoying.

This bug is also affecting WPEngine users a lot.

Warning: Parameter 1 to wp_default_scripts() expected to be a reference, value given in /[..]/your-directory/wp-includes/class-wp-hook.php on line 286
Warning: Parameter 1 to wp_default_packages() expected to be a reference, value given in /[..]/your-directory/wp-includes/class-wp-hook.php on line 286
Warning: Parameter 1 to wp_default_styles() expected to be a reference, value given in /[..]/your-directory/wp-includes/class-wp-hook.php on line 286

The Error

The Error is pretty self evident that somewhere in the core wp_default_scripts, wp_default_packages, and wp_default_styles are all being called incorrectly. But where? and why?

Diving into class-wp-hook.php we’ll find that this is error is emitted from inside the apply_filters code. Unfortunately for us, the apply_filters code uses a lot of magic in the form of call_user_func_array – a built in PHP function that calls a user’s function with an array of arguments. Blammo. That should already be setting off your alarms. If wp_default_scripts expects a reference passing an array of values is definitely going to blow it up.

The Investigations

You’ve probably found yourself here by googling the above error message(s). You’ve probably already sifted through a few forum posts that tell you to “ignore errors” or “fix your theme” or “we have no idea!”

I’m going to start with ignoring warnings is a terrible suggestion. It always has been. Fix your code, increase your code quality. But what happens when your code isn’t the problem?

Unfortunately, a bug in core code takes time to fix. So you can report it (it’s a 5 month old bug, but the problems show up on the internet long before that). You can wait. And you can hack your core files to get rid of the error yourself.

Disclaimer: hacking your core files will void your warranty.

The Actual Problem

Since I’ve been tracking down this problem, I’ve discovered that it’s not super simple. The problem is how parts of WordPress core handle the wp_scripts object. If you write themes you’re probably aware of wp_equeue_script (and it’s friends). If you look at wp_enqueue_script you’ll notice the first thing it does is obtains a global object of $wp_scripts (I also despise global variables, but that’s another issue) and if this object doesn’t exist, it creates a new one. Easy enough.

The part that breaks is when the filter gets run for WordPress’ default filters. In there it adds add_action( 'wp_default_scripts', 'wp_default_scripts' ); and as one would expect, it tries it’s hardest to set up the default scripts. But that’s when it breaks. wp_default_scripts expects the reference to the global $wp_scripts to be passed. It’s not. It’s passed as a value and that breaks.

It would be a lot easier (and likely better) if it wasn’t passing the instance wp scripts at all and used the global wp_scripts() function, the same way wp_enqueue_script does. It would be even better if WP_Scripts was an actual singleton since that’s what they’re attempting to do with the global variable. But what the heck do I know?

The Hack

To fix this, jump into your core files and find class-wp-hook.php, roll on down to line apply_filters and make the function look like this:

public function apply_filters($value, $args)
{
    if (!$this->callbacks) {
        return $value;
    }

    $nesting_level = $this->nesting_level++;

    $this->iterations[$nesting_level] = array_keys($this->callbacks);
    $num_args = count($args);

    do {
        $this->current_priority[$nesting_level] = $priority = current($this->iterations[$nesting_level]);

        foreach ($this->callbacks[$priority] as $the_) {
            if (!$this->doing_action) {
                $args[0] = $value;
            }

            // Avoid the array_slice if possible.
            if ($the_['accepted_args'] == 0) {
                $value = call_user_func_array($the_['function'], array());
            } elseif ($the_['accepted_args'] >= $num_args) {
                $special_cases = array('wp_default_scripts', 'wp_default_packages', 'wp_default_styles');
                if (in_array($the_['function'], $special_cases)) {
                    $arg = $args[0];
                    $args = array(&$arg);
                }
                $value = call_user_func_array($the_['function'], $args);
            } else {
                $value = call_user_func_array($the_['function'], array_slice($args, 0, (int)$the_['accepted_args']));
            }
        }
    } while (false !== next($this->iterations[$nesting_level]));

    unset($this->iterations[$nesting_level]);
    unset($this->current_priority[$nesting_level]);

    $this->nesting_level--;

    return $value;
}

This will get rid of the warnings by passing the arguments to the default functions as references. Of course, if you upgrade PHP this hack will get overwritten and you’ll have to do it again if the fix isn’t actually put in the core.

Hope that helps you out and thanks for visiting!

Published by Darryl Clarke

random text goes here, i guess