Code Review Part 4- 4 Designing Swappable Search Request Implementations

0
58

https://ift.tt/2rLbRGL

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:

  1. We want to build FilterWPQuery once and then reuse it over and over again on each project.
  2. We want a way to isolate our project’s business needs for “getting content” for the search query.
  3. We want those “needs” to be swappable implementations.
  4. 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.

  1. FilterWPQuery::getPosts() method doesn’t care what the implementation is; rather, it just tells this generic object: “Hey, give me the search content.”
  2. It uses a standardized method to tell the object, i.e. let’s call this method getContent().
  3. 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.

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:

  1. Add the contract.
  2. Make the posts generator an implementation.
  3. Wire up the contract:
    1. Initialize FilterWPQuery and wire the project’s implementation to it.
    2. Change getPosts() to call the implementation through the contract.
  4. Inject the implementation upon plugin launch.

Step 1: Defining the Contract

Let’s start by building the contract:

<?php
namespace CalderaLearn\RestSearch\ContentGetter;
/**
 * Defines the contract for each content getter implementation.
 * @package CalderaLearn\RestSearch\ContentGetter
 */
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.

<?php
namespace CalderaLearn\RestSearch\ContentGetter;
/**
 * Handles generating posts for the search query.
 * @package CalderaLearn\RestSearch\ContentGetter
 */
class PostsGenerator implements ContentGetterContract
{
	/**
	 * Handles getting the content for the search query.
	 *
	 * @param int $quantity Number to get.
	 *
	 * @return array
	 */
	public function getContent($quantity = 4): array
	{
		return $this->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 < $quantity; $postNumber++) {
			$post             = new WP_Post( new stdClass() );
			$post->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:

<?php
namespace CalderaLearn\RestSearch;
use CalderaLearn\RestSearch\ContentGetter\ContentGetterContract;
/**
 * Class FilterWPQuery
 *
 * Changes WP_Query object during REST API requests
 *
 * @package CalderaLearn\RestSearch
 */
class FilterWPQuery implements FiltersPreWPQuery
{
	/**
	 * Content Getter Implementation.
	 *
	 * @var ContentGetterContract
	 */
	protected static $contentGetter;
	// code left out for brevity
	/**
	 * Initialize the search filter by binding a specific content getter implementation.
	 *
	 * @param ContentGetterContract $contentGetter Instance of the implementation.
	 *
	 * @return void
	 */
	public static function init(ContentGetterContract $contentGetter)
	{
		static::$contentGetter = $contentGetter;
	}
	/** @inheritdoc */
	public static function getPosts(): array
	{
		return static::$contentGetter->getContent();
	}
}

Let me explain what’s happening here:

  1. We specific a static property called $contentGetter to hold the implementation.  This is our generic content getter object.
  2. We add an initialization method called init() and inject the project’s specific content getter’s implementation.
  3. We use this generic property $contentGetter and then invoke the standard method getContent().

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:

<?php
// code left out for brevity
/**
 * Launch the plugin.
 */
add_action( 'init', function(){
	FilterWPQuery::init( new PostsGenerator() );
	( new Hooks() )->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:

  1. Separating the custom implementations.
  2. Making FilterWPQuery not care about which implementation is used.

You learned about:

  1. PHP interfaces are a contract between a caller and each implementation.
  2. A contract defines the strict interface of how to interact with an implementation.
  3. 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.

With over 3 decades of high-tech, enterprise engineering experience, Tonya is on a mission to develop professional WordPress developers and engineers at Know the Code, unlocking each person’s potential and empowering each to excel, innovate, and prosper.

The post Code Review Part 4- 4 Designing Swappable Search Request Implementations appeared first on Torque.

Vía Torque https://ift.tt/2wM2T19

NO COMMENTS

LEAVE A REPLY