Autoloading & Namespaces in WordPress Plugins & Themes: Can it Work?

Has anyone used autoloading and/or PHP namespaces within a plugin or theme?

Thoughts on using them? Any harm? Pitfalls?

Note: namespaces are PHP 5.3+ only. Assume, for this question, that you know you’ll be dealing with servers that you know have PHP 5.3 or greater.

Okay, I’ve had two big projects where I’ve been in control of the server enough to namespace and rely on autoloading.

First up. Autoloading is awesome. Not worrying about requires is a relatively good thing.

Here’s a loader I’ve been using on a few projects. Checks to make sure the class is in the current namespace first, then bails if not. From there it’s just some string manipulation to find the class.

<?php
spl_autoload_register(__NAMESPACE__ . '\\autoload');
function autoload($cls)
{
    $cls = ltrim($cls, '\\');
    if(strpos($cls, __NAMESPACE__) !== 0)
        return;

    $cls = str_replace(__NAMESPACE__, '', $cls);

    $path = PLUGIN_PATH_PATH . 'inc' . 
        str_replace('\\', DIRECTORY_SEPARATOR, $cls) . '.php';

    require_once($path);
}

One could easily adapt this for use without namespaces. Assuming you prefix your plugin’s/theme’s classes uniformly, you could just test for that prefix. Then use underscores in the class name to as placeholders for directory separators. If you’re using a lot of classes, you’ll likely want to use some sort of classmap autoloader.

Namespaces and Hooks

WordPress’ hooks system works by using call_user_func (and call_user_func_array), which takes function names as strings and calls them when the do_action (and, subsequently, call_user_func) function call is made.

With namespaces, that means you’ll need to pass fully qualified function names that include the namespace into hooks.

<?php
namespace WPSE\SomeNameSpace;

add_filter('some_filter', 'WPSE\\SomeNameSpace\\the_function');
function the_function()
{
   return 'did stuff';
}

It would probably be better to make liberal use of the __NAMESPACE__ magic constant if you want to do this.

<?php
namespace WPSE\SomeNameSpace;

add_filter('some_filter', __NAMESPACE__ . '\\the_function');
function the_function()
{
   return 'did stuff';
}

If you always put your hooks into classes, it’s easier. The standard create instance of a class and all hooks in the constructor with $this works fine.

<?php
namespace WPSE\SomeNameSpace;

new Plugin;

class Plugin
{
    function __construct()
    {
        add_action('plugins_loaded', array($this, 'loaded'));
    }

    function loaded()
    {
        // this works!
    }
}

If you use static methods like I want to do, you’ll need to pass the fully qualified class name as the first argument of the array. That’s a lot of work, so you can just use the magic __CLASS__ constant or get_class.

<?php
namespace WPSE\SomeNameSpace;

Plugin::init();

class Plugin
{
    public static function init()
    {
        add_action('plugins_loaded', array(__CLASS__, 'loaded'));
        // OR: add_action('plugins_loaded', array(get_class(), 'loaded'));
    }

    public static function loaded()
    {
        // this works!
    }
}

Using Core Classes

PHP’s classname resolution is a bit wonky. If you’re going to use core WP classes (WP_Widget in the example below) you must provide use statements.

use \WP_Widget;

class MyWidget extends WP_Widget
{
   // ...
}

Or you can use the fully qualified class name — basically just prefixing it with a backslash.

<?php
namespace WPSE\SomeNameSpace;

class MyWidget extends \WP_Widget
{
   // ...
}

Defines

This is more general PHP, but it bit me, so here it is.

You may want to define things you’ll use often, like the path to your plugin. Using the define statement puts things in the root namespace unless you explicitly pass the namespace into the first argument of define.

<?php
namespace WPSE\SomeNameSpace;

// root namespace
define('WPSE_63668_PATH', plugin_dir_path(__FILE__));

// in the current namespace
define(__NAMESPACE__ . '\\PATH', plugin_dir_path(__FILE__));

You can also use the const keyword in the root level of a file with PHP 5.3 plus. constss are always in the current namespace, but are less flexible than a define call.

<?php
namespace WPSE\SomeNameSpace;

// in the current namespace
const MY_CONST = 1;

// this won't work!
const MY_PATH = plugin_dir_path(__FILE__);

Please feel free to add any other tips you might have!

Leave a Comment