OOD in WordPress Plugins

facebooktwittergoogle_pluslinkedin

The past half year I have been working extensively on the development of a couple of WordPress plugins. I have touched on some of the issues I have encountered in a previous post already. But I would like to touch a bit more on the plugin development in regard to object-oriented design in particular.

Coming from the Java world it is very interesting to suddenly work with a procedural language. At with interesting I mean dangerous. You can do stuff in such an straight-forward way you could have never imagined in Java. And I guess it is the best way for the majority of the plugins as their code base is rather small and object-oriented principles can add an unnecessary overhead. But the bigger your code base gets, the more you wish yourself back to the old ways with objects, methods and classes.

Unfortunately there are not that many resources out there that explain techniques and best-practices for object-oriented design principles in WordPress plugin development. There are many resources that get you started code-wise, but that is usually not the problem. The problem is not the code, but the underlying design.

Why should I want OOD?

You can find enough resources that outline the general advantages and features of object-oriented design and programming, ranging from encapsulation, polymorphism and inheritance. In the case of WordPress plugins, I mainly do it because of the following advantages:

  • Less worries about namespace collisions. I can name my methods they way I want without worrying whether this name could already have been used elsewhere or even in any other plugin, because they are all defined on classes. There of course I still have to worry about it.
  • Modularity and natural structure of code. This is certainly the biggest feature of this approach. Suddenly there is a natural way to structure and organize files, functions and properties.
  • Code reuse. Inheritance can be heavily used for reuse of existing code and thus reude code duplication. I provide some concrete examples on this. It could also be achieved in a procedural way, but it is certainly easier with classes and sub-classes.
  • Better use of Type hinting. With classes you can make sure that your methods get called in the correct way. This makes debugging easier and usually improved IDE support (e.g. in PhpStorm).

Language limitations

Object-oriented programming with PHP has its limitations. At least for me, probably because I am not yet that familiar with the language. So it is quite likely that experienced PHP programmers can sport things I did wrong in this post. If you do, please feel free to drop a comment.

If you are as new to PHP as I am, I recommend looking at the documentation of Callbacks on the official PHP documentation. Callbacks are the main way to hook into all WordPress actions and filters and thus are used quite heavily. As a brief summary, just be aware that there are basically three ways to pass a callback:

  • As a String. The string is the name of the callback function.
  • As an array with an object at first position and a string at second position. The the string names the callback method for this object. This method must be public.
  • As an array with a string at first and second position. The first string names a class while the second string names a public static method on this class.

What are my classes?

The first question you have to answer is, what classes you actually have. In the end any executed in your plugin should be part of a method and thus part of a class, but it is important to know which classes actually exist so that you can assign code to the correct class.

So assuming you have a plugin that has the following:

  • Its own table in the database.
  • One or multiple new admin screens.
  • One or multiple shortcodes to display the content on the frontend.

Obviously I am drawing inspiration from my own plugin. Feel free to download the source code and look into my approach of addressing these issues.

So coming from this list of things, we can see the following candidates for classes:

  • A class for the plugin itself.
  • A class for the thing you save on the database.
  • One or multiple classes for the admin screens.
  • One or multiple classes for the shortcodes.

Now I go through each one quickly.

Plugin Class

This one is rather obvious and this one is the one most existing resources on the internet also mention and elaborate on. But it is just the first step of many. In my previous post I also mention this by registering static methods in the register_activiation_hook and register_deactiviation_hook. Since then I went one step further and I now register non-static methods on the plugin object. This allows me to create the object beforehand and thus makes sure the constructor is called before activation.

So the main file for my plugin got very, very slim:

include_once dirname( __FILE__ ) . '/includes/class-network-summary.php';

if ( class_exists( 'Network_Summary' ) ) {
	$network_summary = new Network_Summary();

	register_activation_hook( __FILE__, array($network_summary, 'activate') );
	register_deactivation_hook( __FILE__, array($network_summary, 'deactivate') );

	function get_site_categories( $id = null ) {
		if ( ! isset($id) ) {
			$id = get_current_blog_id();
		}
		global $network_summary;
		return $network_summary->get_site_categories( $id );
	}

	function set_site_category( $id, $category ) {
		global $network_summary;
		$network_summary->set_site_category( $id, $category );
	}

	function remove_site_category( $id, $category ) {
		global $network_summary;
		$network_summary->remove_site_category( $id, $category );
	}
}

network-summary.php

Wait, this does not look object-oriented at all! What are these three functions at the bottom? Well, that is so-to-say the procedural legacy we have to deal with. We are adding Template Tags as new functions. Basically all functions that should be visible to other plugins or themes, can be defined as such. As you can see, all these functions just delegate the call to the appropriate function on my plugin object.

Domain classes

It sounds fairly straight-forward that as soon as you are saving stuff on the database in your own tables, this stuff should be objects as well. Actually I have mixed feelings here. From a conceptual perspective this certainly should be objects. And as soon as you have any from of logic on these objects, you should threat them as such, meaning: As soon as you would define more methods than pure getter and setters on these classes, there is little excuse not to make them proper objects. And I think this is the proper way. Move the logic to the domain and threat them as pure objects.

But quite often I encountered the problem, that these objects remained pure data-classes with only properties and getter and setters for these properties. In that case they seem unnecessary. As you should use the $wpdb object anyway for database access, you will get objects anyway by default (these objects are simple stdClass objects with all the fetched columns as properties).

Repository classes

Now this might be less obvious but working frequently with frameworks like Spring made this a obvious choice. These objects are known as Data access objects (DAO).

Here is how one of my repositories look like:

class Site_Category_Repository
{
	public function __construct() {
		global $wpdb;
		$table_name = $wpdb->base_prefix . 'site_categories';
		$wpdb->site_categories = $table_name;

		...
	}

	public function create_table() {
		global $wpdb;

		$sql = "CREATE TABLE $wpdb->site_categories (
			id BIGINT NOT NULL AUTO_INCREMENT,
			created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
			name VARCHAR(55) NOT NULL,
			description VARCHAR(255) DEFAULT NULL,
			PRIMARY KEY id (id)
		)
		DEFAULT COLLATE utf8_general_ci;";

		require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
		dbdelta( $sql );

		...
	}

	public function get_all() {
		global $wpdb;
		return $wpdb->get_results( "SELECT id, name, description FROM $wpdb->site_categories", OBJECT_K );
	}

	public function add( array $category ) {
		global $wpdb;
		return $wpdb->insert( $wpdb->site_categories, $category );
	}

	public function delete( $id ) {
		global $wpdb;
		return $wpdb->delete( $wpdb->site_categories, array('id' => $id) );
	}

	public function get_by_id( $id ) {
		global $wpdb;
		return $wpdb->get_row( "SELECT id, name, description FROM $wpdb->site_categories where id = $id", OBJECT );
	}

	public function update( $id, array $category ) {
		global $wpdb;
		return $wpdb->update( $wpdb->site_categories, $category, array('id' => $id) );
	}

	...
}

class-site-category-repository.php

So these are just pretty straightforward CRUD-operations but also the method to actually create the table. Everything neat in one file, class and place. As soon as you have multiple DAOs you can even start to create an abstract superclass Repository that reduces code duplication.

Admin Screens

For the admin screens you have multiple ways to wrap your information in a single class. One approach I used quite frequently and is shamelessly copied from the original WordPress Core code base, is the use of reusable data tables. Basically the idea is the following: Quite often we need tables to display many objects and allow editing of said objects. Examples are post categories or tags in the WordPress core.

Now I looked how WordPress has solved this and I saw the following pattern: The template for the admin screen creates a new Class called {somehting}_wp_list_table which holds all the items and knows how to display them. So I went on and created my own List_Tables. This started as quite some copy&pasting (because it is bad practice to extend core classes) but in the end I rewrote large parts anyway.

abstract class Network_Summary_List_Table
{
    protected $items;

    public function prepare_items() {
        $this->items = $this->get_items();
    }

    protected abstract function get_items();

    protected abstract function get_all_columns();

    ...

    private function single_row_column_value( $column_name, $item ) {
        if ( method_exists( $this, 'column_' . $column_name ) ) {
            return call_/user_func( array($this, 'column_' . $column_name), $item );
        } elseif ( method_exists( $item, 'get' . ucfirst( $column_name ) ) ) {
            return call_/user_func( array($item, 'get' . ucfirst( $column_name )), $item );
        } elseif ( is_object( $item ) && property_exists( $item, $column_name ) ) {
            return $item->$column_name;
        } elseif ( is_array( $item ) && isset($item[$column_name]) ) {
            return $item[$column_name];
        } else {
            return '';
        }
    }
}

class-network-summary-list-table.php

This is the abstract for all list tables. Each list table must implement the methods on how to get all items and can override methods on how to display the content.

The magic happens in the last method. This is called for each column to display, together with the item that contains the data. It first checks, whether the actual implementation has a method column_{column_name}. If so, we call this method. Then we check the standard getter for the item. Next we try whether it is a valid property and last but not least we check whether we are dealing with a associate array.

For example, I have a concrete implementation that provides us with a list of all sites. This looks as follows:

class Sites_List_Table extends Network_Summary_List_Table
{
    protected function get_items() {
        $items = array();
        foreach ($this->network_summary->get_sites() as $site_id) {
            switch_to_blog($site_id);
            array_push($items, array(
                'id' => $site_id,
                'name' => get_bloginfo('bloginfo'),
                'categories' => get_site_categories($site_id),
                'sharing' => $this->network_summary->get_option('share_site'),
                'description' => $this->network_summary-    >get_option('site_description'),
                'no_posts' => wp_count_posts()->publish,
                'no_pages' => wp_count_posts('page')->publish,
                'last_post' => wp_get_recent_posts(array(
                    'numberposts' => 1,
                    'post_status' => 'publish'
                    ), 'OBJECT')
            ));
            restore_current_blog();
        }
        return $items;
    }

    protected function get_all_columns() {
        return array(
            'id' => 'ID',
            'name' => __('Site Name', 'network-summary'),
            'categories' => __('Categories', 'network-summary'),
            'sharing' => __('Sharing', 'network-summary'),
            'description' => __('Site Description', 'network-summary'),
            'no_posts' => __('# Posts', 'network-summary'),
            'no_pages' => __('# Pages', 'network-summary'),
            'last_post' => __('Last Post', 'network-summary')
        );
    }

    protected function get_column_widths() {
        return array(
            'name' => '15%',
            'categories' => '15%',
            'description' => '25%',
            'last_post' => '25%'
        );
    }

    protected function column_name($item) {
        return sprintf('%s', get_admin_url($item['id'], 'options-reading.php'), $item['name']);
    }

    ...
}

class-sites-list-table.php

The first method get_items() constructs the array of items. As you can see it is a array of sites with many different data. The method get_all_columns() provides an array of all columns to display. For each of those column the method single_row_column_value on the parent class will be called with this name and the item. No for the name we have defined a custom method. So this method will define how the column should be displayed. In this case we simply wrap it in a link.

On the other hand the columns 'no_posts' and 'no_pages' do not need to have custom methods. So with the second to last if clause, we check whether it is associative array (true) and wether the key ‘no_posts’ exists (true) so we simply output it.

With the method get_column_widths() I can even define which width each column should have.

Once I have set this up it was super easy and fast to add new tables with different content to other admin screens. It is just a matter of minutes – no matter what data I want to display.

Shortcodes

The last thing I wanted to encapsulate was shortcodes. Here we have the same behavior for different shortcodes and thus they seem ideal for an abstract superclass and concrete subclasses for each shortcode.

The first thing I struggled a bit with is the registering of the hook. Here we cannot create one object and then call a method on this object, because we can have multiple instances of the same shortcode active on the same page. Thus I came up with the following solution:

add_shortcode( 'netview', array('Network_Overview_Shortcode', 'render') );
add_shortcode( 'netview-single', array('Network_Single_Shortcode', 'render') );
add_shortcode( 'netview-all', array('Network_All_Shortcode', 'render') );

class-network-summary.php

This is the registration of the shortcodes in my main plugin class. This is a static method on each of the concrete shortcode classes. They look as follows:

class Network_Overview_Shortcode extends Network_Summary_Shortcode
{
    private $sites;

    /**
    * Displays a overview of all sites in a two column display in alphabetic order.
    *
    * @param $atts array with a include list and a exclude list. If include is empty,     all sites are included,
    * except the ones in the exclude list. This is always overridden by the sharing setting of the individual blog.
    * Also accepts a numposts parameter for the number of recent posts displayed. Can be 0 and defaults to 2. The sort
    * parameter can be either 'abc' or 'posts'. 'abc' is the default value and sorts the posts alphabetically. 'posts'
    * sorts the sites so that the one with the most recent posts are listed first. Finally it takes a layout parameter
    * which takes either 'grid' (default) or 'table' and displays the sites accordingly.
    * @return string the html content for the overview view.
    */
    public static function render( $atts ) {
        global $network_summary;
        $code = new Network_Overview_Shortcode($network_summary, $atts, false);
        return $code->output();
    }

    public function __construct( Network_Summary $network_summary, $atts, $cache = true     ) {
        parent::__construct( $network_summary, $atts, $cache );
        ...
    }

    protected function parse_args( $atts ) {
        ...
    }

    protected function validate_args() {
        ...
    }
    ...
}

class-network-overview-shortcode.php

The static method creates the object and calls the appropriate method on it to display the content of the shortcode. Parsing and validation of the arguments is called by the parent constructor.

With these code examples I showcases some ways to make WordPress plugins more object-oriented. For people coming from object-oriented languages like me, I probably do not need much convincing, that this approach has merits over a procedural approach. But it might be a bit tricky at first to apply those principles in a world which is not as strictly object-oriented as others.

If you have any comments or feedback on the approach, the code or any other related thing, let me know.