# Designing a system: WordPress admin pages

In a [previous article](https://carlalexander.ca/designing-class-wordpress-admin-page/), we saw how to design a class to represent a [WordPress admin page](https://codex.wordpress.org/Administration_Menus). The article was an excellent resource for anyone looking to design a class to solve that specific type of problem. But it was only a starting point. For example, we used the [settings API](https://codex.wordpress.org/Settings_API) to handle forms. But the settings API doesn't quite do the job for every use cases. For those other cases, you're going to need to manage the submission of a form yourself. But to handle these different use cases, we're going to need a more solid foundation. So we're going to take the work that we did to design our admin page class, and we're going to take it one step further. We're going to design a whole system for WordPress admin pages.

# Reviewing our admin page class

But first, let's review some of what we had at the end of the previous article. Instead of focusing on creating a system like we're going to do now, that article focused on creating a class. That class was the `MyPlugin_AdminPage` class. ```
class MyPlugin_AdminPage
{
    /**
     * Path to the admin page templates.
     *
     * @var string
     */
    private $template_path;
 
    /**
     * Constructor.
     *
     * @param string $template_path
     */
    public function __construct($template_path)
    {
        $this->template_path = rtrim($template_path, '/');
    }
 
    /**
     * Configure the admin page using the Settings API.
     */
    public function configure()
    {
        /// Register settings
        register_setting($this->get_slug(), 'myplugin_option');
 
        // Register section and field
        add_settings_section(
            $this->get_slug() . '-section',
            __('Section Title', 'myplugin'),
            array($this, 'render_section'),
            $this->get_slug()
        );
        add_settings_field(
            $this->get_slug() . '-api-status',
            __('My option', 'myplugin'),
            array($this, 'render_option_field'),
            $this->get_slug(),
            $this->get_slug() . '-section'
        );
    }
 
    /**
     * Get the capability required to view the admin page.
     *
     * @return string
     */
    public function get_capability()
    {
        return 'install_plugins';
    }
 
    /**
     * Get the title of the admin page in the WordPress admin menu.
     *
     * @return string
     */
    public function get_menu_title()
    {
        return 'My Plugin';
    }
 
    /**
     * Get the title of the admin page.
     *
     * @return string
     */
    public function get_page_title()
    {
        return 'My Plugin Admin Page';
    }
 
    /**
     * Get the parent slug of the admin page.
     *
     * @return string
     */
    public function get_parent_slug()
    {
        return 'options-general.php';
    }
 
    /**
     * Get the slug used by the admin page.
     *
     * @return string
     */
    public function get_slug()
    {
        return 'myplugin';
    }
 
    /**
     * Renders the option field.
     */
    public function render_option_field()
    {
        $this->render_template('option_field');
    }
 
    /**
     * Render the plugin's admin page.
     */
    public function render_page()
    {
        $this->render_template('page');
    }
 
    /**
     * Render the top section of the plugin's admin page.
     */
    public function render_section()
    {
        $this->render_template('section');
    }
 
    /**
     * Renders the given template if it's readable.
     *
     * @param string $template
     */
    private function render_template($template)
    {
        $template_path = $this->template_path . '/' . $template . '.php';
 
        if (!is_readable($template_path)) {
            return;
        }
 
        include $template_path;
    }
}
```

As you can see above, the `MyPlugin_AdminPage` class is pretty big. That's because it has a method for each argument of the [`add_submenu_page`](https://developer.wordpress.org/reference/functions/add_submenu_page/) function. This is a design decision that allows the class to map to the `add_submenu_page` function in a simple way. The `MyPlugin_AdminPage` class also made use of the [settings API](https://codex.wordpress.org/Settings_API). We used it to create the different fields of the admin page. We'd register them with the settings API, and it would put everything together for us. The last few methods of the `MyPlugin_AdminPage` class had to do with HTML generation. The settings API doesn't generate any HTML for us, so we needed a way to do that. The solution was to reuse a method to generate HTML from PHP template that we'd seen in a [previous article](https://carlalexander.ca/designing-class-generate-wordpress-html-content/). ```
function myplugin_add_admin_page() {
    $admin_page = new MyPlugin_AdminPage();
 
    add_submenu_page(
        $admin_page->get_parent_slug(),
        $admin_page->get_page_title(),
        $admin_page->get_menu_title(),
        $admin_page->get_capability(),
        $admin_page->get_slug(),
        array($admin_page, 'render_page')
    );
}
add_action('admin_menu', 'myplugin_add_admin_page');
```

Finally, the last little bit of code from the previous article was the `myplugin_add_admin_page` function. This is the function that we used to register the `MyPlugin_AdminPage` class with WordPress. All that it did was call the `add_submenu_page` using the methods from the `MyPlugin_AdminPage` class for the function arguments. The `myplugin_add_admin_page` function gets called by the [plugin API](https://carlalexander.ca/wordpress-adventurous-plugin-api/). We register it to the [`admin_menu`](https://codex.wordpress.org/Plugin_API/Action_Reference/admin_menu) hook using the [`add_action`](https://developer.wordpress.org/reference/functions/add_action/) function. The `admin_menu` hook that you should always use whenever you want to add admin pages. # Creating an interface

An essential aspect of designing an object-oriented system is to have it rely on [interfaces](https://carlalexander.ca/polymorphism-wordpress-interfaces/) and not concrete classes. This is an important element the [dependency inversion principle](https://carlalexander.ca/dependency-inversion-principle-wordpress/). This means that we need to extract an interface from our `MyPlugin_AdminPage` class. ```
/**
 * A WordPress admin page.
 */
interface MyPlugin_AdminPageInterface
{
    /**
     * Get the capability required to view the admin page.
     *
     * @return string
     */
    public function get_capability();

    /**
     * Get the title of the admin page in the WordPress admin menu.
     *
     * @return string
     */
    public function get_menu_title();

    /**
     * Get the title of the admin page.
     *
     * @return string
     */
    public function get_page_title();

    /**
     * Get the parent slug of the options page.
     *
     * @return string
     */
    public function get_parent_slug();

    /**
     * Get the slug used by the admin page.
     *
     * @return string
     */
    public function get_slug();

    /**
     * Renders the admin page.
     */
    public function render_page();
}
```

Above is the `MyPlugin_AdminPageInterface` interface. It's the result of this extraction process. It contains the relevant methods from `MyPlugin_AdminPage` class. It's worth taking a moment to discuss why we chose these methods and not the others. All these methods have one thing in common. They're all methods that we use to map our admin page class to the `add_submenu_page` function. We omitted every other method. (At least for now.) The reason for doing this is that we want to start with the smallest interface possible. This will allow our system to be much more flexible. So for now, `MyPlugin_AdminPageInterface` interface only has those methods. There's no support for the settings API like the original `MyPlugin_AdminPage` class has. We'll see how to handle the settings API using an interface a bit later. # Adding our admin pages

Next, we want to look at how we'll register our admin pages with WordPress. This is the task that the `myplugin_add_admin_page` function did in our previous article. If you want you can continue using it, you can with this small change: ```
function myplugin_add_admin_pages() {
    $admin_pages = array(
        // Put admin page objects here
    );
 
    foreach ($admin_pages as $admin_page) {
        add_submenu_page(
            $admin_page->get_parent_slug(),
            $admin_page->get_page_title(),
            $admin_page->get_menu_title(),
            $admin_page->get_capability(),
            $admin_page->get_slug(),
            array($admin_page, 'render_page')
        );
    }
}
add_action('admin_menu', 'myplugin_add_admin_pages');
```

Here's the modified `myplugin_add_admin_page` function. First, we changed the name and made it plural. This is to represent the fact that it can add more than one admin page object. We also changed the `admin_page` variable and also made it plural. Instead of containing a single admin page object, it's now an array of admin page objects. We use a [`foreach`](https://secure.php.net/manual/en/control-structures.foreach.php) loop to add them all using the `add_submenu_page` function. # Adding back the rendering system

Now that we have a foundation for our admin page system, we can start fleshing it out. The first thing we can look at doing is adding back the rendering system that we had in the `MyPlugin_AdminPage` class. To do that, we're going to use an [abstract class](https://carlalexander.ca/polymorphism-wordpress-abstract-classes/). ```
/**
 * A WordPress admin page rendered using a PHP template.
 */
abstract class MyPlugin_AbstractRenderedAdminPage implements MyPlugin_AdminPageInterface
{
    /**
     * Path to the admin page's templates.
     *
     * @var string
     */
    protected $template_path;

    /**
     * Constructor.
     *
     * @param string $template_path
     */
    public function __construct($template_path)
    {
        $this->template_path = rtrim($template_path, '/');
    }

    /**
     * Render the admin page.
     */
    public function render_page()
    {
        $this->render_template('page');
    }

    /**
     * Renders the given template if it's readable.
     *
     * @param string $template
     */
    protected function render_template($template)
    {
        $template_path = $this->template_path . '/' . $template . '.php';
 
        if (!is_readable($template_path)) {
            return;
        }
 
        include $template_path;
    }
}
```

Here is our `MyPlugin_AbstractRenderedAdminPage` abstract class. This class implements our `MyPlugin_AdminPageInterface` interface. That said, we've only implemented the `render_page` method. (This is also why the class needs to be abstract.) The reason why we've only implemented the `render_page` method is that we care about. The goal of the `MyPlugin_AbstractRenderedAdminPage` class is to be a base class for any class that wants to use PHP templates. And this happens in the `render_page` method. The `MyPlugin_AbstractRenderedAdminPage` class also has the `render_template` helper method. We've changed [its visibility](https://carlalexander.ca/first-thing-learn-object-oriented-programming/) from `private` to `protected`. This will allow any class that extends our `MyPlugin_AbstractRenderedAdminPage` class to use it. # Adding back support for the settings API

Next, we're going to look into adding back the support for the settings API. This is going to be a bit different from what we did with the rendering system. Instead of using an abstract class, we're going to use [inheritance](https://carlalexander.ca/using-inheritance-wordpress/). ```
/**
 * A WordPress admin page configured using the settings API.
 */
interface MyPlugin_ConfigurableAdminPageInterface extends MyPlugin_AdminPageInterface
{
    /**
     * Configure the admin page using the Settings API.
     */
    public function configure();
}
```

Here's what we mean by using inheritance. We created a new interface called `MyPlugin_ConfigurableAdminPageInterface`. This new interface extends our original `MyPlugin_AdminPageInterface`. ## Why extend?

A valid question would be, "Why do we need to extend the `MyPlugin_AdminPageInterface`?" After all, we could just have the `MyPlugin_ConfigurableAdminPageInterface` with just the `configure` method. And any admin page who wants to use the settings API would have to implement both interfaces instead of just the one. (This would be a type of [multiple inheritance](https://en.wikipedia.org/wiki/Multiple_inheritance).) The reason not to go this route is subjective. From a design perspective, a standalone `MyPlugin_ConfigurableAdminPageInterface` interface doesn't make much sense. There aren't really any scenarios where you'd use the `MyPlugin_ConfigurableAdminPageInterface` interface without also using the `MyPlugin_AdminPageInterface` interface as well. That said, if you had a standalone system for the settings API, it would make sense for that system to have an interface like the `MyPlugin_ConfigurableAdminPageInterface` interface. But such a system is out of the scope of this article. So, for now, the `MyPlugin_ConfigurableAdminPageInterface` interface will extend the `MyPlugin_AdminPageInterface` interface. ## Configuring the settings API

At this point, we have an interface that we can use to configure the settings API, but we still don't have code to use that interface. This is what we have to do next. We have to create a function that calls the `configure` method at the right moment in the [WordPress loading process](https://carlalexander.ca/wordpress-adventurous-loading/). ```
function myplugin_configure_admin_pages() {
    $admin_pages = array(
        // Put admin page objects here
    );
 
    foreach ($admin_pages as $admin_page) {
        if ($admin_page instanceof MyPlugin_ConfigurableAdminPageInterface) {
            $admin_page->configure();
        }
    }
}
add_action('admin_init', 'myplugin_configure_admin_pages');
```

Above is the `myplugin_configure_admin_pages` function used to configure our admin pages that want to use the settings API. It's very similar to our earlier `myplugin_add_admin_pages` function. It's basically a `foreach` loop through all our admin page objects. The difference is that inside the loop we do use a [conditional](https://carlalexander.ca/mastering-php-conditionals/) to check if the admin page object implements the `MyPlugin_ConfigurableAdminPageInterface` interface. We use the [`instanceof`](https://secure.php.net/manual/en/language.operators.type.php) operator to do it. If the given `admin_page` implements the `MyPlugin_ConfigurableAdminPageInterface` interface, we call the `configure` method. It's also worth mentioning that the `myplugin_configure_admin_pages` function also gets called using the plugin API. We don't use the `admin_menu` hook this time. Instead, the hook that you should use with the settings API is the [`admin_init`](https://codex.wordpress.org/Plugin_API/Action_Reference/admin_init) hook. # How do you manage your admin page objects?

Now, there's a problem with the `myplugin_configure_admin_pages` function that we haven't discussed. It's that it has a `admin_pages` variable which contains our admin page objects. The problem with that is that our `myplugin_add_admin_pages` function also had that same variable. So what do we do? We can't have two `admin_pages` variables that initialize our admin page objects. One solution is to move the initialization of our admin page objects to its function like this: ```
function myplugin_get_admin_pages() {
    return array(
        // Put admin page objects here
    );
}
```

The `myplugin_get_admin_pages` function does exactly that. It just returns our array of admin page objects. We can then replace the `admin_pages` array in our functions with this: ```
$admin_pages = myplugin_get_admin_pages();
```

We use the `myplugin_get_admin_pages` function to fetch our admin page objects whenever we need them. In the case of our two earlier functions, we use it to populate our `admin_pages` array. This solves the issue of which of these functions creates our admin page objects. # Using the event management system instead

That said, this is far from an ideal way to solve the problem. For one, it's a [procedural](https://en.wikipedia.org/wiki/Procedural_programming) way of solving the problem. This isn't bad per se. But it isn't helpful when the goal of this article is to show you how to create an object-oriented system. Second, it doesn't really show you how to combine [various systems](https://carlalexander.ca/tag/designing-a-system/) together. (And that's one of the goals of the goals of these articles.) So we're going to take a moment and do that. We're going to combine this system with the [event management system](https://carlalexander.ca/designing-system-wordpress-event-management/). ```
/**
 * Subscriber that registers the plugin's admin pages with WordPress.
 */
class MyPlugin_AdminPagesSubscriber implements MyPlugin_SubscriberInterface
{

}
```

We're going to start with just an empty `MyPlugin_AdminPagesSubscriber` class. It implements the `MyPlugin_SubscriberInterface` from the event management system. This interface has a contract that requires any class that implements it to have a `get_subscribed_events` static method. ## Storing our admin page objects

Now that we have the foundation of our `MyPlugin_AdminPagesSubscriber` class, let's flesh it out some more. The first thing that it needs to have is a way to store all our admin page objects. We can do that using a private property. ```
/**
 * Subscriber that registers the plugin's admin pages with WordPress.
 */
class MyPlugin_AdminPagesSubscriber implements MyPlugin_SubscriberInterface
{
    /**
     * The admin pages that the subscriber manages.
     *
     * @var MyPlugin_AdminPageInterface[]
     */
    private $admin_pages;

    /**
     * Constructor.
     *
     * @param MyPlugin_AdminPageInterface[] $admin_pages
     */
    public function __construct(array $admin_pages)
    {
        $this->admin_pages = $admin_pages;
    }
}
```

Above is the modified `MyPlugin_AdminPagesSubscriber` class. We added the `admin_pages` private property. We also added a [constructor](https://en.wikipedia.org/wiki/Constructor_(object-oriented_programming)) that has an array of admin pages as an argument. But just assigning the `admin_pages` variable to the `admin_pages` property like that isn't great. That's because we say that the `admin_pages` array should only contain objects implementing the `MyPlugin_AdminPageInterface` interface. But the reality is that we have no way of guaranteeing that the way that we coded our constructor. ```
/**
 * Subscriber that registers the plugin's admin pages with WordPress.
 */
class MyPlugin_AdminPagesSubscriber implements MyPlugin_SubscriberInterface
{
    /**
     * The admin pages that the subscriber manages.
     *
     * @var MyPlugin_AdminPageInterface[]
     */
    private $admin_pages;

    /**
     * Constructor.
     *
     * @param MyPlugin_AdminPageInterface[] $admin_pages
     */
    public function __construct(array $admin_pages)
    {
        $this->admin_pages = array();

        foreach ($admin_pages as $admin_page) {
            $this->add_admin_page($admin_page);
        }
    }

    /**
     * Add a new admin page to the subscriber.
     *
     * @param MyPlugin_AdminPageInterface $admin_page
     */
    private function add_admin_page(MyPlugin_AdminPageInterface $admin_page)
    {
        $this->admin_pages[] = $admin_page;
    }
}
```

Here's an improved `MyPlugin_AdminPagesSubscriber` class with the added validation of the `admin_pages` array. There's a new private method called `add_admin_page`. It's now the method in charge of adding admin page objects to the `admin_pages` array. The `add_admin_page` method validates the admin page object using [type hinting](https://secure.php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration). Using our new `add_admin_page` method, we can then rework the constructor. We remove the old assignment of the `admin_pages` variable to the `admin_pages` property. We replace it with just assigning an empty array to the `admin_pages` property. We then use a `foreach` loop to loop through the `admin_pages` variable. We then pass each admin page object to our new `add_admin_page` method. This is what ensures that every admin page object in our `admin_pages` array implements our `MyPlugin_AdminPageInterface` interface. ## Moving our functions

At this point, all that we've done is move everything related to the `myplugin_get_admin_pages` function to our `MyPlugin_AdminPagesSubscriber` class. We haven't touched either function that used the `myplugin_get_admin_pages` function. Those were the `myplugin_add_admin_pages` and `myplugin_configure_admin_pages` functions. Moving these two functions to our `MyPlugin_AdminPagesSubscriber` class is quite straightforward. To begin, the `MyPlugin_SubscriberInterface` interface requires that we create the `get_subscribed_events` public static method. This method is where we'll put the hooks that we registered with the `add_action` function before. ```
/**
 * Subscriber that registers the plugin's admin pages with WordPress.
 */
class MyPlugin_AdminPagesSubscriber implements MyPlugin_SubscriberInterface
{
    // ...

    /**
     * Returns an array of events that this subscriber wants to listen to.
     *
     * The array key is the event name. The value can be:
     *
     *  * The method name
     *  * An array with the method name and priority
     *  * An array with the method name, priority and number of accepted arguments
     *
     * For instance:
     *
     *  * array('event_name' => 'method_name')
     *  * array('event_name' => array('method_name', $priority))
     *  * array('event_name' => array('method_name', $priority, $accepted_args))
     *
     * @return array
     */
    public static function get_subscribed_events()
    {
        return array();
    }

    // ...
}
```

Above you can see the `get_subscribed_events` method. For now, it returns an empty array. But the array that it returns needs to follow the convention of the `MyPlugin_SubscriberInterface` interface. This convention is written in the [PHPdoc](https://en.wikipedia.org/wiki/PHPDoc) of the method. Next, we have to move our two functions to the `MyPlugin_AdminPagesSubscriber` class. Once that's done, we also need to add the hooks and method names to the array that the `get_subscribed_events` method returns. You can see this all below. ```
/**
 * Subscriber that registers the plugin's admin pages with WordPress.
 */
class MyPlugin_AdminPagesSubscriber implements MyPlugin_SubscriberInterface
{
    // ...

    /**
     * Returns an array of events that this subscriber wants to listen to.
     *
     * The array key is the event name. The value can be:
     *
     *  * The method name
     *  * An array with the method name and priority
     *  * An array with the method name, priority and number of accepted arguments
     *
     * For instance:
     *
     *  * array('event_name' => 'method_name')
     *  * array('event_name' => array('method_name', $priority))
     *  * array('event_name' => array('method_name', $priority, $accepted_args))
     *
     * @return array
     */
    public static function get_subscribed_events()
    {
        return array(
            'admin_init' => 'configure_admin_pages',
            'admin_menu' => 'add_admin_pages',
        );
    }

    /**
     * Adds the plugin's admin pages to the WordPress admin.
     */
    public function add_admin_pages()
    {
        foreach ($this->admin_pages as $admin_page) {
            add_submenu_page(
                $admin_page->get_parent_slug(),
                $admin_page->get_page_title(),
                $admin_page->get_menu_title(),
                $admin_page->get_capability(),
                $admin_page->get_slug(),
                array($admin_page, 'render_page')
            );
        }
    }

    /**
     * Configure the plugin's admin pages using the Settings API.
     */
    public function configure_admin_pages()
    {
        foreach ($this->admin_pages as $admin_page) {
            if ($admin_page instanceof MyPlugin_ConfigurableAdminPageInterface) {
                $admin_page->configure();
            }
        }
    }

    // ...
}
```

We added two new methods: `add_admin_pages` and `configure_admin_pages`. The two methods kept the same name as our earlier functions. The only difference is that we removed the `myplugin_` prefix since it's not useful inside a class. The code inside the methods hasn't changed from the earlier version with the functions with one exception. We replaced the use of the `admin_pages` variable with the `admin_pages` class property. That class property was the main reason why we created the `MyPlugin_AdminPagesSubscriber` class in the first place. # A solid foundation

So this is a good place to wrap things up! With what we've seen in this article, you have all that you need to create admin pages like in the previous article. But now, you've broken things down more and built a system around it. The great thing with this system is that it lets you handle different use cases. So far, we've only covered the use cases from the previous article like rendering templates and using the settings API. But, we'll look at some more in the future! You can find a gist with all the complete code sample [here](https://gist.github.com/carlalexander/33e364ad5e0d4833eba88ae91501b665). *Photo Credit: [Hal Gatewood](https://unsplash.com/photos/tZc3vjPCk-Q)*