This website uses cookies to ensure you get the best possible experience. See our Cookies Policy.
WordPress Plugin Design Patterns
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
Subscribe to our newsletter
});
Posted by: Christopher Davis