Developers know that duplicate code is a bad thing. Making changes to many areas of a project for it to stay consistent is an opportunity for bugs to creep in, and an unnecessary waste of time.
For a recent refactoring project, the client had a theme functions file that was 2500 thousand lines long. It connected to two other databases, registered 11 post types, and had the other site customisations. My task was to move that out of theme and into discrete plugins which would persist if the client changed the theme.
This post looks at how I approached handling the post types and taxonomies, and the customisations they needed.
Registration of a Single Post Type
In a previous project, I worked on how best to register a single post type. From this, my object-orientated Gamajo_Registerable
library emerged.
With many post types and taxonomies to register, the design of the library makes a post type trivial to create. The concrete implementations only included code to meet internationalization requirements of strings. A developer can copy those strings into a new class and updated as necessary. The end result is a reduction in code, time saved now, and time saved in the future.
I decided to split each post type up into its own plugin. While not necessary, it provided several advantages:
- Some post type plugins have related renderer code or a taxonomy registration as well. Grouping these related classes together means a user can activate or deactivate the plugins independently to get all or none of the functionality.
- Future developers can better guess which plugin some code is likely to be in.
- Plugins with just a registration are immediately available to have later additions. The client or future developers don’t have to re-organise code themselves.
Using Gamajo_Registerable
would mean including the interface and classes in each post type plugin leading to code duplication. If I released a bug fix version of one of the classes, then having to update it in 11 plugins would be painful.
So, I created a Core Library plugin.
Core Library Plugin
Bill Erickson and others have written about “core functionality plugins” before. My core library plugin is different – even when activated, on its own, it has no effect. What it does do is make re-usable classes and interfaces available that other plugins can assume are present.
I make the core library a must-use plugin. It requires the library files when PHP parses the plugin. The standard plugins do not instantiate their dependent classes until the init
hook so there are no issues with load order.
Core Library Plugin Example
I’ve created an example of a core library plugin on GitHub called Core Library Plugin Example.
I’ve pulled it from a real project, so I’ve anonymised the site’s details and replaced them with X.
Included are the library classes from:
What’s not included yet is a check to see if PHP already knows about the interface and classes yet. This could happen if an individual plugin included them. It wasn’t needed on my client’s site on this occasion so x_core()
remains quite simple.
If multiple plugins need to contain some other functionality, then add it to your core library plugin. An example would be the
class-gamajo-dashboard-rightnow.php
class, or the Custom Meta Boxes library.
The x-core.php
and x-core
directory would sit inside your wp-contents/mu-plugins
directory (create it if it doesn’t exist).
Standard Plugin Example
I’ve also created an example standard plugin, which depends on the core library plugin. I’ve called this the Core Library Plugin Consumer Example.
As before, I pulled this from a real project, so I’ve re-branded the client as X again.
The main plugin file contains a simple check to see if the x_core()
function exists. If it doesn’t, the plugin deactivates to avoid causing fatal errors. The theme can be setup with the TGM Plugin Activation class so all dependent plugins activate if x_core()
is present. That’s a topic for another day.
The plugin registers an offer post type, an offer type taxonomy, and show the offers within a Genesis child theme. The three classes keep everything as separate objects: a Post Type object, a Taxonomy object and a Renderer object. A plugin could add an Admin or UI object to amend post type columns or filters, or a Meta object for handling metadata boxes for the post type etc.
This object-orientated approach isn’t specific to using a core library plugin. Having helpful classes in the library to add those features makes the other plugins smaller and consistent. The best example here is adding a row to the At a Glance dashboard widget. The library class only needs a post type and status from the standard plugin to work.
Conclusion
I see the main benefits of extracting out code as reduced maintenance, and a promotion of cleaner code.
What are your thoughts on using a core library plugin?
I think it is an interesting idea. It works best for projects on a single site. For a publicly released plugin I don’t think it is the best way as it can be sometimes confusing for users. In the past I have used submodules for git where I only update the code in one place and just update the submodule in the other places.
Thanks for commenting Ulrich.
Since the purpose is to encapsulate multiple site-specific customisations, without duplicating code (or as much re-usable code as possible, depending on your point of view), I completely agree that the core library plugin works well on a single site, but not as a distributable plugin. Even for my example core library plugin, I considered doing it as submodules of the main library repos, or subtree splits, but in the end, decided a snapshot of them would be sufficient.