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:
1 2 3 4 5 6 7 8 9 10 |
<?php /** * Functionality for Package A. */ namespace SteveGrunwell\PackageA; function do_something() { // Do something } |
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:
1 2 3 4 5 6 7 8 9 10 |
<?php /** * Functionality for Package B. */ namespace SteveGrunwell\PackageB; function do_something() { // Do something totally different than Package A. } |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<?php /** * The CreativeWork Schema. * * @package Schemify * @link http://schema.org/CreativeWork */ namespace Schemify\Schemas; class CreativeWork extends Thing { // ... } |
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
1 2 3 4 5 6 7 8 |
<?php namespace SteveGrunwell\PackageB; function do_something() { // Call do_something() from Package A. $packageA = \SteveGrunwell\PackageA\do_something(); } |
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:
1 2 3 4 5 6 7 8 9 10 |
<?php namespace SteveGrunwell\PackageB; use SteveGrunwell\PackageA as A; function do_something() { // Call do_something() from Package A. $packageA = A\do_something(); } |
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):
1 2 3 4 5 6 7 8 9 10 |
<?php namespace SteveGrunwell\PackageB; use function SteveGrunwell\PackageA\do_something as pkgA; function do_something() { // Call do_something() from Package A. $packageA = pkgA(); } |
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:
1 2 3 4 5 6 7 8 9 10 |
<?php /** * My custom WP-CLI command. */ namespace SteveGrunwell\MyCommand; class Command extends WP_CLI_Command { // Define my custom command here. } |
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:
1 2 3 4 5 6 7 8 9 10 11 12 |
<?php /** * My custom WP-CLI command. */ namespace SteveGrunwell\MyCommand; use WP_CLI_Command; class Command extends WP_CLI_Command { // Define my custom command here. } |
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:
1 2 3 4 5 6 7 8 9 10 11 |
<?php function get_recent_posts( $limit = 5 ) { return new WP_Query( array( 'post_type' => 'post', 'post_status' => 'publish', 'orderby' => 'post_date', 'order' => 'desc', 'posts_per_page' => $limit, ) ); } |
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:
1 2 3 4 5 6 7 |
<?php class MyPlugin { public function get_recent_posts( $limit = 5 ) { return new WP_Query( /* ... */ ); } } |
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:
1 2 3 |
global $my_plugin; $my_plugin = new MyPlugin(); |
From here, developers seem to have split into two factions: the first, dead-set on removing global variables, moved toward the Singleton pattern:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
# Class definition: class MyPlugin() { protected static $instance; protected function __construct() { // Made protected to prevent `new MyPlugin()` calls. } public static function get_instance() { if ( ! self::$instance ) { self::$instance = new MyPlugin(); } return self::$instance; } public function get_recent_posts( $limit = 5 ) { return new WP_Query( /* ... */ ); } } # Usage: $plugin = MyPlugin::get_instance(); $recent_posts = $plugin->get_recent_posts(); |
Some plugins (such as WooCommerce) took it a step further, and hid some of the get_instance()
ugliness in an easily-referenced function:
1 2 3 4 5 6 7 |
# Definition: function my_plugin() { return MyPlugin::get_instance(); } # Usage: $recent_posts = my_plugin()->get_recent_posts(); |
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:
1 2 3 4 5 6 7 8 9 |
# Class definition: class MyPlugin { public static function get_recent_posts( $limit = 5 ) { return new WP_Query( /* ... */ ); } } # Usage: $recent_posts = MyPlugin::get_recent_posts(); |
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.
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<?php namespace SteveGrunwell\MyPlugin; use WP_Query; function get_recent_posts( $limit = 5 ) { return new WP_Query( array( 'post_type' => 'post', 'post_status' => 'publish', 'orderby' => 'post_date', 'order' => 'desc', 'posts_per_page' => $limit, ) ); } |
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:
1 |
add_action( 'some_action', 'my_callback_function' ); |
Likewise, calling methods inside a class often look like one of the following:
1 2 3 4 5 6 7 8 9 10 11 |
# With the class instance. add_action( 'some_action', array( $instance, 'my_callback_function' ) ); # With a Singleton. add_action( 'some_action', array( MyPlugin::get_instance(), 'my_callback_function' ) ); # A Singleton with a helper function. add_action( 'some_action', array( my_plugin(), 'my_callback_function' ) ); # A public, static method add_action( 'some_action', 'MyPlugin::my_callback_function' ); |
Fortunately, hooking callbacks that live within PHP namespaces looks much more like the first example:
1 |
add_action( 'some_action', '\SteveGrunwell\MyPlugin\my_callback_function' ); |
If you’re referencing a callback function in the same (or a child) namespace, you may also use the __NAMESPACE__
PHP magic constant:
1 |
add_action( 'some_action', __NAMESPACE__ . '\my_callback_function' ); |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
<?php /** * Plugin Name: My Plugin * Plugin URI: https://wordpress.org/plugins/my-plugin/ * Description: An example plugin * Version: 1.0.0 * Author: Steve Grunwell * Author URI: https://stevegrunwell.com * License: MIT */ /** * Deactivate the plugin if the site cannot support it. */ function myplugin_deactivate() { deactivate_plugins( plugin_basename( __FILE__ ) ); } /** * Display a notice informing the user that the plugin was deactivated. */ function myplugin_show_deactivation_notice() { echo wp_kses_post( sprintf( '<div class="notice notice-error"><p>%s</p></div>', __( '"My Plugin" requires PHP 5.3 or newer.', 'my-plugin' ) ) ); } if ( ! version_compare( PHP_VERSION, '5.3.0', '>=' ) ) { add_action( 'admin_init', 'myplugin_deactivate' ); add_action( 'admin_notices', 'myplugin_show_deactivation_notice' ); // Return early to prevent loading the other includes. return; } // Load the rest of the plugin files, which use namespaces. |
Conclusion
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.
Héctor Cabrera
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.
TLDR;
Thanks for the reminder!
Steve
What’s even better is that between the “Requires PHP” header in the WordPress.org plugin readme.txt file and efforts like Serve Happy, it’s becoming even easier to say “if you want the advanced functionality that comes from my plugin, you need to be on a (semi-) recent version of PHP. If not, you have bigger problems than whether or not your site has X feature.”
Thanks for reading and the kind words!
Caspar Hübinger
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:
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:Is there any scenario where you’d recommend using backslashes instead of
use
, and if so, why?Steve
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:In that example,
ArrayAccess
andCountable
are both preceded by backslashes, but the other classes that aren’t built-into PHP are explicitly imported viause
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!
yogaman5020
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.
Alice Wonder
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
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."
Alice Wonder
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.
robbertdekuiper
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()
orSteveGrunwell\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 getSteveGrunwell\MyPlugin\OptionalSubdirectories\FileName\myFunc()
Drew Jaynes
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};
over
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.
Steve
I agree 100%, and that might make for a good follow-up article. I originally left them out because I didn’t want to dilute the “seriously, don’t worry about the people running PHP < 5.3" argument, but importing functions + constants from other namespaces wasn’t added until PHP 5.6.
Marko Štimac
What is your preferred way of namespacing data for wp_localize_script, which become attached to window object?
Steve
Different kind of namespacing, but still important :)
Generally, I wouldn’t apply full namespace paths to localized resources, but try to keep the root-level object unique (e.g.
window.pluginName.i18n
rather thanwindow.i18n
). The last time I checked, WordPress doesn’t have logic surrounding “ifwindow.vendorName
already exists, append this data rather than create a new variable”, so I generally aim for a single plugin creating no more than one entry in the globalwindow
object.Taufik Nurrohman
Fix typo:
public function get_instance() {}
should be:
public static function get_instance() {}
Steve
Great catch, thank you!
Yasser
Very helpful thank you!
Am I supposed to write use/use function for every WordPress class/function I’m going to use?
For example, sometimes I see “use function wp_enqueue_scripts” but other WP functions worked without this like the_content()
I mean when should I write use function wordpress_function_name; and when not?
Steve
While you certainly can import everything, PHP will be able to find anything that’s in the global namespace (which, at this point, includes everything defined within WordPress core).
As a general rule, I’ll usually import any classes (outside of those provided by PHP itself) but not the functions. Meanwhile, classes provided by PHP will get an explicit leading backslash, as if to say “don’t bother looking in the codex for this, it’s standard PHP). For example:
In that example, I didn’t bother to import
__()
, (though I certainly could have). I chose to importWP_Query
(though not totally necessary), and to use the fully-qualified\Exception
to make it clear this was theException
class from PHP core.At the end of the day, remember that the goal is to make it clear to developers, the PHP interpreter, and tools like IDEs what definitions are being referenced.