Had a client come to me with a ‘quick job’ the other day. They have a super active blog, great community, and they wanted to highlight reader comments inside their posts with a simple shortcode. Easy enough. But the problem wasn’t the feature itself, it was the code I inherited. The previous dev had mashed all the HTML generation right inside the shortcode function. A big, ugly string of concatenated divs and spans.
This is a classic way to create a maintenance nightmare. As soon as you need a simple tweak—”can we add an avatar?”—you have to dig through PHP logic to change a snippet of HTML. It’s messy, it’s brittle, and it’s how you get spaghetti code. We’re going to fix that by properly handling WordPress generate HTML tasks.
My first thought, I’ll admit, was just to clean up the existing function. Maybe use a `sprintf` to make it a little more readable. But then the change requests started piling up. “Can we have a different layout for comments by the post author?” “Can we add a ‘Verified Reader’ badge?” My ‘simple’ function was about to explode into a dozen `if` statements. Total mess. That’s when you know you have to back up and decouple your logic from your presentation.
The Right Way to WordPress Generate HTML
Instead of cramming everything into one function, we’ll create a dedicated class whose only job is to generate the HTML. This is a much cleaner approach, and it’s a concept that builds on a great idea I saw over at carlalexander.ca. The idea is to separate the “what” (the data, like the comment object) from the “how” (the HTML output). Here’s the kicker: you use a separate template file for the HTML itself.
This makes life easier for everyone. The PHP dev can work on the class, and a front-end dev can modify the template file without ever needing to touch the logic. Let’s see what that looks like.
class MyPlugin_CommentGenerator
{
private $template_path;
public function __construct($template_name = 'highlighted-comment.php')
{
// Allow themes to override the template
$located = locate_template('myplugin/' . $template_name);
if (!empty($located)) {
$this->template_path = $located;
} else {
$this->template_path = __DIR__ . '/templates/' . $template_name;
}
}
public function generate(WP_Comment $comment)
{
if (!is_readable($this->template_path)) {
return '<!-- Template not found -->';
}
ob_start();
// Make the $comment object available to the template file
include $this->template_path;
return ob_get_clean();
}
}
// And the shortcode function becomes clean and simple:
function myplugin_display_comment_shortcode($atts) {
$a = shortcode_atts(['id' => 0], $atts);
$comment_id = (int)$a['id'];
if (empty($comment_id)) {
return '<!-- Missing comment ID -->';
}
$comment = get_comment($comment_id);
if (!$comment instanceof WP_Comment) {
return '<!-- Invalid comment -->';
}
$generator = new MyPlugin_CommentGenerator();
return $generator->generate($comment);
}Look at how clean that shortcode function is now. All it does is handle the data—getting the comment ID and fetching the comment object. It then hands off the rendering job to our `MyPlugin_CommentGenerator` class. No HTML in sight.
So, What’s the Point?
The point is to write code that you, or another developer, can actually maintain six months from now. Separating your PHP logic from your HTML templates is a fundamental principle that saves you from future headaches. This isn’t just for shortcodes. It applies to:
- Custom widget outputs
- AJAX/REST API responses that return HTML fragments
- Complex meta box displays
Thinking in terms of templates and data models forces you to write better, more organized code. Period.
Look, this stuff gets complicated fast. If you’re tired of debugging someone else’s mess and just want your site to work, drop my team a line. We’ve probably seen it before.
Leave a Reply