PMG Digital Made for Humans

Building Truly Decoupled WordPress Themes

4 MINUTE READ | April 17, 2015

Building Truly Decoupled WordPress Themes

Author's headshot

Christopher Davis

Christopher Davis has written this article. More details coming soon.

When you’re building a WordPress site some things belong in themes and some in plugins. Usually anything having to do with display is in the theme and anything that might be used elsewhere should be in a plugin.

As an example: take a custom post type and a custom meta box. Those are things that your end user will probably want to keep around after a theme change. They both belong in a plugin. Because you’re a really good WordPress dev, you don’t use magic strings and instead built a nice set of wrapper methods to fetch those custom meta values for your post type. Maybe as a nice object that someone can pass a post ID into and use.

CustomerMeta.php

<?phpnamespace PMG\ExampleCustomer;/** * An API class for fetching custom metadata for a given customer post type. */final class CustomerMeta{    const WEBSITE_LINK = '_pmg_examplecustomer_websitelink';    /**     * The customer's post ID     *     * @var     int     */    private $postId;    public function __construct($postId)    {        $this->postId = $postId;    }    /**     * Get the link to the customer's home page.     *     * @return  string     */    public function getWebsiteLink()    {        return get_post_meta($this->postId, self::WEBSITE_LINK, true);    }    // ...}

Should your theme use that class from the plugin? No. Not if you’re building a decoupled WordPress theme it shouldn’t. What happens if your plugin is deactivated? Things break. Instead we should wrap up our API with something in our theme that hides the fact that there’s a plugin involved:

Customers.php

<?phpfinal class Customers{    private $cache = [];    private static $ins = null;    public function __construct()    {        $this->hasPlugin = class_exists('PMG\\ExampleCustomer\\CustomerMeta');    }    // if you want to share the same instance...    public static function instance()    {        if (!self::$ins) {            self::$ins = new self();        }        return self::$ins;    }    public function getWebsiteLink($postId)    {        $customer = $this->loadCustomer($postId);        return $customer ? $customer->getWebsiteLink($postId) : null;    }    private function loadCustomer($postId)    {        if (!$this->hasPlugin) {            return null;        }        if (!isset($this->cache[$postId])) {            $this->cache[$postId] = new \PMG\ExampleCustomer\CustomerMeta($postId);        }        return $this->cache[$postId];    }}

The above is a bit obscure, so let’s use something more real world. Let say you’re relying pretty heavily on Advanced Custom Fields (ACF). Should you use get_field like the documentation says?.

Nope. It’s the same idea as above: a deactivated ACF plugin causes the theme to fail with a function does not exist fatal error. Use a wrapper:

acf.php

<?phpfunction pmg_exampletheme_get_field($field){    if (!function_exists('get_field')) {        return null;    }    return get_field($field);}

Depends means that the theme can’t do its job at all without a plugin. That’s okay, just change your wrapper a bit:

acf2.php

<?phpfunction pmg_exampletheme_get_field($field){    if (!function_exists('get_field')) {        throw new \LogicException('The Advanced Custom Fields Plugin is Not Loaded');    }    return get_field($field);}

Not having a dependency you need is a truly exceptional situation so throw an exception and let your error handler log it on production or show the error on dev.

Why is this different from a fatal error? Context. The exception tells you exactly what’s wrong and how to fix it. This context is not for you or the person who did the ACF integration. It’s for other developers who might not have reviewed the latest changes. Or, more likely, it’s for future you who will have totally forgotten the context by the time the error crops up again.

Here’s an example:

actions.php

<?php// in a theme filedo_action('pmg_exampletheme_some_action', $post);// in a pluginadd_action('pmg_exampletheme_some_action', 'pmg_exampleplugin_some_action');function pmg_examplepluing_some_action($post){    // do stuff with $post to create some nice display}

Stay in touch

Bringing news to you

Subscribe to our newsletter

By clicking and subscribing, you agree to our Terms of Service and Privacy Policy

I think this is a bad solution. It’s the theme’s job to do frontend display. Mixing that into plugins is a recipe for an unmaintainable blob. Sometimes it makes sense to do the above, but most times it doesn’t. Let the WordPress template system do its job and decouple by creating nice abstractions within your theme.


Related Content

thumbnail image

AlliPMG CultureCampaigns & Client WorkCompany NewsDigital MarketingData & Technology

PMG Innovation Challenge Inspires New Alli Technology Solutions

4 MINUTES READ | November 2, 2021

thumbnail image

Applying Function Options to Domain Entities in Go

11 MINUTES READ | October 21, 2019

thumbnail image

My Experience Teaching Through Jupyter Notebooks

4 MINUTES READ | September 21, 2019

thumbnail image

Working with an Automation Mindset

5 MINUTES READ | August 22, 2019

thumbnail image

3 Tips for Showing Value in the Tech You Build

5 MINUTES READ | April 24, 2019

thumbnail image

Testing React

13 MINUTES READ | March 12, 2019

thumbnail image

A Beginner’s Experience with Terraform

4 MINUTES READ | December 20, 2018

ALL POSTS