Advanced WordPress Development: Writing Object-Oriented Plugins
There are a lot of benefits to learning object-oriented programming if you’re a WordPress developer. OOP code can help organize your code and make it reusable. It’s more extensible, easier to maintain, and encourages a culture of using design patterns.
This is the second post in our six-part series focusing on WordPress for advanced developers. This series follows on from our popular WordPress Development for Intermediate Users, which introduced you to some meaty coding topics, including theme development in detail, making themes customizer-ready, building plugins, custom post types and taxonomies, queries and loops, custom fields and metadata, and localization.
Last week we looked at the basis of OOP. In this tutorial, you’ll start building plugins with your newfound knowledge of object-oriented programming techniques. We’ll start with just using it to make structural changes to code using the basics of the object approach.
Third party code in plugins and themes often employ a lot of “spaghetti” – also known as “cowboy” – coding compared to platforms that use OOP. By the time you finish reading this post, I hope you’ll be inspired to give OOP a go and clean up your code!
Let’s get stuck in.
Note: It’s important that you have a working knowledge of PHP as this is the foundational language of WordPress for this series, which covers advanced topics aimed at developers. I’ll be referring to code snippets throughout this series. It’s also important, for this tutorial in particular, that you have a basic knowledge of how to create a plugin. Need some help? Check out Getting Started with WordPress Plugin Development: The Ultimate Guide.
Writing Object-Oriented Plugins: The Project
For the purposes of this lesson, let’s create a plugin that will add a subscription form after the content of our single posts. The subscription form will send its data straight to MailChimp. This is a simple affair, which could easily be done with procedural code but begs for an OOP approach.
This is a project where extendability is easy to see. You could add the form in other locations, you could implement it to work with other APIs like MadMimi, or even to add the data to the local database.
You’ll see how easy this is later on.
Let’s look at the full procedural code first, understand what’s going on, and then do some restructuring to add some OOP magic.
There are two things we need to do: create a form on the front-end and send the entered data to MailChimp. The
msf_post_form() function takes care of the former. It is hooked unto
the_content and returns the content as is if we’re not on a singular page.
Otherwise, we build a form that submits to the
admin-ajax.php file. I’ve added two security elements to the form. The most obvious one is the nonce field, the more sneaky one is the honeypot name text field. This field isn’t used and is hidden via CSS. I’ve deliberately given it a frequently used name (as opposed to “honeypot”) to lure bots into filling it out.
msf_assets() function adds the stylesheet for us. Note that I register it in this function but I enqueue it in
msf_post_form(). This ensures that it is only loaded when needed.
msf_form_submit()> function is responsible for handling the submission. Once the nonce and honeypot are verified we send an remote post request to the appropriate location with the required data. Check out the MailChimp API docs for more information.
There are a couple of additional checks we could (should) perform but this isn’t the main focus of this tutorial so I’ll just list them here.
- Checking the email server-side as well
- Matching the server (us6) in the URL with the server in the API key
I’ve added some basic styling to make the form fit into the Twenty Fifteen theme nicely.
This code is just fine for now, but when you try to add features it quickly becomes a lot more complex. How would you handle multiple lists? What about adding the same form to other parts of the site? What if you wanted to quickly switch between mailing list providers?
You would end up with a whole lot of if statements in your code making it hard to follow and hard to maintain, not to mention some lengthy function names you’d need to come up with. I also find that putting all our actions all over the place is detrimental to code quality. Let’s fix some of these issues with our basic OOP knowledge.
Rewriting With OOP in Mind
Let’s rewrite from scratch and copy-paste existing code when needed. For most projects I like to create a wrapper class that encapsulates the full functionality. Here is the basic skeleton.
I’m passing a configuration array to the new object. This will make it easier to set things up for different environments if needed. I can modify the array and the class will use the information in there – similarly to how
wp-config.php works for WordPress.
The constructor function runs as soon as the class is created which means that this is the place to put all our actions and filters:
I find this structure a lot better because it is more self-documenting. You can see all the points where the class plugs into WordPress in one place.
Note that when you use hooks in classes you can’t simply specify the name of the function, you need to use the
array( $this, 'name_of_function' ) convention. This is because the function we are referring to is within the class, not in the global space. I’ve also removed the function prefixes and simplified their names making their purpose easier to ascertain.
All that remains is plopping these functions into the class’ body. The two places where you’ll notice changes is the function names (which have been simplified) and the use of the API key which is now taken from the
$api_key property. Here’s the full OOP code:
The code is now a lot more easy to understand, better structured and more extendible.
There is a whole lot we can do to extend our code. Let’s focus on two aspects: controlling the form fields and adding other providers.
Depending on our needs there could be a couple more form fields. We could hard-code them in every time we need a change but I prefer to use hooks to control such things. Remember that even if you don’t release your work to the public you may use it in multiple projects yourself. Making it easy to extend is an investment into decreasing your workload over time.
To get started I’ll separate everything the form must have from optional components. This is a great place for a quick discussion! Would you consider the email field a must, or is it optional for your application?
It could go either way. If you will always use this implementation for modifying recipient lists then it may be a good decision to hard code it. If you might extend it to modify newsletters, create new lists – processes where a single email address is not needed – you should consider it options. For the purposes of this example I’ll be considering it optional but including by default.
Here’s how the code of the
form() method changes with the above in mind:
I’ve created a
$defaults variable which holds an array of field HTML. The only one I want to enforce as a default is email, so I’ve added that. I follow that by applying a filter to the default array. The filter allows me to extend functionality easily by adding as many fields as needed.
To add an “age” field, simply use the filter either from your own plugin or another. To add an age field from another plugin you can do something like this:
Adding Another Provider
Another aspect of the application that is ripe for extension is the provider. The ability to choose between MailChimp, MadMimi or other email list providers would be awesome, let’s get to it.
Instead of using if statements in our submission handler – making it super-long – let’s use a separate function that launches the correct submission handler. Let’s begin by rewriting our config array:
Looking at this array you might think: “why is this so complex? Couldn’t the provider array just be a list of key value pairs where the key is the provider and the value is the API key?”. The answer to that is: sure it could!
The reason I chose to go one level deeper is forward thinking. We’re not really going into authentication here whereas most APIs require it. That means that we’ll be needing at least two bits of info for each provider. In addition, we might want to streamline the API calls later, adding the base URLs and other info into this array.
Right now we’ll need to rewrite our code a bit to work with our new providers array. Since we attempted to think forward, future changes will likely not require alterations to existing code.
Now that our config array has a different structure we need to update the constructor. Instead of an
api_key property I’ll use
providers which will contain the info of every provider we use.
Next, let’s rename
mailchimpHandler() and make sure it references the api key in our new
providers property. The change can be seen in the
wp_remote_post() function, take a look below:
Finally, we need to launch the correct handler at the correct time. To do that, let’s alter our config array one last time, including the handler we want to use.
I’ll also add the provider to the properties within our class by assigning it in the constructor:
When someone submits the form the
submissionHandler function is still called, however it no longer exists because we renamed it to
mailchimpHandler. Let’s re-create the submission handler function now:
Our submission handler checks if there is a method to process the action. It looks for the name of the provider appended with “Handler”. If the method is not found it displays a notification. Otherwise it executes the method.
The Full Code
Since we made a lot of modifications I’m posting the full code for the plugin below. Try not to copy-paste the code below; arrive at it using the modifications instead as it will give you a better understanding of what’s going on.
Working with OOP takes some getting used to but once the penny drops you’ll be fluent in it! I struggled with it on my first go as well so don’t get discouraged if you need a couple of passes at it before it clicks.
Finally, there are a fair number of possible additions and there are better OOP practices you could use to make the code even better. If you feel proficient in the material we’ve covered so far I urge you to take a look at Interfaces, which you could use to add much more modular integration with third party services.