One of the great things about WordPress plugins is that it’s fairly easy to get started and build something functional without a ton of effort. However, once you get beyond a simple concept, have to maintain a growing codebase, or find yourself repeating similar steps, structure becomes essential.
A recent discussion on Post Status and some nudging from Nate Wright convinced me to write a bit about my approach to building plugins.
In most cases, I prefer to build standalone plugins that don’t rely on external libraries if possible due to the lack of dependency management in WordPress (projects where Composer is available is another story). I value simplicity and the ability to re-use code despite my tendency to over-engineer and abstract more than is always necessary.
Autoloading
Autoloading classes allows your code to be loaded automatically when it’s needed instead of having to litter your code with a bunch of calls to include()
and require()
. Basically, when PHP encounters a class it doesn’t know about, it runs through a list of registered autoloaders until it finds one that knows how to load it.
An autoloader is just a callback function, similar to a WordPress filter, that takes a class name and requires the path to the file where the class is defined. As you can probably tell, using an autoloader means you need to follow a particular convention when naming files. It’s also beneficial to only define a single class in each file and nothing else.
WordPress has its own naming conventions for files with classes, but it can become a bit unwieldy with longer file names. I usually follow PSR-0 for distributed plugins or PSR-4 for internal code, but writing an autoloader using the WordPress convention is trivial.
I usually put PHP classes in a /classes
or /php
directory, but some developers put them in /includes
or /src
.
Bootstrapping a Plugin
With all that out of the way, I usually start most plugins with just two classes I can copy between projects to provide a bit of structure:
- AbstractPlugin.php – A base plugin class that can be used to register hooks and access plugin information from other classes.
- AbstractProvider.php – A base for classes that register actions and filters to extend and automatically gain a reference to the main plugin instance.
Each plugin has a main class that extends AbstractPlugin.php and should be instantiated in the main plugin file. The main class may have a few methods that help set up the plugin — sometimes it might not have anything. Instantiating it looks like this:
<?php | |
/** | |
* Plugin Name: My Plugin | |
* Plugin URI: https://gist.github.com/bradyvercher/dde7306ba472fec8a39f | |
* Version: 1.0.0 | |
* Author: Cedaro | |
* Author URI: http://www.cedaro.com/ | |
* License: GPL-2.0+ | |
* License URI: http://www.gnu.org/licenses/gpl-2.0.html | |
*/ | |
function myplugin() { | |
static $instance; | |
if ( null === $instance ) { | |
$instance = new MyPlugin_Plugin(); | |
} | |
return $instance; | |
} | |
myplugin()->set_basename( plugin_basename( __FILE__ ) ) | |
->set_directory( plugin_dir_path( __FILE__ ) ) | |
->set_file( __FILE__ ) | |
->set_slug( 'myplugin' ) | |
->set_url( plugin_dir_url( __FILE__ ) ); | |
myplugin()->register_hooks( new MyPlugin_Provider_I18n() ); | |
add_action( 'plugins_loaded', array( myplugin(), 'load' ) ); |
This works similar to a singleton or a static factory method, but moves the responsibility out of the class itself, making the class easier to test, and provides a more simplistic API.
Calling the loader function in internal classes should be avoided in favor of injecting the plugin object where it’s needed. The loader function is mainly for other plugins, helper functions, or templates that need access to the main plugin instance.
Hook Providers
It’s nearly impossible to develop a WordPress plugin that doesn’t use actions and filters. Without some guidelines, it can be difficult to know where and when to register them, especially if they require interacting with other hooks later in the page life cycle.
Personally, I prefer to register the hooks in the same class where the callbacks are defined to keep the code self-contained and make it easier to understand. That being said, please don’t register hooks in the constructor.
I group the various hooks needed to implement a feature in a single class and register them in a method called register_hooks()
. Generically, I refer to these types of classes as “Hook Providers.” You’ll often see plugins do something similar in a method named run()
, init()
, or setup()
.
In the past I would instantiate these classes in a method and then call the run()
method manually like this:
$myhooks = new MyPlugin_MyHooks(); | |
$myhooks->run(); |
Sometimes you’ll see classes loaded like that in the same file where they’re defined. That always felt dirty and gets ugly pretty quickly. It requires the parent class to know about all the classes it’s instantiating and causes the registration methods to be spread out among various files.
Instead, hook provider classes can extend AbstractProvider.php, which helps clean the code up a bit and allows all the providers to be instantiated in the main plugin file.
Given the plugin loader function from earlier, registering hook providers works like this:
myplugin()->register_hooks( new MyPlugin_Provider_I18n() ); |
Registering hook providers this way automatically calls their register_hooks()
method and injects a reference to the main plugin class for any WordPress API methods that need a reference to the plugin’s path, URL, slug, etc. It’s also possible to inject dependencies when instantiating the hook providers, or the main plugin instance can be used as a service locator if that’s your thing.
General Recommendations
That’s my general approach to starting a new plugin and it’ll likely evolve over time, especially as WordPress’ minimum PHP requirements are increased. It’s not meant to be dogmatic, but if you’re struggling with structure or spending too much time scaffolding new plugins, give it a shot and see what you think.
No matter your approach, here are some things I do recommend:
- Define the main plugin class in its own file (not in the main plugin file).
- Don’t register hooks in constructors.
- Don’t execute code in the same file where functions or classes are defined.
A bare bones example can be found here:
Further Reading
To see a functioning plugin using this approach, take a look at the AudioTheme Agent.
John P. Bloch open-sourced his approach to building plugins last year and it really resonated with me and served as the base for the AbstractPlugin
class. His library, along with the idea of “root composition” as espoused by Tom J. Nowell, have been foundational to the way I’ve been building plugins since. John James Jacoby’s SLASH Architecture also provided a great deal of inspiration, so I’d recommend checking out those resources if you want to dig deeper.
Great tips! Regarding “don’t register hooks in the constructor”, I’ve seen this as a best practice several times, but I don’t think I’ve seen the reasons for it often explained. In your case, is it to remove side effects from being introduced by constructors?
Yes, it’s primarily to keep the constructors lightweight and prevent issues if the class is instantiated multiple times. I write unit tests far less often than I should, but that’s one situation where not registering hooks in the constructor is helpful.
I tend to mentally differentiate classes that are used as namespaces and ones that define object instances, but I know a lot of developers don’t and sometimes mix them. In the latter case, I think it’s especially important not to register hooks in the constructor.
Then again, in many cases, it doesn’t make a difference (singletons, etc), but it helps make classes more independent and reusable since it doesn’t instantly tie them to global state. For those reasons, I think it’s a good idea, if not a best practice.
Thanks for reading and commenting!
For Weston’s comment, I’d also add that not adding hooks into the constructor and relegating them to another method (and perhaps having them communicate with another class such as a loader) can help do two things:
1. Separate business concerns / domain logic
2. Make the classes more testable
3. Probably more that people smarter than me could share here!
Depending on your setup, you may not need to worry about if you have a sample WordPress environment for testing. Sometimes it’s inevitable, but if you’re looking for “pure” (and I hate that word in this context, but that’s okay :) testing, then keeping the classes as having a low-level of cohesion on WordPress allows you to test the functions within the class without needing the database and all of the other functions that come with WordPress.
For whatever this is worth, I used to be this way.
The more I’ve gotten into WordPress development and used WP-CLI in my work, the more I’ve been fine having it set up a test environment and destroy it when tests are done. This doesn’t change the attempt to keep domain logic separate from the rest of the communication to the CMS, but serious headway has made unit testing in WordPress much easier than it was just three years ago.
Thank you for sharing your patterns, Brady. I have a few of my own (that I occasionally talk about) and I love seeing what other people, such as yourself, do as well as the rationale as to why.
Always something to learn :).
A big +1 on that!
Thanks for summarizing brady!
Hey Brady, do you have an example plugin you could share that utilises the structure base plugin? I’d like to see how it follows through in practice?
Thanks very much for the post BTW, it’s given me plenty to think about :)
Aside from AudioTheme Agent mentioned in the article, I’ve refactored a few plugins to use this same approach. Check out Gravity Forms Iframe add-on, AudioTheme (this was a large refactor), and Cue. Bandstand is an in-progress rewrite of AudioTheme without some of the legacy baggage and includes more modern PHP.