Want to learn object-oriented programmming? Get my free course

Polymorphism and WordPress: Abstract classes

When we covered inheritance, there were some questions about interfaces and abstract classes.

  • What can you use them for?
  • What are the advantages?
  • When should you use them?

These are all great questions that are worth exploring. As the title suggests, “Polymorphism” is the object-oriented feature that helps answer these questions. It’s not an easy feature to grasp.

That’s why most of the article will be about an in-depth example. You’ll see the thought process involved with using it. This will help you understand it better so you can apply it to your own projects.

Many forms. Such polymorphism. Wow.

Polymorphism is all about a single idea. It’s that you can use a common interface or class to represent different types of objects.There are different types of polymorphism. The one that we care about is “Subtype polymorphism“. It’s usually the type of polymorphism referred to in object-oriented programming.

So what is polymorphism? So if you remember, inheritance is about creating “is-a” relationships between your classes. Well, polymorphism is all about those relationships. It’s about how you build your classes around it. It’s about harnessing those common elements between your classes.

When working with polymorphism, you ask questions like:

  • What does `My_Widget` class have in common with every other widget classes?
  • Is there a way I could reuse these common widget elements?
  • Are these common elements related to anything else?

These are WordPress related questions. That said, you can ask the same general questions regardless of the context. The goal is to identify what you need to extract and reuse between your classes. You’ll rely on the tools that you saw in the inheritance article to do this.

Copy/pasting is a great way to detect polymorphism

As programmers, we are notorious for copy/pasting code. You do it to learn from others, but you also do it to just to copy code between projects or even within projects.

If you find yourself copying code and altering a few lines, you might want to use polymorphism. Look through that code and ask yourself the same questions as earlier. You might see a pattern emerge.

This will help you deal with duplicate code and bug fixing (you only fix the bug once!). Your code is stronger as a result. You might even learn something about your code in the process.

Abstract classes: where the common code lives

So you found some common elements in your code. What do you do now? This is where abstract classes come in. You can put that code in one and then have your other classes inherit it.

An example: WordPress admin pages

Let’s go over a practical example to go over the thought process involved. Here are two classes that represent two different admin pages in WordPress. Below is an example based on the code in the single responsibility principle article.

namespace MyPlugin;

/**
 * Shortcode page.
 *
 * @author Carl Alexander
 */
class ShortcodePage
{
    /**
     * My plugin options.
     *
     * @var array
     */
    private $options;

    /**
     * Register the shortcode page with all the appropriate WordPress hooks.
     */
    public static function register()
    {
        $page = new self(get_option('my_plugin_options', array()));

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

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

    /**
     * Adds the shortcode page to the menu.
     */
    public function add_admin_page()
    {
        add_options_page(__('My Plugin Shortcodes', 'my_plugin'), __('My Plugin Shortcodes', 'my_plugin'), 'install_plugins', 'my_plugin_shortcode', array($this, 'render'));
    }

    /**
     * Configure the shortcode page.
     */
    public function configure()
    {
        // Register settings
        register_setting('my_plugin_shortcode', 'my_plugin_options');

        // General Section
        add_settings_section('my_plugin_shortcode', __('Shortcodes', 'my_plugin'), array($this, 'render_section'), 'my_plugin_shortcode');
        add_settings_field('my_plugin_shortcode_title', __('Shortcode Title', 'my_plugin'), array($this, 'render_shortcode_field'), 'my_plugin_shortcode', 'my_plugin_shortcode');
    }

    /**
     * Renders the shortcode page.
     */
    public function render()
    {
        ?>
        <div class="wrap" id="my-plugin-shortcodes">
            <h1><?php echo __('My Plugin Shortcodes', 'my_plugin') ?></h1>
            <form action="options.php" method="POST">
                <?php settings_fields('my_plugin_shortcode'); ?>
                <?php do_settings_sections('my_plugin_shortcode'); ?>
                <?php submit_button(); ?>
            </form>
        </div>
        <?php
    }

    /**
     * Renders the section.
     */
    public function render_section()
    {
        ?>
        <p><?php _e('Configure MyPlugin shortcodes.', 'my_plugin'); ?></p>
        <?php
    }

    /**
     * Renders the shortcode field.
     */
    public function render_shortcode_field()
    {
        ?>
        <input id="my_plugin_shortcode" name="my_plugin_options[shortcode]" type="text" value="<?php echo $this->options['shortcode']; ?>" />
        <?php
    }
}

/**
 * Options page.
 *
 * @author Carl Alexander
 */
class OptionPage
{
    /**
     * My plugin options.
     *
     * @var array
     */
    private $options;

    /**
     * Register the options page with all the appropriate WordPress hooks.
     */
    public static function register()
    {
        $page = new self(get_option('my_plugin_options', array()));

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

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

    /**
     * Adds the shortcode page to the menu.
     */
    public function add_admin_page()
    {
        add_options_page(__('My Plugin Options', 'my_plugin'), __('My Plugin Options', 'my_plugin'), 'install_plugins', 'my_plugin_shortcode', array($this, 'render'));
    }

    /**
     * Configure the options page.
     */
    public function configure()
    {
        // Register settings
        register_setting('my_plugin_options', 'my_plugin_options');

        // General Section
        add_settings_section('my_plugin_options', __('Options', 'my_plugin'), array($this, 'render_section'), 'my_plugin_options');
        add_settings_field('my_plugin_options_color', __('Custom color', 'my_plugin'), array($this, 'render_color_field'), 'my_plugin_options', 'my_plugin_options');
    }

    /**
     * Render the options page.
     */
    public function render()
    {
        ?>
        <div class="wrap" id="my-plugin-options">
            <h1><?php echo __('My Plugin Options', 'my_plugin') ?></h1>
            <p>I want some introduction paragraph about my plugin here.</p>
            <form action="options.php" method="POST">
                <?php settings_fields('my_plugin_options'); ?>
                <?php do_settings_sections('my_plugin_options'); ?>
                <?php submit_button(); ?>
            </form>
        </div>
        <?php
    }

    /**
     * Renders the section.
     */
    public function render_section()
    {
        ?>
        <p><?php _e('Customize MyPlugin with these options', 'my_plugin'); ?></p>
        <?php
    }

    /**
     * Renders the color field.
     */
    public function render_color_field()
    {
        ?>
        <input id="my_plugin_color" name="my_plugin_options[color]" type="color" value="<?php echo $this->options['color']; ?>" />
        <?php
    }
}

Analyzing the common elements of both classes

Just by being admin pages, these two classes scream commonality. Let’s look at the code, what’s the same? What’s different? Each of you will have unique common elements to your plugin or theme. It’s possible that the elements that you see below won’t apply to you. That said, the thought process is the same for everyone.

Let’s start with what’s the same. Take a few seconds to think about it before continuing. Here is a rough list:

  • You need to register the admin page with WordPress
  • You register the same methods with WordPress
  • You need access to your plugin options
  • Your constructor is the same
  • You need to render form fields in both cases

Now, what’s different? Well, there are a few things.

  • The page title is different
  • The menu slug is different
  • Your forms are different and need different interactions with the settings API
  • Rendering these forms will be different

Creating an abstract class

With our analysis complete, let’s build an abstract class with that information. This abstract class represents the essence of what an admin page is about.

namespace MyPlugin;

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

    /**
     * Register the admin page with all the appropriate WordPress hooks.
     */
    public static function register()
    {
        $page = new static(get_option('my_plugin_options', array()));

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

    /**
     * Constructor.
     *
     * @param array $options
     */
    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(), array($this, 'render'));
    }

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

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

    /**
     * Render a form field.
     *
     * @param string $id
     * @param string $name
     * @param string $value
     * @param string $type
     */
    protected function render_form_field($id, $name, $value = '', $type = 'text')
    {
        ?>
        <input id="<?php echo $id; ?>" name="<?php echo $name; ?>" type="<?php echo $type; ?>" value="<?php echo $value; ?>" />
        <?php
    }

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

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

Note: The use of “new static” in the code above requires PHP 5.3.

Let’s go over some of the design decisions. As you learn more object-oriented concepts, these decisions become more complicated. They aren’t as clear cut because the problems are harder. That’s what object-oriented programming is about in the end.

That why it’s useful for you to see why and how those decisions happen. This will allow you to develop your own opinion and find your own solutions.

Registering and initializing each admin page

To solve this problem, we need three of the methods defined above. You use the register method which is our custom constructor. It calls the constructor and adds the common hooks. You also need to register the options page with WordPress.

A valid concern would be that you might not want to use get_option for each admin page. In that case, the constructor isn’t necessary. You could just use method overloading in the child class to load the options. Here is how it could look like.

namespace MyPlugin;

/**
 * Base class for all WordPress admin pages.
 *
 * @author Carl Alexander
 */
abstract class AbstractAdminPage
{
    /**
     * Register the admin page with all the appropriate WordPress hooks.
     */
    public static function register($page = null)
    {
        // If we didn't receive an initiated child class, instantiate one.
        if (null === $page) {
            $page = new static();
        }

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

    // ...
}

/**
 * A WordPress admin page using overloading.
 *
 * @author Carl Alexander
 */
class AdminPage extends AbstractAdminPage
{
    /**
     * My plugin options.
     *
     * @var array
     */
    private $options;

    /**
     * Register the admin page with all the appropriate WordPress hooks.
     */
    public static function register()
    {
        // Create an instance of the admin page with the options
        $page = new self(get_option('my_plugin_options', array()));

        // Pass it up to the main register function
        parent::register($page);
    }

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

Abstract methods

The class has four abstract methods defined: configure, render, get_menu_slug and get_page_title. The abstract class registered these methods with WordPress because every admin page needs them.

That said, each admin page doesn’t need these methods to do the same thing. They have their own unique title, menu slug, configuration and rendering.  That’s why we defined them as abstract using the abstract keyword.

Now, if you hadn’t noticed, these four methods have something in common. They represent what we analyzed to be different between the two classes. Abstract methods are one of the possible solutions to handling these non-common elements.

It works well in this example, but you might not always be able to do this. You might want to use overloading instead. Each situation is unique. This is where knowledge of the inheritance toolkit comes in handy.

render_form_field method

This is a helper method that any admin page can use. Helper methods like this are worth putting in the abstract classes. By doing that, any child class can leverage them. In this case, most admin pages need to render a form field.

It’s worth pointing out that you shouldn’t add every method into the abstract class. You want to take time and think about it. You don’t want your abstract class to act as a “catch all”. You’ll end up with a class that’s bloated and hard to work with.

As mentioned earlier, copy/pasting is a good way to test what you need to put in it. Are you copy/pasting code between two admin page classes? Take a good look at what you are copying. How can you alter it so you can reuse it elsewhere? Once you figure that out, that would go in your abstract class.

The child classes

Let’s take a look at the resulting child classes. They are smaller. Their code is clearer. That’s because you extracted the common code out of them. What you have left is code that focuses on the unique task that each class needs to do.

namespace MyPlugin;

/**
 * Shortcode page.
 *
 * @author Carl Alexander
 */
class ShortcodePage extends AbstractAdminPage
{
    /**
     * Configure the shortcode page.
     */
    public function configure()
    {
        // Register settings
        register_setting($this->get_menu_slug(), 'my_plugin_options');

        // General Section
        add_settings_section('my_plugin_shortcode', __('Shortcodes', 'my_plugin'), array($this, 'render_section'), $this->get_menu_slug());
        add_settings_field('my_plugin_shortcode_title', __('Shortcode Title', 'my_plugin'), array($this, 'render_shortcode_field'), $this->get_menu_slug(), 'my_plugin_shortcode');
    }

    /**
     * Renders the shortcode page.
     */
    public function render()
    {
        ?>
        <div class="wrap" id="my-plugin-shortcodes">
            <h1><?php echo $this->get_page_title() ?></h1>
            <form action="options.php" method="POST">
                <?php settings_fields($this->get_menu_slug()); ?>
                <?php do_settings_sections($this->get_menu_slug()); ?>
                <?php submit_button(); ?>
            </form>
        </div>
        <?php
    }

    /**
     * Renders the section.
     */
    public function render_section()
    {
        ?>
        <p><?php _e('Configure MyPlugin shortcodes.', 'my_plugin'); ?></p>
        <?php
    }

    /**
     * Renders the shortcode field.
     */
    public function render_shortcode_field()
    {
        $this->render_form_field('my_plugin_shortcode', 'my_plugin_options[shortcode]', $this->options['shortcode']);
    }

    /**
     * Get the shortcode page menu slug.
     *
     * @return string
     */
    protected function get_menu_slug()
    {
        return 'my_plugin_shortcode';
    }

    /**
     * Get the shortcode page title.
     *
     * @return string
     */
    protected function get_page_title()
    {
        return __('My Plugin Shortcodes', 'my_plugin');
    }
}

/**
 * Options page.
 *
 * @author Carl Alexander
 */
class OptionPage extends AbstractAdminPage
{
    /**
     * Configure the options page.
     */
    public function configure()
    {
        // Register settings
        register_setting($this->get_menu_slug(), 'my_plugin_options');

        // General Section
        add_settings_section('my_plugin_options', __('Options', 'my_plugin'), array($this, 'render_section'), $this->get_menu_slug());
        add_settings_field('my_plugin_options_color', __('Custom color', 'my_plugin'), array($this, 'render_color_field'), $this->get_menu_slug(), 'my_plugin_options');
    }

    /**
     * Render the options page.
     */
    public function render()
    {
        ?>
        <div class="wrap" id="my-plugin-options">
            <h1><?php echo $this->get_page_title() ?></h1>
            <p>I want some introduction paragraph about my plugin here.</p>
            <form action="options.php" method="POST">
                <?php settings_fields($this->get_menu_slug()); ?>
                <?php do_settings_sections($this->get_menu_slug()); ?>
                <?php submit_button(); ?>
            </form>
        </div>
        <?php
    }

    /**
     * Renders the section.
     */
    public function render_section()
    {
        ?>
        <p><?php _e('Customize MyPlugin with these options', 'my_plugin'); ?></p>
        <?php
    }

    /**
     * Renders the color field.
     */
    public function render_color_field()
    {
        $this->render_form_field('my_plugin_color', 'my_plugin_options[color]', $this->options['color'], 'color');
    }

    /**
     * Get the options page menu slug.
     *
     * @return string
     */
    protected function get_menu_slug()
    {
        return 'my_plugin_options';
    }

    /**
     * Get the options page title.
     *
     * @return string
     */
    protected function get_page_title()
    {
        return __('My Plugin Options', 'my_plugin');
    }
}

We’re entering design territory

The goal with these examples was to show you the thought process when using polymorphism. It’s about easing you into the idea of software design with objects. It’s how you use the tools under your belt to solve your unique problems.

Problems that don’t always have predefined solutions because each situation is unique. Sometimes there’s more than one way to do what you want to do.

An example about copy/paste

Here is a design decision example using the abstract class shown earlier. The first version of the class had an abstract method for add_admin_page and not render. Each admin page needs to add its own admin page and rendering methods. At first glance, it made sense.

namespace MyPlugin;

/**
 * Base class for all WordPress admin pages. This class uses add_admin_page
 * as an abstract method.
 *
 * @author Carl Alexander
 */
abstract class AbstractAdminPage
{
    /**
     * My plugin options.
     *
     * @var array
     */
    protected $options;

    /**
     * Register the admin page with all the appropriate WordPress hooks.
     */
    public static function register()
    {
        $page = new static(get_option('my_plugin_options'), array());

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

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

    /**
     * Adds the admin page to the menu.
     */
    abstract public function add_admin_page();

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

    /**
     * Render a form field.
     *
     * @param string $id
     * @param string $name
     * @param string $value
     * @param string $type
     */
    protected function render_form_field($id, $name, $value = '', $type = 'text')
    {
        ?>
        <input id="<?php echo $id; ?>" name="<?php echo $name; ?>" type="<?php echo $type; ?>" value="<?php echo $value; ?>" />
        <?php
    }
}

When it came time to create the child classes, I was copying the same code over. I had add_admin_page, but I was only changing the page title. This raised a flag right away.

“What do I really need in my child class?”, I thought.

The answer was that I needed to render the page and I needed the page title/slug. So I altered the code to get the result you saw earlier. I added render, get_menu_slug and get_page_title as abstract methods. I replaced add_admin_page with a method using those three new abstract methods.

What about interfaces?

The initial plan was to have this article cover both interfaces and abstract classes. As the article grew, it became obvious that interfaces needed their own article. They need their own discussion, their own examples and their own conclusion.

We’ll cover them in an another article.

Creative Commons License