Creating a PSR-4 WordPress Plugin

When you’re used to making use of modern PHP namespaces and autoloading, WordPress’ soup of global functions and manual requires can feel like a chaotic mess. But just because WordPress hasn’t yet itself embraced conventions such as PSR-4, it doesn’t mean we can’t when we’re writing plugins.

Most modern PHP development makes use of the dependency manager Composer, which normally would handle most of the autoloading for us. WordPress by default does not use Composer so we’ll need to write our own PSR-4 autoload function. If (like us) you happen to use the awesome Bedrock project for your WordPress development, you’ll already have Composer baked into your setup. We want our plugin to work with a vanilla WordPress install and also when it’s loaded by Composer.

For the rest of this post, I’m assuming you already have some experience of both PSR-4 and creating WordPress plugins.

As an example we’ll create a trivial plugin called Widget that we want to use within a namespace of Acme. To start we need to create the plugin folder structure:

acme-widget
  - src
    - Acme
      - Widget.php 
  - autoload.php
  - composer.json
  - acme-widget.php

First you’ll need a file to act as a bootstrap for your plugin, in this example we’ve called this acme-widget.php:

<?php
/*
Plugin Name: Widget
Description: Doesn't do a great deal!
Version: 1.0.0
Author: Acme
*/

// If we haven't loaded this plugin from Composer we need to add our own autoloader
if (!class_exists('Acme\Widget')) {
    // Get a reference to our PSR-4 Autoloader function that we can use to add our
    // Acme namespace
    $autoloader = require_once('autoload.php');

    // Use the autoload function to setup our class mapping
    $autoloader('Acme\\', __DIR__ . '/src/Acme/');
}

// We are now able to autoload classes under the Acme namespace so we
// can implement what ever functionality this plugin is supposed to have
\Acme\Widget::init();

We then need to define our autoload.php file which returns a reference to an anonymous function. This function wraps our autoload function but allows us to provide a mapping between our PSR-4 namespace and where the relevant classes can be loaded from. The actual autoloader is a modified version of an example on the PSR-4 website.

<?php
/**
 * Anonymous function that registers a custom autoloader
 */
return function ($prefix, $baseDir) {
    spl_autoload_register(function ($class) use ($prefix, $baseDir) {
        // does the class use the namespace prefix?
        $len = strlen($prefix);
        if (strncmp($prefix, $class, $len) !== 0) {
            // no, move to the next registered autoloader
            return;
        }

        // get the relative class name
        $relative_class = substr($class, $len);

        // replace the namespace prefix with the base directory, replace namespace
        // separators with directory separators in the relative class name, append
        // with .php
        $file = $baseDir . str_replace('\\', '/', $relative_class) . '.php';

        // if the file exists, require it
        if (file_exists($file)) {
            require $file;
        }
    });
};

The final thing todo is to make sure that we have a composer.json file incase the plugin is being brought in via Composer.

{
    "name": "acme/widget",
    "description": "Doesn't do a great deal!",
    "type": "wordpress-plugin",
    "authors": [
        {
            "name": "Jane Doe",
            "email": "jane@thecompany.com"
        }
    ],
    "require": {
        "php": ">=5.3.0"
    },
    "autoload": {
        "psr-4": {
            "Acme\\": "src/Acme"
        }
    },
    "minimum-stability": "stable"
}

It’s important to add the "type": "wordpress-plugin" otherwise Bedrock won’t know how to handle the package when it is brought in via composer.

You should now have a WordPress plugin that can be installed and used either in the traditional manner or by using Composer.