WordPress Abstract Classes: Stop Copy-Pasting Your Code

I was digging into a new client’s plugin last week. They had a handful of admin pages for different settings, and the request was simple: add a new configuration option to every single one. Easy enough. But when I opened the code, I saw the same 150 lines copy-pasted across six different files, with only a few variables changed. What should have been a ten-minute job turned into an hour of careful search-and-replace. This is the kind of mess that happens when you’re not using WordPress abstract classes to keep your code DRY—Don’t Repeat Yourself.

My first thought? Just do the grunt work. Copy, paste, and bill for the hour. But that’s a junior dev move. You’re not just fixing the immediate problem; you’re leaving a landmine for the next person—which is often your future self. The real fix is architectural. You have to eliminate the repetition.

Why Abstract Classes Are Your Best Friend Here

An abstract class is like a blueprint for other classes. It lets you define the common methods and properties that a group of similar classes will share, but you can’t create an instance of the abstract class itself. It forces a contract. Any class that extends it must implement the specific “abstract” methods you define. In our admin page scenario, every page needs to register itself, render some HTML, and have a title. That’s our blueprint right there. The core concept is based on polymorphism, a principle I saw explained well over at Carl Alexander’s blog, which inspired this post.

Let’s look at the problem. You might have a ShortcodePage class and an OptionPage class that look almost identical. Both have methods to register hooks, a constructor to set options, and functions to render the page. It’s 90% the same code.

The “Blueprint” Abstract Class

Instead of that duplication, we create one parent class to hold all the shared logic. Anything that will be unique to each child class—like the page title or the actual form fields—we define as an abstract method. This is the contract.

namespace MyPlugin;

/**
 * Base class for all WordPress admin pages.
 */
abstract class AbstractAdminPage
{
    /**
     * My plugin options.
     * @var array
     */
    protected $options;

    /**
     * Register the admin page with WordPress hooks.
     */
    public static function register()
    {
        // Late static binding in PHP 5.3+
        $page = new static(get_option('my_plugin_options', []));

        add_action('admin_init', [$page, 'configure']);
        add_action('admin_menu', [$page, 'add_admin_page']);
    }

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

    /**
     * Adds the admin page to the menu.
     */
    public function add_admin_page()
    {
        add_options_page($this->get_page_title(), $this->get_page_title(), 'install_plugins', $this->get_menu_slug(), [$this, 'render']);
    }

    /**
     * Configure the admin page using the Settings API. (The Contract)
     */
    abstract public function configure();

    /**
     * Renders the admin page. (The Contract)
     */
    abstract public function render();

    /**
     * Get the admin page menu slug. (The Contract)
     * @return string
     */
    abstract protected function get_menu_slug();

    /**
     * Get the admin page title. (The Contract)
     * @return string
     */
    abstract protected function get_page_title();
}

Now, look how clean the actual page classes become. All they have to do is extend the abstract class and fill in the blanks defined by the contract. No more repeated hook registrations or constructor logic. Just the code that makes each page unique.

namespace MyPlugin;

/**
 * Options page.
 */
class OptionPage extends AbstractAdminPage
{
    public function configure()
    {
        register_setting($this->get_menu_slug(), 'my_plugin_options');
        add_settings_section('my_plugin_options', __('Options', 'my_plugin'), null, $this->get_menu_slug());
        // ... add fields
    }

    public function render()
    {
        echo '<h1>' . $this->get_page_title() . '</h1>';
        // ... render form
    }

    protected function get_menu_slug()
    {
        return 'my_plugin_options';
    }

    protected function get_page_title()
    {
        return __('My Plugin Options', 'my_plugin');
    }
}

So What’s the Point?

This isn’t just about being a fancy coder. It’s about being a professional. That original client task? With this structure, adding a new option would have taken minutes. You’d modify the one or two child classes that needed it, and you’d be done. No hunting, no copy-pasting, and zero chance of missing a file. It makes the codebase predictable and, more importantly, maintainable.

  • It centralizes your logic. One place to fix bugs or make updates.
  • It enforces consistency. Every admin page must have the required methods.
  • It saves you time. The next time a similar task comes up, you’re building on a solid foundation.

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.

The next time you find yourself copying a class just to change a few lines, stop. Ask yourself if there’s a blueprint underneath. Trust me on this, your future self will thank you for it.

Leave a Reply

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