Writing WordPress plugins the Object Oriented way

Writing WordPress plugins the Object Oriented way

So, you want to take your plugin game to the next level?  You want to load resources easily, but only when they are needed?  Looking to keep your code localized so it doesn't have collisions with other code?  Interested in an easy way to do versioning?

That's great!

Don't want to read the article? Get the Repo.

I love for themes and plugins to be based on the Object Oriented Programming ( OOP for short )  paradigm with auto loading build in.    Maybe you have heard of it, maybe you haven't, here is why I recommend following the methodology:

  • Reusability in your code.  It's easy to re-use your work in multiple projects without having to worry about functional conflicts with other pieces of code.
  • Maintainability.  It helps keep code organized.  That makes it easy to find and fix any errors and debug easily.  Unit testing is beyond the scope of this article, but will be covered soon.
  • Extendability.  When was the last time a project you worked on really complete?  If you freelance and you don't have a dialog with your client about extending the contract, then you're leaving money on the table.
  • Encapsulation.   Although this covers a lot, I always like to use it as a way to protect incoming data.  Rule #1 is to never trust data coming from your user.   This allows you to validate and protect your data and methods, aka functions.  It allows you to keep your code more secure and will make your life easier in the long run.

That being said, we are going to break this very simple plugin into three pieces:  The Autoloader, Common Code and Administrative Code.  

Holy cow, three files for a plugin?   Why?!

As a big believer in segmentation, I like to keep code separate.  I do not want the front end user loading all of my administrative code.  This keeps the front end experience running lean and also makes sure they don't have access to all of the functions that the administrative side has.  That keeps things safe and simple.    There are essentially two ways to handle objects in PHP: Static or Instantiated.  Which one should you use?  That depends on what your data is going to do.  Static definitions will share data across all instances, instantiated will not, which may sound like a mouthful.  Generally, you are okay writing static classes.  You're only using one theme per page load, so it's a great example of when to use a static class.  But, you might be iterating through multiple posts ( class \WP_Post ) and since they don't share data, they are instantiated.  Hopefully that helps make sense of it, if not, let me know in the comments.  We are going to write a static plugin in this how to.

Autoloader - Plugin.php

This file has two main goals.  It is the base of our plugin and needs to include the appropriate File Headers that any plugin should have.   After you get out of the header block, you have a few things to put together:  Your Namespace, autoloader and a launch point.   Namespaces are important here.  We use them as a structure to know where files should be loaded from.  It will take away the need to use the require and include functions you have gotten used to.   To keep it simple, if you're trying to bring a the Crumbls\Plugins\Example\Common class online and are in the Crumbls\Plugin\Example namespace, all you have to do is bring Common online. It will search in the src/Common/Plugin/Example folder for a file named Common.php and automatically load it, if it exists.  I'll talk about extending this at the end of the article.

<?php
/*
Plugin Name: Example OOP Plugin
Plugin URI: https://crumbls.com/writing-wordpress-plugins-the-object-oriented-way/
Description: A very basic OOP plugin example.
Version: 0.1.0
Author: Chase C. Miller
Author URI: https://crumbls.com
*/

// This line is our namespace. You want to keep it unique to you. You will use this throughout the plugin.
namespace Crumbls\Plugin\Example;

// Easy trap door.
defined('ABSPATH') || exit(1);

/**
 * This is our autoloader.
 * It does all of the heavy lifting to find files you are looking for.
 * It searches in the plugin/src directory for classes to automatically include as you call them.
 * That helps keep unneeded classes from hogging resources.
 **/
spl_autoload_register(function ($class) {

    // Simple Sanitization
    $class = str_replace(' ', '', ucwords(preg_replace('#[^\da-z/]#i', ' ', str_replace('\\', '/', $class))));
    // Set file path
    $file = __DIR__ . '/src/' . $class . '.php';

    if (file_exists($file)) {
        require_once($file);
        return true;
    }

    return false;
});

/** 
* This is a simple switch to load code if you're in the /wp-admin side
* of things, or floating around on the front end. We are calling the static
* getInstance method to get our actual plugin rolling.
**/ if (is_admin()) { Admin::getInstance(); } else { Common::getInstance(); }

Common Code - src/Crumbls/Plugins/Example/Common.php

The autoloader is in place, now it's time to write our first real piece of the library.  It's goal is to handle front end requests.  You might end up extending it to add REST endpoints and more.

<?php

// This line is our namespace. You want to keep it unique to you. You will use this throughout the plugin.
namespace Crumbls\Plugin\Example;

// Easy trap door.
defined('ABSPATH') || exit(1);

/**
 * Class Common
 * @package Crumbls\Plugin\Example
 */
class Common
{

    /**
     * Placeholder for our instance.
     * @var null
     */
    protected static $_instance = NULL;

    /**
     * An easy way to get our current class name
     * @var null
     */
    protected static $_class = NULL;

    /**
     * Prevent direct object creation.
     */
    final private function __construct()
    {
    }

    /**
     * Prevent object cloning.
     */
    final private function __clone()
    {
    }

    /**
     * Returns new or existing Singleton instance.
     * @return Singleton
     */
    final public static function getInstance()
    {
        if (null !== static::$_instance) {
            // Instance already exists, return it.
            return static::$_instance;
        }
        // Declare our instance.
        static::$_instance = new static();
        /**
         * Late model binding to get our class name.
         * Useful for when you extend.
         **/
        static::$_class = get_called_class();
        // Execute our _init method.
        static::_init();
        return static::$_instance;
    }

    /**
     * A static method that executes any time the common code is generated.
     */
    protected static function _init()
    {
        /**
         * This functions a lot like a __construct in a instantiated class.
         */

        // Add the getFooter method to get_footer action.
        add_action('get_footer', [static::$_class, 'getFooter']);
    }

    /**
     * Display code in the administrative footer.
     * It override's the common
     */
    public static function getFooter()
    {
        ?>
        <p>Content in the front end's footer. This code comes from <?php echo __METHOD__; ?></p>
        <?php
    }
}

Administrative Code - src/Crumbls/Plugins/Example/Admin.php

You have your autoloader set and the code that runs on the front end of the site.  Now, you get to write the code that only loads in /wp-admin and ajax calls.  This code never gets executed on the front end of the site.  This helps with security and keeping the front end lean.  We wrote it as an extension of the common code.  Why?  Because we don't want to re-invent the wheel.  One of the greatest things about OOP programming is being able to extend code.  This lets you add extra functionality to what you already wrote, but give it a specific use case.  You can then access the inherited methods and variables, as long as they are not defined as private.

<?php

// This line is our namespace. You want to keep it unique to you. You will use this throughout the plugin.
namespace Crumbls\Plugin\Example;

// Easy trap door.
defined('ABSPATH') || exit(1);

/**
 * Class Admin
 * @package Crumbls\Plugin\Example
 */
class Admin extends Common
{
    /**
     * We override Common's _init method, but still call it to bring
     * it online.
     */
    protected static function _init()
    {
        /**
         * If you want to call the Common's _init to bring any shared code online,
         * then uncomment the following line.
         */
//        parent::_init();

        /**
         * This functions a lot like a __construct in a instantiated class.
         */

        // Add the getFooter method to admin_footer action.
        add_action('admin_footer', [static::$_class, 'getFooter']);
    }

    /**
     * Display code in the administrative footer.
     * It override's the common
     */
    public static function getFooter()
    {
        ?>
        <p>More content in the Admin footer. This code comes from <?php echo __METHOD__; ?></p>
        <?php
    }
}

What if you wanted to use external libraries in your code?

That's the beauty of the autoloader.  As long as your libraries are named correctly and adopting the modern standard of namespacing, you're life is incredibly easy.   Say you found a plugin that tells you the winning lottery numbers.   It's written in a namespace of Life\Hacks and the plugin's class name is Lottery.  The Lottery class would generally end up being at src/Life/Hacks/Lottery.php.  All you'd have to do is bring the class online, usually via something similar to $plugin = new \Life\Hacks\Lottery() or \Life\Hacks\Lottery::getInstance();   You no longer need to use the require() and include() functions, since that's handled for you.