In the last article, we identified that the task of generating posts, albeit arbitrary, is just one possible implementation of getting content for a search query. As such, we split the tasks of “getting content” and “generating posts” into separate methods. This design sets us up for a discussion about designing swappable search request implementations.
Before we get started, I want to remind you that Josh designed this plugin for educational purposes in order to teach you Advanced OOP and testing. As you and I work through the code review together, the tasks of “getting content” reveals itself as a perfect opportunity to go deeper into OOP by exploring how to architect for reuse.
Let’s start with the need and then work our way into the design concepts.
Exploring The Needs
Think about a real-world search processing application. Each project will likely require different needs for getting the search’s content.
One project may need to do some fetching and processing work before returning it back. Another project may need to do some sorting and assembling tasks. Project-to-project, the specifics of how to handle “getting content” could be different based upon the site’s content mapping and business needs.
How can you make the FilterWPQuery
class more flexible to accommodate these different project needs without increasing our costs or risks?
Let’s brainstorm our design goals to meet our needs:
- We want to build
FilterWPQuery
once and then reuse it over and over again on each project. - We want a way to isolate our project’s business needs for “getting content” for the search query.
- We want those “needs” to be swappable implementations.
- We want to reduce our costs and risks.
Exploring the Architectural Concept
I want you to picture this architectural concept in your mind. Imagine that the FilterWPQuery::getPosts()
method is able to call a standardized method on a generic $contentGetter
object. Imagine that this generic object represents the project’s implementation, meaning that we can swap out implementations from project-to-project.
Think about the power of this concept.
FilterWPQuery::getPosts()
method doesn’t care what the implementation is; rather, it just tells this generic object: “Hey, give me the search content.”- It uses a standardized method to tell the object, i.e. let’s call this method
getContent()
. - Each implementation has this standardized method in it, which internally wires up to handling its own tasks for “getting the content.”
This design makes FilterWPQuery()
reusable.
What else does it do? Think about it.
It allows you to create a new implementation for each project and then wire it back to FilterWPQuery()
. It creates a swappable and flexible design.
Wait there’s another advantage. It provides a mechanism to reduce your costs and risks. I think we need to talk about why this is important.
Why Reuseable Is Important
It’s time for a sidebar discussion on why reusability is important to your team and business.
Imagine if you were to hard-code each implementation into FilterWPQuery::getPosts()
. What happens on the next project? You need to change the implementation. That means you are manually changing the FilterWPQuery::getPosts()
class. That seems innocent enough. But it has hidden costs and risks lurking beneath it. Why?
The class has other baseline functionality built into it such as hooking and unhooking into WordPress as well as deciding whether or not to filter the search request.
Let me ask you a question.
What happens down the road when you find a bug or something changes requiring a change in this baseline code? Think about the projects you’ve already shipped with this same code.
If a change occurs, you’ll need to update all of those other projects. But if each class is different, you can’t push one change to fix all of them. Whoops. That means you need to manually update each project. That’s time-consuming.
But there’s one more costly problem with this manual approach. Manually making bug fixes across multiple projects increases the likelihood of making a typo and introducing another bug.
Hard-coding each project’s implementation for getting content is a bad strategy. It will cost your company money in the long run to support and maintain all of your projects.
A better strategy is to isolate the customizations between projects, designing your codebase to be flexible and reusable while providing the ability to swap different implementations.
Exploring How to Make it Reusable
How can we get FilterWPQuery::getPosts()
to call a generic object? We can leverage the power and flexibility of polymorphism.
Stick with me as I walk you through a thought experiment.
Imagine that there’s a way to set a strict standard that defines how to interact with multiple implementations. For this to be reusable, we would need a way to strictly define each method for the interaction and then force each implementation to implement the method(s).
Are you still with me? I think to help you visualize the design we need to circle back to our code.
Let’s say that we want our FilterWPQuery::getPosts()
to always call the same method regardless of which implementation is wired to it. Let’s call this standard method getContent()
. Then each implementation on every project will have a getContent()
method. Internally, each implementation handles its own custom work for “getting the content.”
But wait a minute. How do we force this strict standardization? In PHP OOP, we use an interface.
What is an interface? An interface is a contract that defines how to interact with each object that implements it. This contract establishes a strict standard.
An interface is a contract that defines how to interact with each object that implements it by establishing a strict standard.
Conceptually, the interface is the glue that binds our code together, allowing us to isolate project-specific, custom implementations while reusing the FilterWPQuery
class over and over again.
It’s time to look at some code.
Injecting Different Implementations
Let’s walk through the process of refactoring the code to be more reusable per what we’ve been discussing in this article. We’ll need to:
- Add the contract.
- Make the posts generator an implementation.
- Wire up the contract:
- Initialize
FilterWPQuery
and wire the project’s implementation to it. - Change
getPosts()
to call the implementation through the contract.
- Initialize
- Inject the implementation upon plugin launch.
Step 1: Defining the Contract
Let’s start by building the contract:
<?php namespace CalderaLearnRestSearchContentGetter; /** * Defines the contract for each content getter implementation. * @package CalderaLearnRestSearchContentGetter */ interface ContentGetterContract { /** * Handles getting the content for the search query. * * @param int $quantity Number to get. * * @return array */ public function getContent( $quantity = 4 ): array; }
Notice that our contract has one method getPosts()
and this method accepts the quantity and returns an array of content.
Step 2: The Posts Generator Implementation
Let’s use this contract and build the posts generator as a separate implementation. To do this, we implement the contract, add the method, and then move the posts generator code from FilterWPQuery
to this new class.
generatePosts($quantity); } /** * Generates an array of mocked posts. * * @param int $quantity Number of posts to generate. * * @return array */ private static function generatePosts($quantity): array { $mockPosts = []; for ($postNumber = 0; $postNumber post_title = "Mock Post {$postNumber}"; $post->filter = 'raw'; $mockPosts[] = $post; } return $mockPosts; } }
Step 3: Wire Up the Contract to FilterWPQuery
Next, we need to wire up the implementation to the search query:
getContent(); } }
Let me explain what’s happening here:
- We specific a static property called
$contentGetter
to hold the implementation. This is our generic content getter object. - We add an initialization method called
init()
and inject the project’s specific content getter’s implementation. - We use this generic property
$contentGetter
and then invoke the standard methodgetContent()
.
Notice that we type hint with the contract and not the implementation class name. Why? We want to make the content getter generic, such that FilterWPQuery
does not care which implementation you use. Huh?
All implementations implement the contract. Right? What’s common about each of the implementations? The contract.
In PHP, we can type hint via the contract. That allows us to inject any of the implementations. Does that make sense? If no, ask me below in the comments.
Step 4: Bind the Implementation on Plugin Launch
Finally, we need to bind the implementation for our project to FilterWPQuery
. Let’s do that at plugin launch in the plugin’s bootstrap file:
addHooks(); });
Let’s Review
In this article, you and I walked through a thought experiment of how to think about designing a flexible architecture by:
- Separating the custom implementations.
- Making
FilterWPQuery
not care about which implementation is used.
You learned about:
- PHP interfaces are a contract between a caller and each implementation.
- A contract defines the strict interface of how to interact with an implementation.
- How to leverage polymorphism by injecting the implementation your project needs.
We did this exercise to develop how you think about building code for reuse. By designing separate implementations for the parts of the code that will likely change from project-to-project, you are designing for reuse. You then leverage these swappable implementations.
The result is you reduce your costs and risks while making your projects more flexible.
The final code and each refactoring step is documented in the Pull Request #5 on GitHub. I invite you to explore it.
Let’s Discuss It
What did you think? No, really, I want to know what you think. I’m inviting you to ask questions, share your opinion, or share some insight in the comments below.
Do you see the advantages of designing for reuse? Through this thought experiment and refactoring walkthrough, does it make sense of how to separate the implementations from the reusable parts of your codebase?
I look forward to discussing reusability with you.
The post Designing Swappable Search Request Implementations – Code Review Part 4 appeared first on Torque.