Creating WordPress Plugins in an Object Oriented Way
One of the common criticisms of WordPress is that the codebase is a mess. There is some truth to this, but as the core code is secure and fast it shouldn’t be a huge concern because backwards compatibility is more important.
Third party code (plugins and themes) do employ a lot of spaghetti – or cowboy – coding and “I do whatever I want” methodology. Compared to a strict object oriented platform like Laravel, the code written for WordPress is usually (not always) worse in terms of quality.
In this article I’ll show you an object oriented approach (there are many others), which I hope will serve as inspiration for cleaning up your code. This article is for the advanced crowd. It requires a solid PHP foundation a rudimentary grasp of OOP won’t hurt. If you are new to OOP, it may be a good idea to read a getting started guide. You should also be familiar with how plugins work and how hooks are used to modify WordPress functionality.
We’ll be using a plugin I recently released to WordPress.org as an example: Unread Posts. The plugin contains a widget and a facility for showing unread posts on single pages, but it is also a foundation other plugins can use to get unread posts. It works whether a user is logged in or not, which is neat.
Let’s get started with how Unread Posts was made.
Planning before typing is always a good idea, even more so when creating objects. I think our initial goal should be to create a facility that can handle unread posts. This means we should be able to retrieve read/unread posts, set posts as read/unread and perhaps delete a user’s list of read posts altogether.
So the first step I took was to plan out the functions we will need, something like this:
set_posts_as_read( $posts )will receive one or more post as an argument and set these as read posts.
set_posts_as_unread( $posts )will receive one or more post as an argument and set them as unread posts (they will be removed from the read list).
get_read_posts()will retrieve all read posts
get_unread_posts()will retrieve all unread posts
delete_read_posts()will delete all read posts
Why Procedural Code Falls Short
Based on this information we’ll need two sets of functions, or we’ll need to use if statements a lot. Getting/setting data in the database and in a cookie is radically different. If we use spaghetti code we would end up with a set of functions like this:
There are two issues here. First of all, you would need that “if” logic for every single function you write. The other issue arises if you want to add a third method of saving the data, perhaps syncing it to an external API. You would need to go through all your functions, adding the third options using another “if” statement.
You could hide some of this logic by calling functions dynamically, something like this:
This is a step in the right direction, but still not great. You’d need to create a bunch of potentially confusingly named functions. In addition, you’d probably have to globalize the
$type variable if you want to use it within functions.
Interfaces Are Here to Help
Before we get into interfaces I think it’s pretty obvious we will be using classes for our functionality. Classes allow us to create self-contained units, and to use more generic function names. The plan is to create a separate class that handles read posts in the database and a separate one for cookies.
Each class will contain the functions we mentioned above, making sure the usage is the same. When you have multiple classes with the same purpose, implementing the same functions just differing in the details of functions, you are looking at a scenario where an interface might be useful.
Interfaces seem really scary but are actually pretty simple. Among other things, they allow you to establish a common pattern for classes. An interface doesn’t contain and code, just function names and constants. It defines the functions that classes implementing the interface must use. In a sense this makes our code self-documenting as well as super flexible, which you’ll see a bit later.
Let’s create the interface, naming it
UP_Unread_Posts_Handler_Interface. “UP” is the prefix for the class which comes from the name of the plugin (Unread Posts). It’s highly unlikely that this interface would clash with someone else’s but it’s best to be on the safe side.
As you can see, all the interface is, is a list of functions. I’ve added some phpDoc to make things even clearer, as much information as needed can be added here. Any object that uses (implements) this interface must define these functions, otherwise errors will be displayed.
I use the convention where each class/interface goes in its separate file. These files are included in the main plugin’s file.
So, how do we get a class to implement an interface? Easy, let’s create our class for interacting with the database.
Simple use the
implements keyword. Don’t forget, that once you do this, you’ll need to define all the functions defined in the interface.
Inheritance is Also Here to Help
Another common construct used in OOP is class inheritance. This is a simple concept but can be hard to implement correctly. I like to think of inheritance as a way to generalize functionality. If we create our two classes – one for database interactions, one for cookie interactions – and we find that a few functions are exactly the same it is a good sign that inheritance could be used.
Let’s discuss how our classes and methods will work, perhaps we can figure out if we should use inheritance or not. I decided early on that the best way to use the classes is to have a
$this->read_posts property that contains an array of IDs for the read posts.
Due to this the
get_read_posts() function’s only job is to return the value of this property. In other words, it will look like this in both classes:
get_unread_posts() is also the same for both classes. What I want to do there is to get all posts from the database, except the ones in
$this->read_posts. This looks something like this:
Note that the only information this method uses which comes from within our class is retrieved using the
get_read_posts() method, which we’ve already discussed is the same for both classes.
Also note that I’ve added a custom filter to the arguments passed to the
WP_Query class, just before it is called. This might be useful for other developers who want to modify the functionality. It would allow them to add a custom post type to the query for example.
Instead of having two classes for our two purposes let’s create a general class. Any function which would be the same regardless of the method of implementation will go in the general class. Any functions that differ from class-to-class would remain in the sub-classes.
I haven’t added the content of any functions just to make sure it’s clear what is happening. The two functions which are the same will be defined in
UP_Unread_Posts_Handler. Other functions will be defined in both sub-classes.
Note that interfaces take inheritance into account. The functions defined in the interface can be defined by the implementing class or any of its parent or sub-classes.
One of the take-home messages here is to stop worrying about what users do. It doesn’t matter (at least at this stage)! We don’t care how read posts are added – the push of a button, the loading of a single post page, etc. The only thing that matters is what you absolutely need to add a post to the read list – which is a simple ID. How that ID gets there is a worry for later.
Another important aspect is writing understandable code. We could get away with calling our
add() and our
remove() but this would be a bit ambiguous. In addition, functions are heavily documented in the source code, even though I’ve left that bit out in the examples here.
The goal is to enable other developers to read our code, much like musicians can read sheet music. You need to know a bunch of things like key signatures, staffs, note lengths and such, but once you learn these, you can read music like a book. It should be the same with code!
This bit is relatively straightforward, we need to code the functions we have and add some new ones in the process.
The Database Handler
Let’s start with the database handler. This class will have three properties.
$meta_fieldwill contain the meta_key used to store the read posts in the usermeta table for each user.
$user_idwill contain the ID of the currently logged in user.
$read_postscontains a list of read posts.
The constructor function – which runs as soon as an object is instantiated – will fill these three properties with data. This is fairly straightforward:
What may confuse you is how the meta field is set. This could simply be hard-coded as
up_read_posts. This would be just fine, but I wanted to make the plugin as extendable as possible. The purpose of the
up/meta_field filter is to let users modify the meta field’s name. If no filters are defined
up_read_posts will be used.
Next up are the three functions which add/remove read posts from the list and delete the whole list of read posts altogether. Let’s start with
set_posts_as_read(), the rest will be easy.
In this function we first sanitize the
$posts parameter. If a single ID was passed we create an array with the ID as it’s first and only member.
To make the plugin efficient we check if the read posts are already all in our read list. We can do this using
array_intersect. The idea is that if the intersection of all our read posts and the posts we want to mark as read is exactly the same as the posts we want to mark read, that means that all members of
$post are in
$this->read_posts which means we don’t need to do anything at all.
If we do need to mark some of them as read we merge the two arrays, weeding out any duplicate items in the process. It is important to do two things here: The new user meta field must be saved and
$this->read_posts must be updated to contain the new items, in case we want to use this property later on during the execution of the page.
The remaining two functions –
delete_read_posts() – are relatively simple affairs, here they are:
The Cookie Handler
The cookie handler only needs two properties:
$cookie_namewill contain the name of the cookie used to store the read posts.
$read_postscontains a list of read posts.
The class constructor sets the values for these properties in much the same way, here’s the code:
There are two important points here. The default name of the cookie will be
site-title is the actual site title, sanitized with dashes. Since the plugin may be used on multiple websites if we simply use
up-read-posts the chances of marking posts as read on two different sites is higher.
There may still be two separate sites with the same title using this plugin – this could cause an issue. It just occurred to me that if we used the domain name instead of the site title, we can pretty much sidestep the issue. Oh well, as you can see code can always be improved!
The second point is the way the cookie is retrieved, specifically:
explode( ', ', gzuncompress( $_COOKIE[$this->cookie_name] ) ). This is due to how the value of the cookie will be saved.
A comma separated list of IDs would be idea to use as the cookie value but they could end up taking up a lot of space. Due to this, the
gzcompress() function is used to compress the string. The
explode() function simply takes the comma separated list and converts it into a nice array.
The remaining three functions that set/remove read posts and delete them altogether are much the same. The set/remove functions are actually exactly the same, except they use
setcookie() at the end to save the data, as does the
delete_read_posts() function to remove the cookie altogether.
Creating Our Object
At this stage we have an interface, a main handler class and two sub-classes that take care of the details of working with read posts. It is now time to use what we’ve created. I think this is actually the hardest part – understanding how to create our objects.
The easiest way to get going would be to simply use the classes we have directly, something like this:
This would work just fine, but is a little bit less flexible than I’d like it to be. I also need to create another class which will handle other operations like creating the unread posts widget, and creating the settings page. This class will make use of our handlers of course, so let’s create it now!
Coding to an Interface
For the time being, this class declares a single property –
$handler which will be an instance of one of our handler classes which must be passed to it in the constructor. I’ve type-hinted this to make it super-clear what is expected:
__construct( UP_Unread_Posts_Handler_Interface $handler = null ).
Notice that we do not expect any particular class. We expect any class which implements our handler interface. This is an extremely powerful practice know as “coding to an interface”. It de-couples our application from any specific implementation.
As long as an object is passed that implements the
UP_Unread_Posts_Handler_Interface interface, we’re all good. This enables us to pass wither one of our pre-existing classes in addition to accepting any classes created by third parties.
This would allow developers to create a class that uses HTML 5 Local Storage for example without ever having to touch our existing code.
At the end of the day we use a single function defined in the main plugin file to get our functionality going. Let me show it to you first and explain after.
When plugins have been loaded we fire the
up_initialize_plugin() class. We immediately define a global variable –
$up_unread_posts. Once we’re done, this variable will be an instance of our handler object, available globally to anyone.
Next, we determine which handler we want to use. If the user is logged in we use
UP_Unread_Posts_Handler_DB, otherwise we use
The next filter is crucial in allowing others to pass their own handlers. The
up/handler filter allows others to overwrite the post handler and pass it to the
Next we create an instance of the
UP_Unread_Posts class, passing in a new handler object. This object can be accessed via the
$handler property within the object. We make
$up_unread_posts equal to this to allow others to utilize the functionality.
I make use of the
UP_Unread_Posts class in the plugin quite a bit, I’ll show you a glimpse of this in a moment. For now however we are done with our basic implementation!
$up_unread_posts variable now contains our handler and can be used anywhere – within a theme or another plugin. Users don’t have to worry about which handler is being used. All they need to know is that they can use the
set_posts_as_unread, etc. methods.
Leveraging Read Post Functionality
To show how easy it is to use what we’ve built, let’s create two new plugins which will interact with our base plugin.
Automatic Read Posts
Let’s automatically add an article to the read list if the user visits the single article page. This functionality is actually exist in the plugin by default, but let’s assume it doesn’t and create a new plugin.
Here’s the complete code for a plugin that would add a post to the read list automatically, provided that the Unread Posts widget is active.
Simple right? Note that I’ve used the
wp hook which may not be the obvious choice. The reason I chose this was that we need to use something which is fired before any HTML output is sent. This is due to the use of
setcookie which should always be called before HTML output.
Unread Posts Widget
This is also a part of the actual plugin, but let’s forget about that for now and create a new plugin for it. Again, the full code:
While this may seem like jumping through hoops at first, getting your head around an object oriented mindset is one of the best things you can do. It actually takes me less time to create the plugin this way than to use procedural code.
This is because there is very little thinking involved, which is a good thing. The thinking is expended in the planning phase, from then it’s just a matter of programming the functions you need.
An object oriented style will make sure your code is flexible, future proof and a joy to work with for other coders.
If you’d like to take a look at the full source code of Unread Posts, take a look in the WordPress Repository or the Github Repo. There are a few more hooks in there and functionality for adding unread posts below single post content but we’ve gone through the most important parts.
Image credit: Moyan Brenn on Flickr.Tags: