In this series on applying advanced object-oriented programming (OOP) principles to the PHP code we use in WordPress plugins, we are creating a plugin that modifies how REST API routes work to improve their search capabilities. One of the great things about WordPress is its extensibility.
In this post, I’m going to walk through using the improvements made in the last article. In this series to make the plugin extensible, Tonya Mork of Know The Code is writing code review articles in this series. The last article I wrote was all about making the search system swappable. The changes I’m making here also build off of changes she made to the plugin as part of one of her code review articles. That post really goes into depth about what we mean by “swappable.”
This post, taking advantage of how swappable the default implementations are in this plugin now, makes it extensible. By extensible, I mean other plugins will be able to modify itsbehaviorr without modifying its code. How? With the WordPress Plugins API — hooks. That’s the WordPress way and it works and we can make it testable using mocks, so that’s what we’re going to use.
Creating A Router
When we left off, we modified our main class to pass the current WP_Query and to the class responsible for generating the content to be outputted. Here is what it looked like:
That’s what we need to make this extensible. If you look at getPosts(), it already has an abstraction for setting, storing and getting the WP_REST_Request object that it captures from the plugins API. That object could contain a signal — a query parameter “mode” to switch what search method or “mode” we use.
A “mode” switch would require changing the class that creates the results, which has to implement the ContentGetterContract. In my last post, I explained how FilterWPQuery gained a getter and setter for that object.
What getPosts() is currently lacking is a way to run that setter method right before the Content Getter is used. That’s what actions are for. So my first step was to trigger an action right before retriving the content getter, so we can call the setter at that event:
Now we are emitting that event. We need a system that hooks into it and responds with the default implementation, but can be modified by plugins. Let’s start by making it just do the same thing, so we can prove it still works with our tests. Later we’ll add a filter to make it extensible.
MY next step was to introduce a class to control the search mode. That I called “Modes.” It takes the responsibility of getting the right Content Generator into FilterWPQuery based on the query argument “mode” indicated in the current WP_Rest_Request.
These two tests prove that the same type of object this system always produced is returned. That’s an important step as it always needs to do that by default. This test makes sure that the next step — adding the filter — does not affect the default behaviour of this system.
In the second test, I’m using Mockery to create a mock WP_Rest_Request. This is necessary since the unit tests for this plugin do not load WordPress. I learned how to do this from Tonya’s article on mocking. This is a tool for me, that I’m going to make use of a lot more. Not having an easy way to do mocks is why I often just use WordPress’ test suite, with a WordPress environment instead of unit tests for my tests. Keeping them seperate as this example plugin does is great, but creating a mock for every WordPress class doesn’t scale.
Adding The Filter
Now that we have this class in place, let’s actually make it extensible. That’s the goal here. Let’s walk through that.
For my next commit I added a filter that if it is null, uses this internal router. If its a valid object, that is returned. I proved this with an integration test. WordPress core uses this pattern of “early return filters.” This pattern uses a filter to optionally prevent default behaviour. If nothing changes the return of the filter form null to a valid result, the default core behaviour runs, if its valid output, the default core behaviour is bypassed.
I wrote in my first article in this series about how posts_pre_query acts as an early return filter for WP_Query’s actual querying of the database. We’re using that filter to supply our own results for WP_Query.
Here is the refactored Mode class with the new filter:
Notice that the filter’s name is defined by a class constant. This makes it easier to keep the name of the filter in sync in multiple places. I like doing this, but it doesn’t scale if classes interact with multiple hooks, which I try and avoid, but can’t always do.
One place I used that constant was in a test to prove this filter properly short-circuits the default logic. I used an integration test instead of a unit test to prove this. I chose this strategy, instead of testing with a mock, because it demonstrates how a key part of an add-on implementation would work. While tests are no replacement for documentation, tests often time serve as an example of how the code should be used.
This kind of integration test is less clean than a unit test with mocks. I’d honestly prefer both, you should add a unit test if you want to try all of this out and contribute it with a pull request to the example plugin.
If I have to choose one, I’m going with an integration test here because it’s the most honest test. And until I get to docs, the test shows how to extend the plugin.
In this test, I actually add the filter using an anonymous function. I run an assertion inside the filter to prove that the arguments supplied are correct. After it runs I assert that it caused the right effects on the
Also, notice that even though WordPress is loaded in the integration test suite, I’m still using a mock WP_Rest_Request. Why? Because I want to control for external effects as much as possible. This test doesn’t test the interaction with WP_REST_Request, so that mock abstracts its role out.
Can You Make An Extension?
Now the plugin is extensible. You can see the code I added to this plugin to make it extensible in this pull request. If you’ve been reading along, you know how this plugin works. You know what the parts that can swap out are, and how the factory works. This article showed you how the plugin is extensible.
A great next step to use what you’ve learned is to create your own add-on for this plugin. @ me on Twitter or leave a comment with what you create. In my next article in this series, I’ll be covering how to test add-on plugins, which will help you in that quest.