PMG Digital Made for Humans

WordPress Plugin Design Patterns

5 MINUTE READ | June 25, 2012

WordPress Plugin Design Patterns

Author's headshot

Christopher Davis

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

With several publically released plugins under my belt and just as many custom themes and plugins for clients, I’ve found several distinct patterns emerge in my Plugins and themes. This post is so I can share them with you.

your-plugin/

your-plugin.php

readme.txt

inc/

another-file.php

js/

a-script.js

css/

admin-style.css

images/

button.jpg

lang/

your-textdomain.pot

es_ES.po

es_ES.mo

Every plugin should be in its own folder. The only two files that should be in this root directory are your main plugin file and a readme. The inc folder should contain all your other PHP files. The js, css and images folders are for JavaScript, CSS and image files respectively. Finally the lang directory should contain your pot file and any po/mo files for internationalization.

The biggest issue I see with many plugins is the use of magic values – eg. strings or other data types that have special meaning. A great example: a settings name. Using a base class, provides a convenient way around this. You can define a base class that has several class constants with those magic strings and use that as a parent for other classes in your plugin.

<?php

class Your_Plugin_Base

{

const SETTING = ‘yourplugin_settings’;

// more stuff here

}

// elsewhere you in your plugin

class Your_Plugin_Front extends Your_Plugin_Base

{

public static function do_stuff()

{

$opts = get_option(self::SETTING);

// do stuff with $opts

}

}

// you can access the constants directly

$opts = get_option(Your_Plugin_Base::SETTING);

Alternatively, you can use this class as a sort-of namespace and fill it with functionality you’ll use throughout your plugin.

A final note on using Classes/OOP in your plugin: use static functions for hooks. This makes it easier for other developers to unhook your functions if necessary. I tend to use the get_class function to add hooks:

<?php

// example from SEO Auto Linker

class SEO_Auto_Linker_Post_Type extends SEO_Auto_Linker_Base

{

/*

* Nonce action

*/

const NONCE = ‘seoal_post_nonce’;

/*

* Sets up all the actions and filters

*

* @uses add_action

* @uses add_filter

* @since 0.7

*/

public static function init()

{

// register post type

add_action(

‘init’,

array(get_class(), ‘register’)

);

}

// snip

public static function register()

{

$labels = array(

‘name’              => __(‘Automatic Links’, ‘seoal’),

‘singular_name’     => __(‘Automatic Link’, ‘seoal’),

‘add_new’           => __(‘Add New Link’, ‘seoal’),

‘all_items’         => __(‘All Links’, ‘seoal’),

‘add_new_item’      => __(‘Add New Link’, ‘seoal’),

‘edit_item’         => __(‘Edit Link’,’seoal’),

‘new_item’          => __(‘New Link’, ‘seoal’),

‘search_items’      => __(‘Search Links’, ‘seoal’),

‘not_found’         => __(‘No Links Found’, ‘seoal’),

‘not_found_in_trash’ => __(‘No Links in the Trash’, ‘seoal’),

‘menu_name’         => __(‘SEO Auto Linker’, ‘seoal’)

);

$args = array(

‘label’         => __(‘Automatic Links’, ‘seoal’),

‘labels’        => $labels,

‘description’   => __(‘A container for SEO Auto Linker’, ‘seoal’),

‘public’        => false,

‘show_ui’       => true,

‘show_in_menu’  => true,

‘menu_position’ => 110,

‘supports’      => array(‘title’)

);

register_post_type(

self::POST_TYPE,

$args

);

}

// snip

} // end class

SEO_Auto_Linker_Post_Type::init();

Got a string that’s going to be exposed the user in some way?  Make sure it gets run through one of WordPress‘ numerous internationalization functions. Do this even if you don’t plan on releasing the plugin/theme. Many times you code can be repurposed for other clients/gigs/whatever – making sure its internationalized can potentially save you a lot of time down the road.

Also don’t forget to load your plugin’s text domain:

<?php // example from SEO Auto Linker

add_action(‘init’, ‘seoal_load_textdomain’);

function seoal_load_textdomain()

{

load_plugin_textdomain(

‘seoal’,

false,

dirname(plugin_basename(__FILE__)) . ‘/lang/’

);

}

Chances are you’re going to need a few things throughout your plugin:

  • The URI of your plugin’s directory – very useful for enqueueing scripts and styles

  • The full path of your plugin’s directory – useful for includes

  • Your plugins basename (optional) – used in a few filters, such as the “plugin action links”

I usually define these constants outside of my base class. WordPress has a few functions you should use to get these values as well.

<?php

// plugin example

define(‘YOURPLUGIN_PATH’, plugin_dir_path(__FILE__));

define(‘YOURPLUGIN_URL’, plugin_dir_url(__FILE__));

define(‘YOURPLUGIN_NAME’, plugin_basename(__FILE__));

// theme example

define(‘YOURTHEME_PATH’, trailingslashit(get_template_directory()));

define(‘YOURTHEME_URL’, trailingslashit(get_template_directory_uri()));

// use these constants to enqueue scripts/styles and such

add_action(‘wp_enqueue_scripts’, ‘yourplugin_enqueue’);

function yourplugin_enqueue() {

wp_enqueue_script(

‘myscript’,

YOURPLUGIN_URL . ‘js/myscript.js’,

array(),

);

});

// or to include files

require_once(YOURPLUGIN_PATH . ‘inc/some-file.php’);

At the very least, separate admin and front end components where it make sense to do so. Then in your main plugin file, you can conditionally include the file.

if(is_admin())

{

require_once(YOURPLUGIN_PATH . ‘inc/admin.php’);

}

else

{

require_once(YOURPLUGIN_PATH . ‘inc/front.php’);

}

You could also split up your plugins functionality across multiple files and only include files when a feature is enabled. That senario isn’t feasible for single serving plugins.

Include inline documentation in your code; use whatever format you see fit. If the function is hooked is hooked into something, mention what hooks is used. This is for you as the developer – it helps a lot when you have to go back and read your own code later. It also makes it easier for people to jump in and contribute to your plugin if it happens to be publicly released.

<?php

/**

* Hooked into ‘wp_enqueue_scripts’, adds the plugins scripts and styles for

* the front end

*

* @since 1.0

* @uses wp_enqueue_script

* @return null

*/

function yourplugin_enqueue() {

wp_enqueue_script(

‘myscript’,

YOURPLUGIN_URL . ‘js/myscript.js’,

array(),

);

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

});


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