A Crash-course in PHP Namespaces for WordPress Developers

Way back in 2009, PHP 5.3 was released to the world and with it brought support for PHP namespaces — a way of easily separating your code from other developers’ code, which has since become the de facto way of encapsulating functionality across the PHP ecosystem.

With namespaces, multiple packages could use the same class and function names without conflict, because each one would operate in their own PHP namespaces:

Since that code lives within the SteveGrunwell\PackageA namespace (as declared at the top of the file), I could use the code seamlessly next to:

While both packages define a do_something() function, the functions from Package A and Package B exist in different PHP namespaces (SteveGrunwell\PackageA and SteveGrunwell\PackageB, respectively).

What’s in a name(space)?

Conventionally, PHP namespaces will follow the pattern of {vendor}\{package}, such as SteveGrunwell\PackageA in the examples above. If you’re working as part of a development or product team, you might also consider nesting namespaces (for example, some things I’ll build as an engineer on the Managed WordPress (MWP) product at Liquid Web might live under the LiquidWeb\MWP\{package} PHP namespace.

Nesting namespaces can be a really useful feature, especially when you’re dealing with lots of similar functionality. For example, my Schemify plugin uses PHP namespaces (and autoloading, which is a topic all in itself). Here’s an excerpt from the Schemify\Schemas\CreativeWork class:

Since I wanted to separate the individual schema definitions from the rest of the plugin code, I put them in the Schemify\Schemas namespace (Schemify doesn’t currently use a vendor namespace, so the main plugin code is in Schemify instead of SteveGrunwell\Schemify).

Working with functions defined in PHP namespaces

To call these functions from within the same namespace, we can simply reference the function by name; if I added a do_something_else() function within the SteveGrunwell\PackageA namespace, I can simply call do_something() and it’s implied that I’m referencing SteveGrunwell\PackageA\do_something().

If we need to call functions in different namespaces, we can do it in a few different ways:

First, we can reference the function name prefixed with the full PHP namespace

That’s probably my least favorite way to use namespaces, but is perfectly valid (especially if you’re only referencing the function from Package A once or twice.

Next, we can import code from other PHP namespaces into the current namespace via the use keyword:

Finally, we can import the specific functions we need into the current namespace via use function (note: this is only available in PHP 5.6 or newer):

Each of these approaches is valid and has good use-cases, producing the same result.

Classes and PHP namespaces

Where PHP namespaces become really handy are when we’re dealing with object-oriented programming (OOP). Instead of dealing with importing specific functions, we can tell PHP “when I reference a given class name, I’m referring to this specific class.”

Let’s say I’m writing my own WP-CLI command, which I’ll put in the SteveGrunwell\MyCommand namespace:

If I were to try to load my command just like that, PHP would give me an error to the effect of “Class SteveGrunwell\MyCommand\WP_CLI_Command does not exist”, because the WP_CLI_Command class is defined in the global namespace (in other words, is not namespaced).

I can solve this by adding a single import to my file:

With that use statement in place, PHP will now understand that SteveGrunwell\MyCommand\Command extends WP_CLI_Command, which lives in the global PHP namespace.

Using PHP namespaces in WordPress themes and plugins

Now that we have a basic understanding of what PHP namespaces are and how to use them, let’s take a look at namespaces from a WordPress perspective:

WordPress development through the ages

I’ve written a bit about this before, but in the early days of WordPress development, plugin developers might write a function that looks like this:

However, the developer might find that another theme or plugin — or even WordPress core itself — introduces a get_recent_posts() function, which then causes the site to break because PHP has received two separate declarations of a get_recent_posts() function.

To get around this, developers started prefixing their function names with their theme/plugin slug or another unique identifier; get_recent_posts() might become myplugin_get_recent_posts(). Suddenly, the chances of a name collision dropped, but function names would get longer and longer. Developers with multiple plugins might find themselves adding vendor names, resulting in functions like grunwell_myplugin_get_recent_posts(), which made the code harder to read.

Next, some enterprising developers thought “wait a second, why don’t we just encapsulate everything in classes?”, so we ended up with classes that looked something like this:

As a result, developers would need to instantiate (e.g. create an instance of) these classes, so we often ended up with messy global variable declarations:

From here, developers seem to have split into two factions: the first, dead-set on removing global variables, moved toward the Singleton pattern:

Some plugins (such as WooCommerce) took it a step further, and hid some of the get_instance() ugliness in an easily-referenced function:

Unfortunately, the Singleton pattern makes testing a bit of a bear and doesn’t really eliminate globals (while the global keyword isn’t used, you’re still storing a single reference to an object in a globally-static class property.

The second group of developers recognized that these plugins were rarely being handled as true objects, instead being used as pseudo-namespaces — constructs to encapsulate code from global namespace collisions without using true PHP namespaces. However, since WordPress still (at the time of this writing) supports PHP 5.2 (which lacks PHP namespace support), developers trying to reach the broadest possible audience still needed a way to reasonably encapsulate their code without relying on (or having to learn, depending on who you ask) PHP namespaces. The result was a class full of static methods, acting as a more-responsible pseudo-namespace:

The 10up Engineering Best Practices (disclosure: I’ve contributed to those documents) does a great job of outlining common cases for this approach. Since the methods are static, we’re not creating rogue objects or abusing globals, but we’re still throwing code that likely doesn’t need to be object-oriented into classes; it’s a “it’s an ugly hack, but it’s the least ugly of the ugly hacks” approach.

The "Expanding brain" meme, with 1. Write a function, 2. Prefix the function with your plugin name, 3. Make the function a public, static class method, and 4. use proper PHP namespaces

Proper PHP namespaces in WordPress themes and plugins

Now that we’re up-to-speed, let’s take a look at that original get_recent_posts() function and see how we can get it working with PHP namespaces:

Can you spot the differences between this function and our original example? If not, I’ll give you a hint: it’s literally two lines at the top of the file, starting with namespace and use.

By adding the namespace declaration to the top of the file (and importing the WP_Query class from the global namespace, since our function uses it), we’re able to write clean, well-encapsulated code.

PHP namespaces and the WordPress Plugin API

One of the challenges I see when developers run into when learning to use namespaces within WordPress is the WordPress Plugin API (e.g. actions and filters). When working with functions in the global namespace, hooking into an action might look like this:

Likewise, calling methods inside a class often look like one of the following:

Fortunately, hooking callbacks that live within PHP namespaces looks much more like the first example:

If you’re referencing a callback function in the same (or a child) namespace, you may also use the __NAMESPACE__ PHP magic constant:

When hooking into namespaced callbacks, remember the leading backslashes!

Dealing with users on PHP 5.2

It’s hard to definitively say that every WordPress plugin should be using PHP namespaces, but it’s really easy to make the case that the majority of them should. According to WordPress.org, less than 4% (3.1%, at the time of this writing) of WordPress installations are running on PHP 5.2, and there are well-supported efforts within the community to drop support for PHP 5.2. Put bluntly, there’s no reason to continue to support WordPress sites running PHP 5.2.

However, if you’re building a plugin or theme that might be installed by users running PHP 5.2, you might consider the following approach: make your main plugin file (e.g. my-plugin/my-plugin.php) act as a bootstrap for the rest of the plugin, and include a check for older versions of PHP. The result might look something like this:


Hopefully this crash-course in PHP namespaces has helped to demystify the topic, especially for PHP developers who focus mainly on WordPress. A strong understanding of PHP namespaces will not only help you better structure and encapsulate your code, but will also make it much easier to understand other projects written using more modern PHP methodologies.

10 comments on "A Crash-course in PHP Namespaces for WordPress Developers"

  1. Héctor Cabrera 3 months ago

    I maintain two WordPress plugins and one of the things that have held me back from catching up with the latest PHP features (namespaces, closures, and surely a ton of other things) is precisely the fact that WordPress still supports PHP 5.2 (the other is a bit of laziness, I must admit).

    Nonetheless, I’ve been keeping an eye on WordPress’ usage stats for a few months now and I’m thrilled to see that most people are running WP 4.6 or newer (around 80% of the sites last time I checked) and it should be safe to use namespaces now as well because -like you said- only a small fraction of the sites out there are still running PHP 5.2.

    Hopefully the WordPress team will finally drop support for PHP 5.2 in the near future, although I’m probably not going to wait that long.


    Thanks for the reminder!

  2. Caspar Hübinger 3 months ago

    This post is tremendously helpful Steve, thank you very much for putting it together!

    One thing I’m wondering about (as somebody whose day job sometimes includes reading, but usually not writing PHP) is the use of backslashes to reference back to the global namespace. To modify one of your examples above:

    namespace SteveGrunwell\MyCommand;
    class Command extends \WP_CLI_Command {
      // Define my custom command here.

    Would that work as well?
    Is there an advantage in using use instead of backslashes, aside from that it arguably looks better and makes the code more readable?

    I noticed the Drupal documentation on namespaces goes so far to discourage use for global classes and interfaces:

    Classes and interfaces without a backslash \ inside their fully-qualified name (for example, the built-in PHP Exception class) must be fully qualified when used in a namespaced file. For example: new \Exception();. Do not use global classes.

    Is there any scenario where you’d recommend using backslashes instead of use, and if so, why?

    • Steve 3 months ago

      Hey Caspar, thanks for reading!

      Your modified example (with class Command extends \WP_CLI_Command) is functionally equivalent, so it’s something of a matter of personal preference.

      I definitely appreciate you linking to the Drupal docs on the matter, however, since I had not come across them before. Upon closer reading, it seems that the Drupal coding standards are stating “don’t use built-in (global) PHP classes, use leading backslashes instead.” This is also consistent with the examples in the official PSR-2 spec:

      namespace Vendor\Package;
      use FooClass;
      use BarClass as Bar;
      use OtherVendor\OtherPackage\BazClass;
      class ClassName extends ParentClass implements \ArrayAccess, \Countable
          // constants, properties, methods

      In that example, ArrayAccess and Countable are both preceded by backslashes, but the other classes that aren’t built-into PHP are explicitly imported via use statements. I think this makes a lot of sense, and helps demarcate native PHP functionality from code being defined locally or via packages.

      Great question, thank you!

  3. yogaman5020 3 months ago

    Hello Steve, Great article! I understood it entirely! I came to the article by way of @hellofromtonya, A WordPress educator and developer who teaches the principles of PHP and WP development through KnowTheCode.io. Thank you for making the case for namespacing, and showing the many ways that it can be used with WordPress through either procedural or object-oriented programming.

  4. Alice Wonder 3 months ago

    PHP 5.2 has not been supported by the PHP developers for quite some time, and 5.6 is on its way out, only receiving security updates.

    Any server still running 5.2 is not secure. Yes some distributions backport patches, but those backports are not well tested nor do they ensure the versions of additional modules (e.g. PECL modules) that are required for that version of PHP are secure.

    You can safely ignore any users not at least using 5.6 and at this point you can just focus on PHP 7 users, I suspect when 7.3 is officially released, 5.6 support will soon be completely dropped by the PHP developers. I’d have to check, but that’s what I suspect.

    • Steve 3 months ago

      PHP will 5.6 stop receiving security updates at the end of 2018, along with PHP 7.0 (source: PHP: Supported Versions), but the challenge is that WordPress core officially still supports PHP 5.2 (though the core team has thankfully raised the recommended version to PHP 7.2).

      I disagree that it’s as simple as ignoring users running PHP < 5.6 (at least as far as distributed code goes), but plugin and theme developers outside of the WordPress core team also shouldn't be bound to 5.2, either. Like so many other decisions we have to make as developers, it helps to know where our intended audience lies; building a plugin to optimize performance? One of the best things you can do for PHP performance is to upgrade from 5.x to 7.x, so it's completely justified to say "yeah, if you want to use this you need to be running PHP 7". Meanwhile, plugins or themes targeting the "you've never built a site before, so let us help you through every step" niche will be far more likely to run into users who spin up a one-click install of WordPress on whatever discount host is the cheapest, regardless of specifications. If an uniformed user — who likely has no idea what PHP is, much less the version they're running — activates your plugin and it breaks their site, they're going to assume it's your fault. The best we can do as developers is to be intelligent about how we're approaching environments that don't meet our requirements (such as the auto-deactivation pattern from this post); we don't have to cater to users on older versions of PHP, but we should try our best to "fail" gracefully and nudge the user "hey, you're trying to run this on a really old version of the underlying software, which has long since stopped receiving security updates."

  5. Alice Wonder 3 months ago

    One potential problem with the use statement is that you can’t redefine it, so you can end up with the same namespace clash when use is used in the root namespace in environments like WordPress where many plugins may be running with their own use commands in the root namespace.

    I only use the use statement when I’m writing web apps where that can’t happen because I control all code. It’s probably safe within the scope of your own namespace.

    When WordPress gets a PSR-4 autoloader it will be a bigger deal (my WordPress already has one, I wrote it and put it in my mu-plugins directory) it will be a bigger issue because then plugins will be calling namespaced classes that are not distributed with the plugin but are installed in the PSR-4 class autoloader search path, making conflict of use is the root namespace more like

    use \foo\bar as bar;
    $foo = new bar();

    Two different plugins do that in the root namespace, one will cause an error. So its safer to just forget the use and do

    $foo = new \foo\bar();

    In my opinion.

  6. robbertdekuiper 3 months ago

    Thanks for the article! How does this work with autoload in PSR-4? I can’t seem to autoload non-class, namespaced files without explicity include the file into the autoload. Files that do have classes gets autoloaded when needed.

    And do you have any advice on the correct namespace? I usually see the convention that the namespace of a file follows the folder directory. When you have a (pseudo) OOP setup you would call a function like this

    `SteveGrunwell\MyPlugin\OptionalSubdirectories\Classname::myFunc()` or `SteveGrunwell\MyPlugin\OptionalSubdirectories\Classname->myFunc()` with initialization. When you don’t have a class name the function call would be a bit ambiguous. `SteveGrunwell\MyPlugin\Subdirectory\myFunc()` You miss where the file is about. Or would add the filename to the namespace? So you get `SteveGrunwell\MyPlugin\OptionalSubdirectories\FileName\myFunc()`

  7. Drew Jaynes 3 months ago

    Glad to have more people in the WordPress bubble talking about modern PHP practices. We really need more of it.

    It’s probably also worth mentioning the group use declarations stuff added in PHP 7. I’ve personally used the crap out of it, especially in codebases where there’s a lot of namespaced functions floating around.

    It’s honestly just way nicer to be able to do this

    use My\Namespace\Goes\{Here, AndHere};
    use function My\Namespace\{function1, function2, function3};


    use My\Namespace\Goes\Here;
    use My\Namespace\Goes\AndHere;
    use function My\Namespace\function1;
    use function My\Namespace\function2;
    use function My\Namespace\function2;

    It might seem like a small thing, but when you’ve got functions from several different namespace trees floating around it can be amazing.

Leave a Reply

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