My Innovative WordPress Plugin Development Framework
As the developer of several WordPress Plugins, including Bible Buddy, StoragePress Self-Storage, and ContentOracle AI Chat, I have learned a lot about building plugins for WordPress. The most glaring among the things I have learned is how ugly a plugin’s codebase becomes as the plugin grows. There is no standard for how a plugin should be organized, which leads to an ugly amalgamation of php, javascript, and css files strewn throughout a messy folder structure. It is hard to find the code for a particular feature when making changes, and very difficult to coordinate work between multiple developers. That is why I decided to try to bring order to the chaos of my plugins by creating my WordPress Plugin Development Framework: to bring some structure and organization to my projects. Read on to see how I did it, and follow along by looking at the source code.
Design: Divide into Features
When I was considering how I wanted to approach building a framework for WordPress Plugins, something that I noticed about WordPress Development that distinguishes it from many other types of software development is how disjointed projects tend to be. Because the plugin development API is so heavily hook-based, projects tend to consist of many small parts that are almost completely independent of each other that combine to produce some useful feature. But, the key is, these parts, or features, do not depend on each other to function for the most part. So, they can be developed almost as small, independent programs on their own.
This feature-based way of dividing up a project appealed to me greatly, as works as a great way to group related code. So, I made this the crux of my framework. A plugin might have many features, such as a settings page in the admin area, a WordPress REST api route handler, a custom block type, etc. In my framework, each of these features is developed in its own folder, and then imported and registered in the master plugin manager. An ideal directory structure for a plugin using my framework, then, would look like this:
plugin
|->features
| |->settings
| |->api
| |->blocks
| |->etc.
|->vendor
| |->dependencies here...
|->composer.json
|->composer.lock
|->plugin.php
|->readme.txt
Implementing this system in object-oriented code meant I had to build two key features: a master plugin manager class, and a class that represents each feature of the plugin. Let’s take a closer look at how I managed this.
The PluginFeature Class
The PluginFeature class contains all the logic of one particular feature of a plugin. Each feature and all its code should reside in a single folder inside the “features” directory shown above. Diving into the code, you will notice that PluginFeature is an abstract class. This means that it cannot be instantiated directly, but instead it defines a blueprint that classes that inherit from it must implement. So, each feature of the plugin must be defined within a class inheriting from PluginFeature, and each feature must contain custom implementations of add_actions and add_filters.
What should be done in these implementations? Well, calls to WordPress’ add_action and add_filter of course! These two functions are where all your hooks should be registered. Their callbacks should be implemented as methods of the child class. This design decision keeps all your hook calls in the same place, and has the added benefit of reducing the probability of function naming conflicts due to prefixing issues, since they are defined under the class. As we will see, the PluginFeature’s add_actions and add_filters methods will be called automatically by the other part, the Plugin class, once they have been registered and the plugin has been initialized.
The PluginFeature class has one other neat feature. It works with the Plugin class to get access to project-wide variables that it would not otherwise have direct access to. These are the plugin prefix (used for prefixing options and other database values, critical for avoiding naming conflicts), the plugin base directory, and plugin root url. These can be gotten anywhere in the feature by simply calling:
$this->get_prefix()
This makes it easy to get the correct values even if folder names or the prefix ever change, making this another valuable feature of the framework.
The Plugin Class
I designed the Plugin class to be the main object that registers all of the plugin features with WordPress. As such, it has one main purpose: to accept as many PluginFeature instances as is needed, and properly register all their callbacks with the appropriate hooks at the appropriate time.
The api for doing this is quite simple. The class has a register_feature function, which accepts a a plugin feature. It does two things:
- It queues the feature for initialization, and
- it provides the feature with a reference to the master plugin manager.
All features queued using the register feature function are initialized when the Plugin’s init function is called. This should be done after all features are imported, created, and registered in the root level main php file (plugin.php above) of the plugin.
The Plugin class also provides a few useful utilities to all features registered with it. It stores the base directory of the plugin, the base url of the plugin, and the prefix of the plugin. The reference to the plugin supplied to the PluginFeature in the register_feature plugin is where the PluginFeature’s get_prefix, get_base_dir, and get_base_url pull from, so each feature is pulling from a single source of truth: the Plugin instance.
Conclusion
My WordPress plugin development framework has vastly cleaned up my codebases and made it much easier to keep a project scaling well and running smoothly. It also provides a range of other benefits beyond code organization, from helpful utility functions to a reduced chance of naming conflicts. ContentOracle AI Chat is a plugin I have built using this framework, and I can safely say that the development process has gone much smoother thanks to my new framework. If you want to try it out, check out this free template project I have up on Github (be sure to run the shell script to initialize your project).
Thanks for reading. Feel free to reach out with questions, comments, or suggestions. Happy coding!
Leave a Reply