Ross Morsali
Posted on August 19, 2021
I've been working on upgrading one of my WordPress plugins - Search & Filter for some time now, quite the overhaul you might say...
When planning this update there were a bunch of things I knew I needed to implement. I won't bore you with all the details, but two tasks I wanted to tick off were:
- Use PHP namespaces
- Setup an autoloader
PHP namespaces
The plugin is getting quite big, and having everything as a class with a unique name was getting a bit messy.
PHP namespaces solve this issue perfectly as you can scope your classes and then you only need to worry about the uniqueness of your top-level namespace (uniqueness is important in WP plugins in order to avoid conflicts with other plugins).
This also lends itself very nicely to consistent project structure, because you will likely nest your files in directories matching your namespaces - everything becomes more predictable and uniform.
PHP autoloader
When PHP applications grow in size, rather than having to require
every other .php
file in a long list (or by some other method) it's good practice to automatically load them.
As long as your application has a predictable structure (namespace and class names), you don't need to specify which files to load because we can predict where the relevant file is for a particular class (more on that later).
What's also great about autoloaders is they only load files at the last minute - when a class is used - giving your application a lighter memory footprint.
These two concepts absolutely go hand in hand.
WordPress enters chat...
Ok, so we know from the above that if our plugin is going to grow that we're going to want to use both namespaces and an autoloader.
And if we're good developers, we also want to follow the WordPress coding standards.
Coding standards are a good thing and often in the life of a developer, we'll find conflicting scenarios where we might want to use one set of standards or the other.
The problem here is, while WordPress coding standards cover a lot they don't mention anything about namespaces.
2023 Correction: Looks like a section on namespaces has now been created and that they follow the same convention described in this post.
WordPress PHP Coding Standards
In the naming conventions, it's stated that class names should be cased like this:
My_Class
And that they should have a corresponding filename something like this:
class-my-class.php
The class name is lowercased, underscores are hyphenated and the filename is prepended with class-
.
So far so good.
Plugin Development Best Practises
There is a section in the best practises which is hugely important:
Prefix everything!
When a site can have hundreds of plugins, the only way our code won't be messed with is if we prefix everything (including class names).
You might want to go with a simple abbreviation prefix, eg, if your plugin was called Speed Up
, you could use SU_My_Class
but in my opinion (and in the examples) the full name is better, so in theory, the ideal class name would be:
Speed_Up_My_Class
It's already getting cumbersome, but it is necessary.
This would ideally be in a file:
class-speed-up-my-class.php
Switching to namespaces
When structuring a plugin like this it is common to find yourself with tons of files starting with class-speed-up-... .php
- to me it's not particularly elegant, but it's necessary because it accurately represents the class name, which has been prefixed too Speed_Up_...
If we switch to namespaces we can alleviate some of this pain and repetition:
namespace Speed_Up;
class My_Class {
}
The great thing here is, now My_Class
lives inside the Speed_Up
namespace, we don't need to worry about prefixing it with Speed_Up
again. By using a namespace, we've kind of prefixed everything inside of it automatically and it won't conflict with other plugins.
This is how we would use the class now:
$my_class = new \Speed_Up\My_Class;
So I think there is not much complexity in this process - just start using them and drop the plugin prefix from all your class names (and filenames) - to be honest I think it's keeping in line with the spirit of prefix everything.
2023 Update: WordPress coding standards now recommends this as the way to use namespaces.
A quick note about namespaces - they support nesting! So you can keep adding names spaces and sub-levels to improve your code organisation - eg:
Speed_Up\Frontend\Scripts\Register
Setting up the autoloader
Now we've got the namespace out of the way let's head back to the autoloader.
There are a few ways to setup autoloaders (each with its own unique benefits) which I'm not going to get into right now - I'm going to go for the easiest and most straightforward method while also keeping things in the spirit of the WordPress coding standards.
Setting one up requires using built-in PHP features.
To implement this in a plugin would require adding some code at the top of your plugins main php
file - if we're sticking with the name speed-up
then it would be located:
wp-content/plugins/speed-up/speed-up.php
- We could also create a new file
autoload.php
in the root directory to store our autoloader - which is something you'll probably see quite often when reading up on the subject. *
If we add the first example from the PHP docs:
spl_autoload_register(function ($class_name) {
include $class_name . '.php';
});
Then we'll be all set to go! Well, kind of.
What this code does is register a callback to be used, to specify which filename/path we should look in order to find a class.
Roughly how it works:
When a class is first used, eg: $my_class = new My_Class()
and it is not yet loaded, the function we passed to spl_autoload_register
will be run to try to resolve the filename for the class.
It is passed in the $class_name
, and then you can see the file is loaded via include
. In the example above, based on the class name the file
My_Class.php
would attempt to be loaded.
Also, something worth observing, when a class is used in a namespace, the full namespace + class name is passed through $class_name
.
Writing an autoloader for a WordPress plugin
So let's apply the coding standards to the autoloader and calculate the correct file path based on namespace and class name.
What we want to achieve:
- Class filenames must be prefixed with
class-
... - Class filenames should be lowercase
- Underscores should be replaced with dashes
And we're going to convert namespaces. Classes belonging to a namespace should live in a folder (namespace name) that follows the conversion process of a class. So we need to add:
- Namespace names will be converted to lowercase
- Namespace names will have underscores replaced with dashes
- Namespaces will be folders so convert
\
to a directory path
In addition - because the root plugin folder "is the plugin" and already has a directory name relevant to the plugin name, eg - speed-up
we shouldn't create another folder called speed-up
just to match our Speed_Up
plugin namespace, it's unnecessarily repetitive and we'll actually remove that, and instead, we'll create a folder called includes
for our classes instead.
So our plugin structure would look like this:
speed-up // top-level plugin folder
-- speed-up.php // main plugin file
-- readme.txt // required with every plugin
-- includes // this folder is where we store all our classes
---- database // folder for the sub namespace Database
------ class-connect.php // class Connect
class-worker.php // class Worker
The class Worker
in includes\database\class-worker.php
above would be accessed like this:
Speed_Up\Database\Worker
For me, this is the perfect balance of adding namespaces to WordPress plugins while keeping in the spirit of the coding standards.
The code
To parse the name space and class names, and follow those above conversion rules (class name -> path + filename) would result in some PHP code like this:
// Define the main autoloader
spl_autoload_register( 'speed_up_autoloader' );
function speed_up_autoloader( $class_name ) {
// These should be changed for your particular plugin requirements
$parent_namespace = 'Speed_Up';
$classes_subfolder = 'includes';
if ( false !== strpos( $class_name, $parent_namespace ) ) {
$classes_dir = realpath( plugin_dir_path( __FILE__ ) ) . DIRECTORY_SEPARATOR . $classes_subfolder . DIRECTORY_SEPARATOR;
// Project namespace
$project_namespace = $parent_namespace . '\\';
$length = strlen( $project_namespace );
// Remove top-level namespace (that is the current dir)
$class_file = substr( $class_name, $length );
// Swap underscores for dashes and lowercase
$class_file = str_replace( '_', '-', strtolower( $class_file ) );
// Prepend `class-` to the filename (last class part)
$class_parts = explode( '\\', $class_file );
$last_index = count( $class_parts ) - 1;
$class_parts[ $last_index ] = 'class-' . $class_parts[ $last_index ];
// Join everything back together and add the file extension
$class_file = implode( DIRECTORY_SEPARATOR, $class_parts ) . '.php';
$location = $classes_dir . $class_file;
if ( ! is_file( $location ) ) {
return;
}
require_once $location;
}
}
I've also setup a tiny demo plugin using the above for you to play with over on GitHub:
Are you doing it a different way? No technique is perfect and there is always room for improvement - let me know your thoughts!
If you want to read more articles like this you can follow me on Twitter to keep up to date.
Posted on August 19, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
May 28, 2024