artikus11
9/15/2018 - 9:11 AM

Using classes in WordPress plugins. Please comment!

Using classes in WordPress plugins. Please comment!

<?php
/* Using Classes in WordPress Plugins */


/**
 * The "normal" way.  Actions are added in a constructor. Create a new instance
 * of the class to "bootstrap" the plugin.
 *
 * Two ways to instantiate:
 *  1. anonymous:
 *      new MyPlugin1;
 *  2. assign it to a variable
 *      $myplugin1 = new MyPlugin;
 *
 * The first makes it nearly impossible to unhook things. The second doesn't.
 *
 * Benefits:
 *  - Easy to understand and implement
 *  - Most documentation on using classes in plugins does this
 *
 * Questionable:
 *  - Makes it harder to unhook things?
 */
class MyPlugin1
{
    public function __construct()
    {
        add_action('plugins_loaded', array($this, 'loaded'));
    }

    public function loaded()
    {
        // do stuff
    }
}


/**
 * The "static" way.  All methods are static. Functions that get hooked in
 * are done so with an array containing the fully qualified class name and
 * method name.
 *
 * eg.
 *  1. add_action('some_action', array(__CLASS__, 'actor'));
 *  2. add_action('some_action', array(get_class(), 'actor'));
 *  3. add_action('some_action, array('MyPlugin2', 'actor')); 
 *  4. add_action('some_action', 'MyPlugin2::actor');
 *
 * Number 3 & 4 is more limited, especially if using namespaces. Change the
 * class name and you'll need to change it everywhere.
 *
 * Actions are added in a central `init` method (or whatever) that pretty
 * much acts as a constructor.
 *
 * Bootsrap the class and all all the actions and such:
 *  MyPlugin2::init();
 *
 * Benefits:
 *  - Very easy to unhook things
 *  - Relatively straightforward to implement and grok
 *
 * Questionable:
 *  - Seems more verbose?
 *  - Can't take advantage of object state in the same ways
 *  - PHP < 5.3 doesn't have late static binding: static properties
 *    behave in unexpected ways in subclasses.
 */
class MyPlugin2
{
    public static function init()
    {
        add_action('plugins_loaded', array(__CLASS__, 'loaded'));
    }

    public static function loaded()
    {
        // do stuff
    }
}


/**
 * The "singleton" way. Use a few static methods for the singleton pattern,
 * add the real hooks in the constructor.
 *
 * Bootstrap the class and all all the actions the same way as you would for
 * the "static" route:
 *  MyPlugin3::init();
 *
 * Or just call the instance method to bootstrap things.
 *  MyPlugin3::instnace();
 *
 * Advantages:
 *  - Easy to unhook stuff: 
 *    remove_action('plugins_loaded', array(MyPlugin3::instance(), 'loaded'), 20);
 *  - Take advantage of object state and more predictable class attributes
 *  - We really only want one instance of this object running around. Singleton
 *    pattern takes care of that.
 *
 * Questionable
 *  - Because of the weirdness of static methods, you'd basically write the
 *    same bolier plate on every plugin class. There are certain singleton
 *    pattners that could alleviate that.  Example:
 *    https://github.com/habari/system/blob/master/classes/singleton.php
 *  - Overly complex?
 */
class MyPlugin3
{
    private static $ins;

    public static function init()
    {
        add_action('plugins_loaded', array(__CLASS__, 'instance'));
    }

    public static function instance()
    {
        is_null(self::$ins) && self::$ins = new self;
        return self::$ins;
    }

    private function __construct()
    {
        add_action('plugins_loaded', array($this, 'loaded'), 20);
    }

    public function loaded()
    {
        // do stuff
    }
}


/**
 * A way to do the singleton pattern with a base class and PHP 5.3+
 *
 */
abstract class Singleton
{
    /**
     * Container for the objects.
     *
     * @since   0.1
     */
    private static $registry = array();

    /**
     * Get an instance of the current, called class.
     *
     * @since   0.1
     * @access  public
     * @return  object An instance of $cls
     */
    public static function instance()
    {
        $cls = get_called_class();
        !isset(self::$registry[$cls]) && self::$registry[$cls] = new $cls;
        return self::$registry[$cls];
    }

    /**
     * Init method that adds the `instance` method of the called class to the 
     * `plugins_loaded` hook.
     *
     * @since   0.1
     * @uses    add_action
     * @return  void
     */
    public static function init()
    {
        add_action('plugins_loaded', array(get_called_class(), 'instance'));
    }

    /**
     * Kill the __clone method.
     *
     * @since   0.1
     */
    private final function __clone()
    {
        // empty
    }

    /**
     * Subclasses must define construct, this should be where all the other
     * actions/filters are added.
     *
     * @since   0.1
     * @access  protectect
     */
    abstract protected function __construct();
}

class MyPlugin extends Singleton
{
    protected function __construct()
    {
        add_action('init', array($this, 'do_stuff'));
    }

    public function do_stuff()
    {
        // do stuff
    }
}