# Designing a class to represent a WordPress meta box

In a [previous article](https://carlalexander.ca/designing-a-class-wordpress-admin-page/), we discussed how to design a class representing a [WordPress admin page](https://codex.wordpress.org/Administration_Menus). This was important because almost every plugin or theme needs an admin page. But this isn't the only common thing that plugins or themes add to the WordPress admin. Another one of those is the meta box. If you're not familiar with meta boxes, they're the draggable boxes that you see on the post editing screen. It's quite common for plugins and themes to add post-specific functionality through them. This makes them a good topic to discuss with object-oriented programming. (Is there really a bad topic to discuss with object-oriented programming!?) And, as we'll see, meta boxes have a lot of in common with admin pages. This means that designing a class to represent them will be a lot like what we did for admin pages.

# What makes a meta box?

A meta box like an admin page has two elements to it. The first one is the registration process. We need to register a meta box with WordPress if we want it displayed anywhere. We also need a way to render the HTML of the meta box. ## Registering a meta box

Like with admin pages, this registration process centres around a single function. That function is the [`add_meta_box`](https://developer.wordpress.org/reference/functions/add_meta_box/) function. But, unlike its admin page counterpart, this function is a lot simpler to use. That's because, while the `add_meta_box` function has a lot of parameters, most are optional. The only required parameters are: `id`, `title` and `callback`. `screen`, `context`, `priority` and `callback_args` are the other four parameters, and they're optional. ### Required parameters

`id` is the ID used by WordPress to store the meta box in the `wp_meta_boxes` global variable. WordPress also uses `id` for the `id` [HTML attribute](https://en.wikipedia.org/wiki/HTML_attribute). This `id` should be unique to your meta box. Or at the very least unique for the `screen` that you're registering it to. (We'll talk about that when we discuss the `screen` parameter.) Meanwhile, the other two required parameters are pretty self-explanatory. `title` is the title that you'll see above the meta box on the admin screen where WordPress will display it. And `callback` is the [callable](https://secure.php.net/manual/en/language.types.callable.php) used by WordPress to render the inside of the meta box. ### Optional parameters

For the optional parameters, we have the `screen` parameter. This is the screen or screens where you want WordPress to display the meta box. The `add_meta_box` function accepts `screen` arguments that are either a string ID, an array of string IDs or a [`WP_Screen`](https://developer.wordpress.org/reference/classes/wp_screen/) object. A screen ID is often the slug of the admin page where you want your meta box to appear. By default, if you leave it empty, it will use the screen returned by the [`get_current_screen`](https://codex.wordpress.org/get_current_screen) function. In general, the `get_current_screen` function will return the WordPress post editing screen. As we mentioned earlier, the `wp_meta_boxes` global array is a multidimensional array. It uses both the screen ID and the `id` argument that you pass to the `add_meta_box` function as keys. This means that, in theory, you can use the same `id` for two different meta box as long as you use two different `screen` values. Meanwhile, `context` determines where WordPress should display the meta box on the given `screen`. This value changes depending on the `screen` value. That said, both `normal` and `side` are values that work on most screens. `priority` determines the priority of the meta box within the given `context`. There are four possible values for priority: `high`, `core`, `default` and `low`. But, in practice, you only want to choose between `high` and `low` if you're going to specify one. `high` will put the meta box at the top of the list of meta boxes for the given `context` while `low` will place it at the end. The last optional parameter is `callback_args`. You use this to pass custom meta box data. WordPress will then pass that data as the second argument of your `callback` callable. ### When do you register a meta box?

It's also worth pointing out that we can't call the `add_meta_box` function whenever we want. We need to use the [plugin API](https://carlalexander.ca/wordpress-adventurous-plugin-api/) to call it at the right time. But, lucky for us, WordPress has hooks specifically to add meta boxes. The main one is the [`add_meta_boxes`](https://codex.wordpress.org/Plugin_API/Action_Reference/add_meta_boxes) hook. WordPress triggers it right after it finishes registering all the built-in meta boxes. It passes two arguments to the registered callback: `post_type` and `post`. `post_type` is the [post type](https://codex.wordpress.org/post_type) of the post that WordPress is registering meta boxes for. Meanwhile, `post` is the [`WP_Post`](https://codex.wordpress.org/Class_Reference/WP_Post) itself. This can be useful if you need more information than just the post type. There's also the `add_meta_boxes_{post_type}` hook. It's useful if you don't want WordPress to call your callback for every post type. Just replace `{post_type}` with the post type that you want to register a meta box for. WordPress will then only call your callback for that post type. ## Rendering the HTML of a meta box

The other element of a meta box is the rendering of the HTML inside of it. This is the role of the `callback` that we pass to the `add_meta_box` function. This problematic because WordPress doesn't have a clean way to generate HTML. The good thing is that [this is a problem that we've explored before](https://carlalexander.ca/designing-class-generate-wordpress-html-content/). We saw that we could create a small templating system that we could use in our classes. This is also what we'll use here in a modified form. (But no need to read the article, we'll go over it again.) # Building our meta box class

Now that we've done an overview how meta boxes work with WordPress, we can start building a class for them. As usual, we'll start with an empty class. We'll name it `MyPlugin_MetaBox`. ```
class MyPlugin_MetaBox
{
}
```

## Registering our meta boxes

To begin, we need a way to register our `MyPlugin_MetaBox` class as a WordPress meta box. This means that we need a way to convert our `MyPlugin_MetaBox` class into something the `add_meta_box` function understands. There are a few ways for us to do this, but there are two of them that come up more often. The first one is to create one method for each argument of the `add_meta_box` function. The other is to pass all our arguments to the `add_meta_box` function using the [`call_user_func_array`](https://secure.php.net/manual/en/function.call-user-func-array.php) function. Both have their advantages and disadvantages so let's take a second to look at them. ### Passing arguments using the "call\_user\_func\_array" function

Let's start by talking about the `call_user_func_array` function. It's possible that you've never heard of this function before, but, don't let that fool you, it's quite useful. In fact, the plugin API couldn't work without it. The function has two parameters: `callback` and `param_arr`. When you call the `call_user_func_array` function, it'll make a call to another function or method using the two arguments that you pass to it. The function that it'll call will be the one defined by `callback`. (This means that `callback` needs to be [callable](https://secure.php.net/manual/en/language.types.callable.php).) And the arguments that it passes to the `callback` will be the ones in the `param_arr` array. In this situation, we know what the `callback` is. It's the `add_meta_box` function that we want to use to add our meta box. For the `call_user_func_array` function to use it, we just need to pass it a string with `add_meta_box`. (PHP considers this string to be a callable since it's the name of a function that it can execute.) The `param_arr` argument is where our `MyPlugin_MetaBox` class comes into play. We need to create a method that will return the array that we want to pass to the `call_user_func_array` function. We can call it the `get_meta_box_arguments` method. ```
class MyPlugin_MetaBox
{
    // ...
 
    /**
     * Get the arguments for the "add_meta_box" function.
     *
     * @return array
     */
    public function get_meta_box_arguments()
    {
        return array(
            'meta-box-id',
            'Meta Box Title',
            array($this, 'render_meta_box'),
        );
    }
}
```

Now, here's our updated `MyPlugin_MetaBox` class. We added the `get_meta_box_arguments` method to it. It returns an array with three elements in it. Those three elements represent the three required parameters of the `add_meta_box` function. - `meta-box-id` is the `id` of the meta box.
- `Meta Box Title` is the `title` of the meta box.
- `array($this, 'render_meta_box')` is the `callback` that WordPress will use to render the inside of the meta box.

```
function myplugin_add_meta_box() {
    $meta_box = new MyPlugin_MetaBox();
 
    call_user_func_array('add_meta_box', $meta_box->get_meta_box_arguments());
}
add_action('add_meta_boxes', 'myplugin_add_meta_box');
```

And here's how you would register that meta box using the `add_meta_boxes` hook that we saw earlier. The `myplugin_add_meta_box` function first initializes a new instance of our `MyPlugin_MetaBox` class. We then call the `call_user_func_array` function using the array returned by the `get_meta_box_arguments` method as the second argument. ### Passing arguments using one method per argument

The other option is to do the reverse of what we just did and have one method per `add_meta_box` argument. This will increase the size of our `MyPlugin_MetaBox` class since we'll have up to seven methods instead of one. That said, the advantage is that it makes our integration a bit more flexible. It's also easier to understand since it doesn't rely on the `call_user_func_array` function. ```
function myplugin_add_meta_box() {
    $meta_box = new MyPlugin_MetaBox();
 
    add_meta_box($meta_box->get_id(), $meta_box->get_title(), $meta_box->get_callback(), $meta_box->get_screens(), $meta_box->get_context(), $meta_box->get_priority());
}
add_action('add_meta_boxes', 'myplugin_add_meta_box');
```

Here's our `myplugin_add_meta_box` function in this context. We replaced our call to the `call_user_func_array` function with one to the `add_meta_box` function. And instead of a call to the `get_meta_box_arguments` method, we now have calls to six different methods. One for each of the `add_meta_box` arguments. A small note for those that might have noticed. The code above doesn't have a method for the `callback_args` parameter. (That's the 7th and last parameter in the `add_meta_box` function.) This is just to make the example a bit simpler. (Six methods is already a lot!) But, if you need it in your project, feel free to add it! ```
class MyPlugin_MetaBox
{
    /**
     * Get the callable that will the content of the meta box.
     *
     * @return callable
     */
    public function get_callback()
    {
        return array($this, 'render_meta_box');
    }

    /**
     * Get the screen context where the meta box should display.
     *
     * @return string
     */
    public function get_context()
    {
        return 'advanced';
    }

    /**
     * Get the ID of the meta box.
     *
     * @return string
     */
    public function get_id()
    {
        return 'meta-box-id';
    }

    /**
     * Get the display priority of the meta box.
     *
     * @return string
     */
    public function get_priority()
    {
        return 'default';
    }

    /**
     * Get the screen(s) where the meta box will appear.
     *
     * @return array|string|WP_Screen
     */
    public function get_screens()
    {
        return null;
    }

    /**
     * Get the title of the meta box.
     *
     * @return string
     */
    public function get_title()
    {
        return 'Meta Box Title';
    }
}
```

And this is our `MyPlugin_MetaBox` class if we do one method per argument. As you can see, it's quite a bit bigger than the other `MyPlugin_MetaBox` class. (That's why we cut one method. You can cut more of them if you don't need them.) That said, all the methods are simple and just return a value. ## There's no correct way

The complicated thing with design is that it's often about making a decision where neither solution is perfect. Well, this happens to be one of those situations. Both options have their trade-offs, and neither is superior to the other. So it boils down to a personal choice on your part. If you like the idea of using a single method to pass arguments to the `add_meta_box` function, then use that. Otherwise, you can use one method per `add_meta_box` function argument. That said, for this article, we're going to use the second option. If you'd rather go ahead and use the first, you can! There's nothing that we'll see next that you can't do with the first options as well. # Creating the rest of our meta box class

Alright, now that we've decided on how to register our `MyPlugin_MetaBox` class, we can continue building it. In the previous section, our `MyPlugin_MetaBox` class methods returned some values just to show how they worked. But that's not what we want to do with our final class. We have to go back to the drawing board a bit. ## Making our class generic

We want to go back to the drawing because it's easier if we make our class generic. What do we mean by that? Well, we mean that it would be better if we could reuse our `MyPlugin_MetaBox` class to create a variety of meta boxes. The best way to do that is by making all the values that we return from our methods properties of the `MyPlugin_MetaBox` class. We then use a constructor to assign values to these internal class properties. Let's look at what that could look like. ```
class MyPlugin_MetaBox
{
    /**
     * Screen context where the meta box should display.
     *
     * @var string
     */
    private $context;

    /**
     * The ID of the meta box.
     *
     * @var string
     */
    private $id;

    /**
     * The display priority of the meta box.
     *
     * @var string
     */
    private $priority;

    /**
     * Screens where this meta box will appear.
     *
     * @var string[]
     */
    private $screens;

    /**
     * The title of the meta box.
     *
     * @var string
     */
    private $title;

    /**
     * Constructor.
     *
     * @param string   $id
     * @param string   $title
     * @param string   $context
     * @param string   $priority
     * @param string[] $screens
     */
    public function __construct($id, $title, $context = 'advanced', $priority = 'default', $screens = array())
    {
        if (is_string($screens)) {
            $screens = (array) $screens;
        }

        $this->context = $context;
        $this->id = $id;
        $this->priority = $priority;
        $this->screens = $screens;
        $this->title = $title;
    }

    /**
     * Get the screen context where the meta box should display.
     *
     * @return string
     */
    public function get_context()
    {
        return $this->context;
    }

    /**
     * Get the ID of the meta box.
     *
     * @return string
     */
    public function get_id()
    {
        return $this->id;
    }

    /**
     * Get the display priority of the meta box.
     *
     * @return string
     */
    public function get_priority()
    {
        return $this->priority;
    }

    /**
     * Get the screen(s) where the meta box will appear.
     *
     * @return array|string|WP_Screen
     */
    public function get_screens()
    {
        return $this->screens;
    }

    /**
     * Get the title of the meta box.
     *
     * @return string
     */
    public function get_title()
    {
        return $this->title;
    }
}
```

So here's our updated `MyPlugin_MetaBox` class. All the earlier returned values are gone. We've replaced them all with individual properties. The constructor is in charge of assigning values to these properties. It has five parameters: `id`, `title`, `context`, `priority` and `screens`. Their names match the five properties that we added right above it. Both `id` and `title` are mandatory parameters. Meanwhile, `context`, `priority` and `screens` are optional. Their default values are the same as the ones for the `add_meta_box` function. The only unusual thing about the constructor is how we handle the `screens` argument. Since `screens` can be either a string or an array of strings, we want to standardize it as an array of strings. To do that, we check if `screens` is an array using the [`is_array`](https://secure.php.net/manual/en/function.is-array.php) PHP function. If `screens` isn't an array, we [cast it](https://secure.php.net/manual/en/language.types.type-juggling.php#language.types.typecasting) as one. ## Where is the callback method?

It's possible that you've noticed already, but there's one method missing from our updated `MyPlugin_MetaBox` class. It's the `get_callback` method. Why didn't we create a `get_callback` method like the other methods that we created? The reason is that, unlike the other methods that we created, the `get_callback` method is more than just a method that returns a value. It's also how we tell WordPress how we'll render the inside of our meta box. This deserves a bit more of a discussion than the methods that we saw so far. So how will we render the inside of our meta box? Well, this is probably one of the more fascinating parts of designing our `MyPlugin_MetaBox` class. That's because this is the area that you have the most [creative freedom](https://carlalexander.ca/creativity-and-teaching-programming/) as a designer. ### Render the HTML inside our class

The most common solution to this problem is to have our class render the HTML that it needs. This means that our `MyPlugin_MetaBox` class would have a method with HTML in it. Here's an example of what this could look like: ```
class MyPlugin_MetaBox
{
    // ...

    /**
     * Get the callable that will the content of the meta box.
     *
     * @return callable
     */
    public function get_callback()
    {
        return array($this, 'render');
    }

    // ...

    /**
     * Render the content of the meta box.
     *
     * @param WP_Post $post
     */
    public function render(WP_Post $post)
    {
        ?>
        <p>Post title: <?php echo $post->post_title; ?></p>
        <?php
    }
}
```

As you can see, our `MyPlugin_MetaBox` class has its `get_callback` method back! (Yay!) It returns an array with our object and the string `render`. This is a `callable` that represents the `render` method that we added below the `get_callback` method. The `render` method has one parameter: `post`. This is the [`WP_Post`](https://codex.wordpress.org/Class_Reference/WP_Post) object that WordPress is loading our meta box for. We use it to render the HTML inside of our meta box. Right now, this is just a paragraph with the title of our post. #### Using an abstract class

The problem with rendering our HTML this way is that we can't keep our `MyPlugin_MetaBox` class generic. Each different type of meta box would need its own unique `render method. It would then make more sense to make our`MyPlugin\_MetaBox` class an [abstract class](https://carlalexander.ca/polymorphism-wordpress-abstract-classes/) like this: ```
abstract class MyPlugin_MetaBox
{
    // ...

    /**
     * Get the callable that will the content of the meta box.
     *
     * @return callable
     */
    public function get_callback()
    {
        return array($this, 'render');
    }

    // ...

    /**
     * Render the content of the meta box.
     *
     * @param WP_Post $post
     */
    abstract public function render(WP_Post $post);
}
```

Here's our modified `MyPlugin_MetaBox` class. There are two changes that we made to it. We added the [`abstract`](https://secure.php.net/manual/en/language.oop5.abstract.php) keyword in front of the class and `render` method. The first made our `MyPlugin_MetaBox` class an abstract class. And the other makes our `render` method abstract. This lets us defer the implementation of the `render` method to a child class. ```
class MyPlugin_PostTitleMetaBox extends MyPlugin_MetaBox
{
    /**
     * Render the content of the meta box.
     *
     * @param WP_Post $post
     */
    public function render(WP_Post $post)
    {
        ?>
        <p>Post title: <?php echo $post->post_title; ?></p>
        <?php
    }
}
```

And above is an example of what such a child class would look like. The `MyPlugin_PostTitleMetaBox` extends our `MyPlugin_MetaBox` abstract class. It then implements the same `render` method as the one we had earlier. ### Render the HTML using a template

Now, our `MyPlugin_MetaBox` abstract class still doesn't solve the problem that we mentioned earlier. Our `MyPlugin_MetaBox` class isn't generic anymore. We need to create a child class for every type of meta box that we want to create. If we want to keep our `MyPlugin_MetaBox` class generic, we need a way to pass it the HTML that we want to render in its constructor. One of the easiest ways to do that is by using a PHP template to render the HTML. (This is also something that we saw when we looked at designing classes for WordPress admin pages.) We can then pass the path to that template in the constructor of our `MyPlugin_MetaBox` class like this: ```
class MyPlugin_MetaBox
{
    /**
     * Screen context where the meta box should display.
     *
     * @var string
     */
    private $context;

    /**
     * The ID of the meta box.
     *
     * @var string
     */
    private $id;

    /**
     * The display priority of the meta box.
     *
     * @var string
     */
    private $priority;

    /**
     * Screens where this meta box will appear.
     *
     * @var string[]
     */
    private $screens;

    /**
     * Path to the template used to display the content of the meta box.
     *
     * @var string
     */
    private $template;

    /**
     * The title of the meta box.
     *
     * @var string
     */
    private $title;

    /**
     * Constructor.
     *
     * @param string   $id
     * @param string   $emplate
     * @param string   $title
     * @param string   $context
     * @param string   $priority
     * @param string[] $screens
     */
    public function __construct($id, $template, $title, $context = 'advanced', $priority = 'default', $screens = array())
    {
        if (is_string($screens)) {
            $screens = (array) $screens;
        }

        $this->context = $context;
        $this->id = $id;
        $this->priority = $priority;
        $this->screens = $screens;
        $this->template = rtrim($template, '/');
        $this->title = $title;
    }

    // ...
}
```

Our `MyPlugin_MetaBox` class constructor now has a sixth parameter: `template`. Like the other constructor parameters, we assign it to an internal property with the same name. But that's where the similarities with what we've done before end. Right away, we have a small difference when we assign the `template` argument to its property. We pass it through the [`rtrim`](https://secure.php.net/manual/en/function.rtrim.php) function. We do this to ensure that `template` never ends with a slash. #### Rendering our template

Next, we need to rework the `render` method that we had previously. Instead of using HTML right in the class, we need to use our `template` to render the HTML. Lucky for us, this isn't too hard to do! ```
class MyPlugin_MetaBox
{
    // ...

    /**
     * Render the content of the meta box using a PHP template.
     *
     * @param WP_Post $post
     */
    public function render(WP_Post $post)
    {
        if (!is_readable($this->template)) {
           return;
        }

        include $this->template;
    }
}
```

Above is our new `render` method. It first starts by checking if we can read the template file. We do that by passing the `template` value to the [`is_readable`](https://secure.php.net/manual/en/function.is-readable.php) PHP function. If the file found at the location stored in `template` isn't readable, we return right away. Otherwise, we use the [`include`](https://secure.php.net/manual/en/function.include.php) statement to include the PHP file at `template` location. This will cause PHP to evaluate the file and generate the HTML inside it. (You can read more on how to generate HTML that way [here](https://carlalexander.ca/designing-class-generate-wordpress-html-content/).) ```
<p>Post title: <?php echo $post->post_title; ?></p>
```

So what would our template contain in this scenario? Well, it would contain the same HTML code that we had earlier. This is what you can see above. But wait... we can use the `post` variable inside our template? Yes, we can! PHP considers our template to be within the same [scope](https://secure.php.net/manual/en/language.variables.scope.php) as the `render` method. # A great foundation

And that's it for our `MyPlugin_MetaBox` class! Because we made the class generic, you can use it to create all sorts of different meta boxes. And, as you use it more, you can look at extending it to deal with more specific scenarios. But, for now, you can find the complete `MyPlugin_MetaBox` class below: ```
/**
 * A WordPress meta box that appears in the editor.
 */
class MyPlugin_MetaBox
{
    /**
     * Screen context where the meta box should display.
     *
     * @var string
     */
    private $context;

    /**
     * The ID of the meta box.
     *
     * @var string
     */
    private $id;

    /**
     * The display priority of the meta box.
     *
     * @var string
     */
    private $priority;

    /**
     * Screens where this meta box will appear.
     *
     * @var string[]
     */
    private $screens;

    /**
     * Path to the template used to display the content of the meta box.
     *
     * @var string
     */
    private $template;

    /**
     * The title of the meta box.
     *
     * @var string
     */
    private $title;

    /**
     * Constructor.
     *
     * @param string   $id
     * @param string   $emplate
     * @param string   $title
     * @param string   $context
     * @param string   $priority
     * @param string[] $screens
     */
    public function __construct($id, $template, $title, $context = 'advanced', $priority = 'default', $screens = array())
    {
        if (is_string($screens)) {
            $screens = (array) $screens;
        }

        $this->context = $context;
        $this->id = $id;
        $this->priority = $priority;
        $this->screens = $screens;
        $this->template = rtrim($template, '/');
        $this->title = $title;
    }

    /**
     * Get the callable that will the content of the meta box.
     *
     * @return callable
     */
    public function get_callback()
    {
        return array($this, 'render');
    }

    /**
     * Get the screen context where the meta box should display.
     *
     * @return string
     */
    public function get_context()
    {
        return $this->context;
    }

    /**
     * Get the ID of the meta box.
     *
     * @return string
     */
    public function get_id()
    {
        return $this->id;
    }

    /**
     * Get the display priority of the meta box.
     *
     * @return string
     */
    public function get_priority()
    {
        return $this->priority;
    }

    /**
     * Get the screen(s) where the meta box will appear.
     *
     * @return array|string|WP_Screen
     */
    public function get_screens()
    {
        return $this->screens;
    }

    /**
     * Get the title of the meta box.
     *
     * @return string
     */
    public function get_title()
    {
        return $this->title;
    }

    /**
     * Render the content of the meta box using a PHP template.
     *
     * @param WP_Post $post
     */
    public function render(WP_Post $post)
    {
        if (!is_readable($this->template)) {
            return;
        }

        include $this->template;
    }
}
```

*Photo Credit: [Dhruv Deshmukh](https://unsplash.com/photos/N9UuFddi7hs)*