Why Your Simple WordPress Plugin Edit Broke Everything

A client hit us up a few weeks back. The request was simple: “Can you add a new size option to our meme shortcode plugin?” Easy enough, right? Then I opened the plugin file and found a single 2,000-line PHP class. Everything was in there: the shortcode logic, the admin page, the database options, the CSS injection. Total mess.

Changing one thing felt like defusing a bomb. You touch one function, and you have no idea what it might break somewhere else. This is a classic symptom of violating the WordPress single responsibility principle. It’s the reason a five-minute job turns into a five-hour headache.

Why We End Up with These Monster Classes

Honestly, it’s not always the developer’s fault. The official WordPress documentation practically encourages you to dump everything into one class to prevent function name collisions. For someone moving from procedural code to OOP, it seems like the logical next step. But it’s a trap.

My first thought was to just hack the new size option in. Add another property, another ‘if’ statement, and call it a day. And yeah, it would have worked… until the next “simple” request came in. The real fix had to be structural. The whole thing needed a refactor.

Applying the WordPress Single Responsibility Principle

The single responsibility principle is the ‘S’ in SOLID. It means a class should have one job, and one job only. A class that handles shortcodes shouldn’t also be rendering an admin page. This approach is a pragmatic take on the great breakdown I first saw over on carlalexander.ca, and it works wonders.

Instead of one giant file, we break the plugin into classes with clear responsibilities.

  • Plugin.php: The main entry point. Its only job is to load the other components.
  • Options.php: Handles all interactions with the database—getting and setting options. Nothing else.
  • AdminPage.php: Responsible for rendering the settings page in the WordPress admin.
  • Shortcode.php: Handles registering the shortcode and generating its output.

Here’s the kicker. When each class has a single job, the code becomes predictable. The `Shortcode` class, for example, becomes clean and focused.

namespace WPMemeShortcode;

class Shortcode
{
    /**
     * @var Options
     */
    private $options;

    /**
     * Constructor.
     *
     * @param Options $options
     */
    public function __construct(Options $options)
    {
        $this->options = $options;
    }

    /**
     * Handles the output of the shortcode.
     *
     * @param array  $attributes
     * @param string $content
     */
    public function handle(array $attributes, $content = null)
    {
        // Do nothing if no ID is given or it is not numeric
        if (!isset($attributes['id']) || !is_numeric($attributes['id'])) {
            return $content;
        }

        // If no size is given, get the default.
        if (!isset($attributes['size']) || !is_numeric($attributes['size'])) {
            $attributes['size'] = $this->options->get('size', '500');
        }

        return "<img src=\"http://cdn.memegenerator.net/instances/{$attributes['size']}x/{$attributes['id']}.jpg\" />";
    }
}

So, What’s the Real Payoff Here?

You might be wondering if this is worth the trouble for a small plugin. Trust me on this, it is. It’s about writing code that you (or the next dev) can actually work with six months from now.

  • Your code is clearer: No more guessing where a function lives. The class name tells you its job.
  • Your code is stronger: Need to change how options are saved? You edit one file, the `Options` class, not ten different places.
  • Your code is testable: It’s way easier to write unit tests for a class that does one thing than for a monster class that does everything.

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

Your email address will not be published. Required fields are marked *