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 CalderaLearn\RestSearch; /** * Class FilterWPQuery * * Changes WP_Query object during REST API requests * * @package CalderaLearn\RestSearch */ 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:
- We eliminate the inline comment as the method’s name tells us what it’s doing.
- 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 CalderaLearn\RestSearch; 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:
- 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. - Next Loop: The loop increments
$postNumber
to 1. The code creates a new post. It’s assigned it to element 1 in the array. - 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.”
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.
No Comments