Global variables in WordPress

[WordPress logo][wordpress] In my critique of the code in WordPress I talked about the frequent use of global variables, something this I think it a bad practice. In response Owen Winkler writes that:

Global variables are used in WordPress in many places to make things simpler for a novice end-user to design. [...]

The point is that novice plugin and template designers shouldn’t have to worry about object-oriented syntax, and therefore lots of object members are put into the global scope.

It might be easy, but I still find this a poor way to pass information around in an application. The global variables obscure the relationship between functions. Take my example from the original rant, the mysql2date() function — it’s the second function (or third, depending on whether or not you expect Gettext to be active) in wp-includes/functions.php.

The first line in the body declares four global variables named $month, $weekday, $month_abbrev, and $weekday_abbrev. So this function is obviously somehow dependent on those variables being initialized. So the first question is: can I always call mysql2date()? Do I first have to call some initialization function, or is this guaranteed to be done for me when I want to use mysql2date() in some plugin or template?

It’s not such a big mystery to figure out where the variables come from: a quick grep reveals that they are defined in locale.php. But who takes care of including locale.php? Another grep, and one sees that this is done by wp-settings.php — I guess that file is always loaded, and thus one can be sure that $month and the other globals are indeed initialized before a call to mysql2date().

No wait! On close inspection of wp-settings.php one sees that locale.php is loaded after the active plugins are loaded, and after a call to do_action('plugins_loaded'). So does this mean that a

plugin that attaches itself to the plugins_loaded action cannot call mysql2date()? I believe it does!

In my installation I found no other references to the plugins_loaded action, and so this could explain why this bug wasn’t found earlier. But in any case — if the developers had taken the time to properly document this dependency between locale.php and functions.php, then the problem should never have occurred in the first place.

This potential bug wasn’t the reason for my initial worries about the use of global variables: I had been trying to get the Smart link plugin working. It uses a global $comments variable to know which comments to process, but the comments.php template doesn’t define it to be global — in fact the $comments variable is a local variable in the comments_template() function in comment-functions.php. This function includes comments.php, and thus $comments has the local scope in that file. (These scope rule games in PHP are just so annoying!)

I now see that the problem that triggered my anger against the global variables was in fact a use of a non-global variable, expecting it to be global. A bit ironic that I were to find a problem with a totally different function, mysql2date(), while initially being started by a mistake in a plugin…

7 Comments

  1. Owen:

    Only really two points of concern here:

    In what way would you suggest that not using global variables would have fixed the problem with mysql2date()? Since you seem to have tracked down the issue to thoroughly to know the answer to this question, why haven’t you written a patch for the specified bug?

    Your analysis of the failure of the plugins_loaded bug doesn’t mention that the plugin hook init is a better choice for the operation you’re trying to accomplish. plugins_loaded would let a plugin affect the locale settings, which would be impossible if the locale settings were loaded first. init occurs after the locale settings are set, and so manke much more sense for use in this case. Note that the call to sink init hooks is 23 lines down the file from the plugins_loaded hook.

    So who can be blamed if you don’t fully understand PHP’s scope rules or WordPress’ call order?

  2. Martin Geisler:

    The idea of my example was to point out that the use of global variables is dangerous because it leads to different parts of the code being connected in non-obvious ways.

    So whenever they are used, such connections must be prominently documented, and here — like in almost all other places in the WordPress code where I’ve looked — there were no comments in mysql2date() to indicate that locale.php must be loaded first.

    I haven’t written a patch because I only just traced through the code this morning before heading to [Uni][eth], and because I’m not sure that just moving things around a little in wp-settings.php would be enough — I might mess up some other code elements which share a weak link.

    The choice of mysql2date() was random, and I didn’t expect to find such a problem at first; I expected to use the function as an example of the kind of integrity checks that must be maintained when using global variables: have they been properly initialized. At first I thought that everything was fine, and that the variables were guaranteed to be loaded — but then I looked at the code again and realized that it wasn’t as bullit-proof as it appears.

    As for your last point, then I think it’s pretty easy to say who is to be blamed if I don’t fully understand the call order in WordPress: the developers who neglected to comment their code. I know how to both read code and how to debug code — but I much prefer to do the first. Having comments in the code is a great way to make it readable.

    About the scope rules of PHP, then I believe I can say that I indeed understand them. That’s not so hard afterall — they are documented in the manual.

  3. Philip:

    Martin,

    I read this entry a couple of months ago (searching for evidence why all-globals is bad), found it interesting, bookmarked it, and promptly forgot about it.

    Recently I started hacking around in WP a bit, and my reaction was very similar to yours. Trying to “make things easier” for newbies by using all globals is foolhardy. Sure, you’re saving them learning a bit about OO — but you’re also training them that this is a good thing. I recognize that blogs are meant to be more accessible/hackable to the non-technical than other apps. But I still believe that “dumbing down” by using poor coding practices is a bad trade-off with spillover effects outside the particular project at hand.

    WP 2 looks marginally better in this regard, but only marginally. I suppose I should search your blog and see if you’ve commented on it.

  4. Martin Geisler:

    Thanks for your comments, I’m glad to hear that I’m not all offtrack in my critique :-)

    And about WordPress 2: no need to search for my comments on it — I haven’t bothered to upgrade to WP 2 yet. And from what I’ve read about it around the net I’ll wait a bit more… (also because I have little time right now).

  5. Idetrorce:

    very interesting, but I don’t agree with you
    Idetrorce

  6. Joe Cascio:

    I have to agree with Martin. I just got into WordPress development, and frankly, I was shocked to see such a bad practice used in what is clearly one of the most frequently used APIs in web-development. Use of global variables really cannot be defended in any way, shape or form. It causes nearly invisible coupling between otherwise completely unrelated pieces of code. Pandering to the ignorance of non-programmers and newbies is no excuse. It only makes the code even more difficult to maintain or modify for them later on.

    During my tenure as Chief Technology Officer at a medical imaging software company, I let the programming staff know that using a global variable was cause for termination. That’s how seriously bad a practice it is.

    I won’t go into the details, but any global can be replaced by either passing a variable into a function, or in the case of a true singleton, a global function. Unfortunately, this will probably never happen because there is now so much code out there that relies on existing ones, which, by the way, is one of the real problems with globals. Once you start to use them, it’s very work intensive to get them back out. I’m surprised there hasn’t been more outrage from educated professionals on this subject.

  7. John Wells:

    This has been the biggest problem I have faced in writing the WP-United integration. When embedding multiple pieces of software, I naturally want to write OO, or at least, compartmentalised code, but WordPress relies too much on variables being in the global scope.

    As it is now, I end up having to use a painful combination of globalising long lists of variables, and preparing all code paths in functions and class members, but eval()ing it in the global scope.

    There are still some instances where I really do have to run the code in a function scope — it is just too swkward to bend the code into the global scope, but despite tracking WordPress’ globals with each iteration, it turns out that the majority of plugins are written assuming that they themselves are in the global scope.

    Then you have to clean up the mess they make afterwards.

    The problem is that none of this can be undone — with tens of thousands of plugins and themes all working on the assumption that they are in the global scope, nothing will ever be able to wrap WordPress 100% cleanly and reliably.

    WordPress in general has excellent, clean and easy to follow code, but this was a decision that has sent them down the wrong path. Forcing all plugins and themes into a function scope, while more painful initially, would have completely removed all of these issues.

    Right now I have even resorted to “compiling” plugins — parsing the files before they are executed, grepping all the “global xxx” definitions, and proactively making them global before they are defined. Huge pain, but necessary if I want wordpress to run at specific points in a third-party application — e.g. during user authentication.

Leave a comment