Code Review Part 3 – Building & Refactoring the Posts Generator

In the last article, I walked you through a code review and refactoring process for the FilterWPQuery class in Josh Pollock’s plugin.  We made his class more readable and performant. But we did not cover the posts generator code in the getPosts() method; rather, I left that review for this article. Why? There are two reasons.

First, I wanted to give us time to thoroughly review this section of code. There are several areas that we can improve. This code provides a learning opportunity to improve your own code as we dive into code style, PHP namespace importing, string processing, and more.

Second, the design of this class and specifically this method can be made flexible for real-world applications. Josh notes that this plugin is for educational purposes. Let’s use this opportunity to explore polymorphism and how to convert this code into a handler for flexible implementations.

In this article, you and I will do a thorough code review of the posts generation code.  Then in Part 4 of this series, we’ll dive into making this class more flexible.

To refresh your memory, this is the starting posts generator code:

<?php
namespace CalderaLearnRestSearch;
/**
 * Class FilterWPQuery
 *
 * Changes WP_Query object during REST API requests
 *
 * @package CalderaLearnRestSearch
 */
class FilterWPQuery implements FiltersPreWPQuery
{
  // Code left out for brevity.
  
  	/** @inheritdoc */
	public static function getPosts() : array
	{
		//Create 4 mock posts with different titles
		$mockPosts = [];
		for ($i = 0; $i <= 3; $i++) {
			$post = new WP_Post((new stdClass()));
			$post->post_title = "Mock Post $i";
			$post->filter = 'raw';
			$mockPosts[$i] = $post;
		}
		//Return a mock array of mock posts
		return $mockPosts;
	}
}

Posts Generation is Just One Implementation

When you read the above code, notice the inline comment.  Why does it exist? Think about it.

It’s there because the code is actually generating posts, albeit mocked posts. It’s a generator, i.e. it builds posts and returns them back. It’s just one implementation that is possible when filtering posts for a search request.

Josh designed the getPosts() method as an arbitrary example of generating mock posts as a learning exercise. I get that.  But as a learning exercise, we can focus our attention on the intent of this method and begin to think about a real-world application.

I want you to think about the more broad scope of handling a search request.

When filtering the posts for a search request, you will likely need to do some processing such as fetching, sorting, assembling, and/or even generating. This “processing” depends upon your project’s needs and business rules, as well as the specific search request itself. That means we need different implementations to handle each of these request scenarios in order to send back the relevant posts for the search request.

We’ll dive into the concept of designing for different implementations in the next article. But here, let’s generally agree that when filtering a search request, we need the flexibility to handle different business rules in our project.

Do you agree? If no, let’s talk about it. If yes, then we accept that the getPosts() method should be able to handle different needs for filtering posts.

The code that is within this method is just one implementation, i.e. one way of handling the posts filtering. Your real-world application will need different implementations, possibly multiple scenarios in the same project.

Therefore, the task of generating posts needs its own implementation.  In our current design, that means abstracting it to a separate method.  In doing so, here’s what happens:

  1. We eliminate the inline comment as the method’s name tells us what it’s doing.
  2. We set up the getPosts() method to be flexible.

Let’s refactor.

Abstract the Posts Generator

Let’s create a new private method called generatePosts().  The name of this method tells us that it will generate posts.

Since we are creating a new method, a better strategy is to tell it how many posts you want it to build for you.

/** @inheritdoc */
public static function getPosts() : array
{
	return static::generatePosts(4);
}

/**
 * Generates an array of mocked posts.
 *
 * @param int $quantity Number of posts to generate.
 *
 * @return array
 */
private static function generatePosts($quantity) : array
{
	$mockPosts = [];
	for ($i = 0; $i < $quantity; $i++) {
		$post = new WP_Post((new stdClass()));
		$post->post_title = "Mock Post $i";
		$post->filter = 'raw';
		$mockPosts[$i] = $post;
	}

	return $mockPosts;
}

Import Classes Into Current Namespace

PHP gives us the ability to import classes and functions that are in a different namespace.  While we could use the full namespace in our code, it makes the code less readable. Even when the class is in the global namespace, the backslash is distracting when we read the code, as it adds another character. They clutter up the code.

A better approach is to import each into the current namespace using the keyword use.  By doing this, we can use the class anywhere in our namespace without the preceding backslash.

<?php
namespace CalderaLearnRestSearch;

use stdClass;
use WP_Post;

// Code left out for brevity

      $post = new WP_Post((new stdClass()));

Make the Loop’s Indexer Tell Us What It Represents

We all know that $i represents the current value of a loop’s index. It’s a common naming convention. However, when that variable is used in the code, it’s a better practice to give it an expressive, descriptive name to tell you what value it represents within its given context.

When you read $i in the generator code, what does it mean to you?  Is the value the number of the loop?

No, here in this context, it represents the post’s number.  Yes, it is an indexer, but since we are including it as part of the post’s title, it tells us the post’s number.

Then let’s call it $postNumber:

for ($postNumber = 0; $postNumber < $quantity; $postNumber++) {
  $post = new WP_Post((new stdClass()));
  $post->post_title = "Mock Post $postNumber";
  $post->filter = 'raw';
  $mockPosts[$postNumber] = $post;
}

Array Index is Unnecessary & Less Performant

In this line of code, the specifying telling PHP to create the post at this index position is unnecessary:

$mockPosts[$postNumber] = $post;

Why?

What position (key) does an indexed array start at when adding elements into the array?  Zero. Indexed arrays start at position 0.

Look at the code.  What is the starting post number, $postNumber, in the loop?  Zero.

Then as the loop iterates, what happens to the $postNumber?  It increments up by one.

How about an indexed array? What happens each time you add another element into the array like this $array[] = $someValue;? Internally, PHP increments to the next index to add the element.

Do you get where I’m going with this?  The loop indexes the variable and PHP indexes the array.  The array’s index matches the loop’s index.

Let me help you visualize what’s going on:

  1. First Loop:  The loop starts and $postNumber is 0.  The code creates a new post.  It’s assigned it to element 0 in the array.
  2. Next Loop:  The loop increments $postNumber to 1.  The code creates a new post.  It’s assigned it to element 1 in the array.
  3. Repeat until done.

It’s unnecessary to tell it to put the post into a specific index point within the array. Why? PHP does it automatically for us.

Therefore, we can refactor the code like this:

$mockPosts[] = $post;

Why is this a better strategy?

First, it’s less code to read.  That means we don’t have to try and figure out if the indexed array could be out of sync with the loop.

Second, it’s faster. Why?  PHP does not have to look up the value bound to that variable before adding the new post element.  It’s one less step to be processed.

Tell PHP Where Embedded Code Starts and Ends in a String

PHP needs your help to quickly identify the variable or code that is embedded inside of a string. How do you do this? By wrapping the variable (or code) inside of curly braces.

The opening curly brace tells the PHP parser: “Hey, this is the start of an embedded variable.”  PHP keeps reading until it gets to the closing curly brace. Using the curly braces, you are declaring that this is an embedded variable that needs to be processed to get the value to insert into the string.

Why?

Imagine that the title had another character after the variable. How would PHP know that the variable is $postNumber and not that plus the other character(s)?

The parser is greedy.  Strings are comprised of different alphanumeric characters.  How does PHP know where a variable or code starts and ends? We can help it out by using the curly braces to explicitly tell it our intent.

There’s an additional benefit.  When you and I read this string, the wrapped code jumps out at us.  It catches our attention, alerting us that this needs to be processed.

$post->post_title = "Mock Post {$postNumber}";

Code Tip: Standardize using this technique.  It will keep your code consistent and eliminate the need to figure out if you should wrap it or not.  Just wrap it.

Code Style and Formatting Matter

Code style is an important component of quality and readability.

”Code formatting is important….The functionality that you create today has a good chance of changing in the next release, but the readability of your code will have a profound effect on all the changes that will ever be made….Your style and discipline survive, even though your code does not.”

Robert C. Martin, Clean Code: A Handbook of Agile Software Craftsmanship

In our standards, we define how our code should be named, constructed, and formatted.  Many of the editors and IDEs we use can be configured to let us automatically reformat an entire file to our defined standard.

There are multiple items that need clean up in our new method.  Let’s walk through them together.

Remove Unnecessary Parentheses From Post Instantiation

Right now, the code has a double set of parenthesis around the creation of a new standard object.  We only need one set.

I’d also suggest adding spacing within the parentheses to give more emphasis to the new object.

$post = new WP_Post( new stdClass() );

Align Grouped Assignments

Aligning a group of assignments allows us to quickly recognize that these are assignments.  It draws our attention to the equals sign, where we can separate the work from the variable. It makes code more readable by communicating: “Hey, these are all assignment operations.”

Let’s Review

Here is our refactored method to generate posts:

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;
}

I covered a lot in this article.  Let’s summarize what we did and identify how it improves the code’s readability and/or performance:

The improvement Readability Performance
Created a new private method for the posts generator code
Imported the classes into the current namespace
Renamed the loop’s indexer variable
Removed the array indexer
Wrapped the embedded variable within the string
Improved the code style and formatting

The final code and each refactoring step is documented in the Pull Request on GitHub.  I invite you to explore it.

Let’s Discuss It

What do you think? Do each of these improvements make sense to you?  No, really, I want to hear what you think.

From the step-by-step walkthrough, do you see how to implement each of these strategies in your own code?

I look forward to discussing this review and refactor process with you.  Feel free to ask me any questions and share your opinions in the comments below.

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 3 – Building & Refactoring the Posts Generator appeared first on Torque.