The basic OOP code structure of a WordPress Plugin – #1

This entry is part 1 of 2 in the series Develop WP Plugin OOP

So, we have decided to make a series of tutorials for coding your own WordPress Plugin from scratch using OOP concept. The best thing about PHP is, it supports both procedural style coding as well as Object Oriented Style coding. All our previous tutorials under WordPress has, for the sake of simplicity, followed procedural style coding. Now it is time for us, (as well as you) to deprecate the procedural style and adopt OOP style for coding all our WP Plugins. The main advantages of OOP are, undoubtedly:

  • Simplicity: We shall create the objects representing your real world problems. So it has better understanding to our natural instincts.
  • Structured: Objects and Classes are the heart of any OOP. PHP is not an exception. With classes you get a well detailed structure of methods you would use for a particular functionality. You don’t overload your plugin with mixed up methods. The methods you need are encapsulated inside a class and you have them only when you need them.
  • Less Redundancy (Re-usability): OOP gives a whole new dimension of reducing the redundancy of your code. In simpler language, you don’t have to write the same code again and again for similar features. Through the usage of polymorphism and inheritance, you are assured that you write the code only once and access only when you need them. Although, I personally find the scope of less redundancy depends entirely upon the programmer which is true. So you have to be smart (don’t worry, we will have minimum redundancy through out all the codes in the series of tutorials) to get the maximum out of it.
  • Easy Maintenance: If you have structured your classes properly, documented them well then you will find that maintaining your plugin becomes easier. For adding or removing a features you don’t need to look at every single bit of the source code. You can just concentrate on the part where you want.

Personally, I use a rather complex framework for all my plugin development. I will release it on web soon as I complete the documentation. But, we will not cover our own framework, rather we will focus on how to create your own framework for your own WP Plugins. For this series I have developed a simpler framework with OOP approach which will be available for download after we have published a few of the mandatory posts in the series. If you have started digging into the WP API world, then you will find this very useful.

So, enough of introduction. Lets dive into the code.

#1: Directory & File structure:

#1.1: Basic Structure:

As said in the WordPress official Writing a Plugin documentation, we should have at least the following two files inside ourown plugin directory.

  • Main plugin file(my_plugin.php): This is the main plugin file loaded by WordPress on plugin activation. This file should have all the plugin header  information in form of WordPress recognizable comments. (Discussed inpart 4.0).
  • ReadMe file (readme.txt): This file contains additional plugin information such as description, faqs, screenshot links, changelog etc. We will not focus on creating a readme file. You can find more information about it here and a sample readme.txt file could be found be seen here.

Well that is just the basics. Over the years while developing many WordPress plugins, I have found that these two files are not just sufficient to code a feature rich and maintainable plugin. For my convenience I use the following directory structure.

#1.2: Recommended Structure:

As you can see in the above image, we use the following directory structure along with the basic two files:

  • classes: The directory which contains all the classes.
    • admin-class.php: The library of all the WP Admin Menu integration classes. All the classes should do everything it needs including hooking itself into the admin menu, loading its scripts and styles, hooking into the admin-post save action etc etc. More details will be available soon as we proceed with the series.
    • install-class.php: This class is fired once during the installation. It should have methods to add all the options and create all the database tables we want to work with.
    • loader.php: This file contains only one class, and that is the loader class. It does everything the plugin itself needs to do by firing other classes and hooking them up properly. Also it interacts with the admin-class classes to get the pagehook as they hooks themselves into the admin menu. If all these sounds Greek to you, then no worries. We would have them covered in the later tutorials of this series.
  • includes: This directory should contain all the php files where you have written general procedural style functions which needs to be included. This is handy if you want to provide your own set of API for the plugin.
  • css: Consists of all the CSS stylesheet .css files.
  • images: Contains all the image files.
  • js: Contains all the javascript .js files.
  • translations: This directory contains all the .po and .mo files required to make your internationalized.
  • itg_plugin.php: Name it anything you want. This will have the minimum code to ignite your plugin loader class and will contain the plugin header information.

Sounds good? Great. Please be aware that this is only a guide for optimizing the structure. I personally use similar structure. If you wish, then you should tweak/change the structure so that it fits your needs.

#2: Understanding the loader class:

From the point I use the loader class, it does several things, like:

  • Initializing the text domain.
  • Instantiating all the admin classes.
  • Enqueuing the common script and styles across your admin menu pages.
  • Registering the activation hook, ie, using the install class on plugin activation.
  • and so on…

Basically it does all the things which you would have done directly in the plugin main file while coding in procedural style. As you can see, in our example it does the following basic jobs:

#2.1: Storing some static variables:

For our ease during the constructor call, we store the following static variables:

  • init_classes: Array of name of admin classes that should be instantiated.
  • abs_path: The absolute path of the directory of the main plugin file.
  • abs_file: The absolute path of the main plugin file.

To do that, we use the constructor like this:

    /**
     * Constructor function
     * @global array $wp_admrs_info The information option variable
     * @param string $file_loc The __FILE__ passed from the main file
     * @param array $classes The name of classes to look for and instantiate
     */
    public function __construct($file_loc, $classes) {
        self::$abs_path = dirname($file_loc);
        self::$abs_file = $file_loc;
        self::$init_classes = $classes;
    }

#2.2: The main load method:

We write the main load method as public in order to call it from the main plugin file. In fact this method should be only one that has to be called from the main file. Some example features of the load method would be to register the activation hook and load the plugin text domain which we shall see shortly. The sample code is written below:

    /**
     * The main load method
     * It should be called from the plugin main file in order to init the plugin
     */
    public function load() {
        //activation hook
        register_activation_hook(self::$abs_file, array(&$this, 'plugin_install'));
        /** Load Text Domain For Translations */
        add_action('plugins_loaded', array(&$this, 'plugin_textdomain'));

        //admin area
        if(is_admin()) {
            //admin menu items
            $this->init_admin_menus();
            add_action('admin_init', array(&$this, 'gen_admin_menu'), 20);

            //wp_ajax
            //add_action('wp_ajax_itgdb_iwi_view', array(&$this, 'admin_form_ajax'));
        } else {
            //add frontend script + style
            //add_action('wp_print_styles', array(&$this, 'enqueue_script_style'));

            //add a shortcode
            //add_shortcode($tag, array(&$this, 'function_name'));
        }

        //other filters + actions
        //add_action($tag, $function_to_add);
        //add_filter($tag, $function_to_add);
    }

As you can see, we do two important things:

#2.2.1: Activation hook:

We do it through register_activation_hook WordPress API. It accepts two parameters:

  • $file: The filename (including path) of the main plugin file. We pass our self::$abs_file in which we had stored the absolute path of the main plugin file.
  • $function: The callback function. This function is called during the plugin activation. We pass the plugin_install method of the loader class. We shall come to this shortly.

So in the callback function we have hooked in the loader public method plugin_install. In the example it does the following

    /**
     * The plugin Install method
     * Creates an instance of the install-class and fires the installation method
     * It is used only during the activation of the plugin
     */
    public function plugin_install() {
        include_once self::$abs_path . '/classes/install-class.php';

        $install = new wp_admrs_install();
        $install->install();
    }

We shall discuss the more of how the installation is carried through and what you can do inside it in the next post of this series.

#2.2.2: Loading Text Domain:

We load the text domain using the load_plugin_textdomain. Technically it takes three parameters:

  • $domain: The domain of the translation. This should be a unique string in order to avoid conflict with other plugins.
  • $abs_rel_path: The relative path to the translation directory with respect to ABSPATH. This is deprecated so we pass false in there.
  • $plugin_rel_path: The relative path to the translation directory with respect to WP_PLUGIN_DIR. So we pass in the translation directory relative path using dirname(plugin_basename(self::$abs_file)) . ‘/translations/’.

The load_plugin_textdomain is written inside the public method plugin_textdomain which is hooked to the plugin loaded action using the load method.

    /**
     * Load the text domain on plugin load
     * Hooked to the plugins_loaded via the load method
     */
    public function plugin_textdomain() {
        load_plugin_textdomain('itg_textdomain', false, dirname(plugin_basename(self::$abs_file)) . '/translations/');
    }

We shall discuss more about the loader class in some future article of this series. This will cover adding common styles and scripts to all your plugin pages and more.

#3: Understanding the admin classes:

We start by creating an abstract class for the admin area. During developing plugins, I have found that while abstracting the admin pages, it should contain the following common features:

  • Storing POST variables: The class should properly store the post variables with escaped and strip slashed (in case magic code gpc is on) values.
  • Page Hook Reference: The class should be able to hook into its own and independent set of styles and scripts. For that whenever it adds to the admin menu, it should store the returned pagehook variable for future use.
  • Action Nonce: Whenever  we do any post operation or any other operation through admin-post.php file, it should be verified with nonce. The class should have a centralized variable for referencing all the action nonces.
  • Capability: An admin page is meant to be used by only a certain set of users (say admin and/or editors). So, it should have its own defined capability.
  • URL Reference: The static js, css and image URLs should be refered through a single array for quicker use.
  • Admin Metaboxes: The WordPress has a great UI in the dashboard which is the metabox. We want to use this feature easily through our admin pages. So the hooks and filters required to use metaboxes should be explicitly abstracted.
  • Action response message: When the user does some action through our plugin (like save the settings) it should show some message regarding what have been done.
  • Abstraction for common HTML: The commonly used HTML elements like form head, form foot, printing checkbox, radiobox, textarea etc… should be abstracted for quicker use and minimum redundancy.
  • Communicating to the loader through pagehook: In simpler words the abstraction should have some concrete method to return the pagehook created by the class to the loader class. This is necessary so that the loader can hook into common js and css files.
  • admin-post.php action hook: We shall process all our post requests through the admin_post_ hook. So it should also be abstracted for quicker usage and lesser redundancy.

Keeping all this aspects in mind, we shall code our base abstract class. We shall discuss in details in some future post.

#4: Understanding the main plugin file:

The main plugin file is as clean as possible. It does the following things:

#4.0: Plugin header and license:

/*
Plugin Name: iTg Sample Plugin
Plugin URI: https://www.intechgrity.com/
Description: Sample WordPress plugin coded in OOP enqueuing own styles and scripts
Author: Swashata
Version: 1.0.0
Author URI: http://www.swashata.com
 */

/*
    Copyright Swashata Ghosh, 2011
    My plugins are created for WordPress, an open source software
    released under the GNU public license
    <http://www.gnu.org/licenses/gpl.html>. Therefore any part of
    my plugins which constitute a derivitive work of WordPress are also
    licensed under the GPL 3.0. My plugins are comprised of several
    different file types, including: php, cascading style sheets,
    javascript, as well as several image types including GIF, JPEG, and
    PNG. All PHP and JS files are released under the GPL 3.0 unless
    specified otherwised within the file itself. If specified as
    otherwise the files are licesned or dual licensed (as stated in
    the file) under the MIT <http://www.opensource.org/licenses/mit-license.php>,
    a compatible GPL license.
 */

To know what the plugin header does and how to customize it, I recommend reading WP’s own documentation.

#4.1: Include the loader and other files:

//include the loader and other include files
include_once dirname(__FILE__) . '/classes/loader.php';
include_once dirname(__FILE__) . '/includes/my-plugin.php';

#4.2: Include admin and frontend specific files:

//load admin specific classes and frontend classes separately
if(is_admin()) {
    include_once dirname(__FILE__) . '/classes/admin-class.php';
} else {
    //load frontend specific files
    //will be discussed later in some example
}

#4.3: Declare the global variables:

//declare the global variables

/**
 * Global variable description
 * @global array
 */
global $my_global;

#4.4: Instantiate the Loader:

//instantiate the loader
$plugin_init = new itg_loader(__FILE__, array('my_admin_class_one', 'my_admin_class_two', 'my_admin_class_three'));

$plugin_init->load();

So, that was all. If we put together the code, it looks something like this:

<?php
/*
Plugin Name: iTg Sample Plugin
Plugin URI: https://www.intechgrity.com/
Description: Sample WordPress plugin coded in OOP enqueuing own styles and scripts
Author: Swashata
Version: 1.0.0
Author URI: http://www.swashata.com
 */

/*
    Copyright Swashata Ghosh, 2011
    My plugins are created for WordPress, an open source software
    released under the GNU public license
    <http://www.gnu.org/licenses/gpl.html>. Therefore any part of
    my plugins which constitute a derivitive work of WordPress are also
    licensed under the GPL 3.0. My plugins are comprised of several
    different file types, including: php, cascading style sheets,
    javascript, as well as several image types including GIF, JPEG, and
    PNG. All PHP and JS files are released under the GPL 3.0 unless
    specified otherwised within the file itself. If specified as
    otherwise the files are licesned or dual licensed (as stated in
    the file) under the MIT <http://www.opensource.org/licenses/mit-license.php>,
    a compatible GPL license.
 */
//include the loader and other include files
include_once dirname(__FILE__) . '/classes/loader.php';
include_once dirname(__FILE__) . '/includes/my-plugin.php';

//load admin specific classes and frontend classes separately
if(is_admin()) {
    include_once dirname(__FILE__) . '/classes/admin-class.php';
} else {
    //load frontend specific files
    //will be discussed later in some example
}

//declare the global variables

/**
 * Global variable description
 * @global array
 */
global $my_global;

//instantiate the loader
$plugin_init = new itg_loader(__FILE__, array('my_admin_class_one', 'my_admin_class_two', 'my_admin_class_three'));

$plugin_init->load();

//and nothing more

But original code will vary a lot as you develop different plugins with different structures. This is just the basic guide and structure I like to follow. You are obviously free to modify it (and I think you should).

So, that was all about the basic OOP structure of a simple and ideal WordPress Plugin. Stay tuned as we dive into more detailed discussion. At this stage, I can not say how many tutorials are going to get published. But at the end of this, you should be able to start writing your own WP Plugin from scratch (using OOP of course).

Coming up next:

For every tutorial in this series, you will find this section where we will put at least the 5 upcoming tutorials. This will also help me keep the series organized. The items will be linked once they are published and the list will gradually increased with every tutorials published. This is the first tutorial of the series. So here is the list of mandatory posts after which the download link will be published.

  • Create default options and database tables during your plugin installation
  • Create user roles and capabilities during plugin installation
  • Enqueue your own styles and scripts to the admin pages of your WP Plugin
  • Create a basic settings page of your WP Plugin with own icon and form
  • Hook into the admin-post.php and save your WP Plugins settings data on post
  • Create your WP Plugin’s top level admin menu with sub pages and hook them properly
  • Use the WordPress metabox functionality in your WordPress Plugin
  • Hook all your admin classes through loader class – The ABC of the loader class
  • Init your loader class through the main plugin file
  • Add proper uninstallation file to your WordPress Plugin

So 10 more tutorials in the queue, hopefully they will get published in the upcoming 15 days. Stay tuned and check out the download link as it will get published.

7 comments

  1. Pingback: Automatic primary site in WordPress Multisite to creating user

  2. Pingback: Automatic login redirect to primary site & block main site admin WP-MS

  3. Pingback: Add default options during WordPress Plugin Installation - Options API

Leave a Reply