Building Your First WordPress Plugin Foundation

Posted by Ronald Huereca / November 6, 2019 /

The following is an excerpt from the upcoming WordPress and Ajax 3rd Edition book. Please visit wpandajax.com to subscribe to updates for the new book.
Ronald Huereca
Ronald Huereca

Building a simple WordPress plugin is just as simple as adding a file or folder to the to your plugins directory, which is found under wp-content/plugins. It’s a good practice to have the plugin file match the folder you put it in.

For example, if you have a folder called sample-wpajax-plugin, the file inside it should be sample-wpajax-plugin.php. Let’s go ahead and create the folder and plugin file inside the wp-content/plugins folder.

The most important starter advice is getting your plugin header set the way you like it, so let’s start with that first.

Plugin Header

Here’s an example plugin header that we’ll be using:

<?php
/**
 * Sample WPAJAX Plugin
 *
 * @package   Sample_WPAjax_Plugin
 * @copyright Copyright(c) 2019, MediaRon LLC
 * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
 *
 * Plugin Name: Sample WPAjax Plugin
 * Plugin URI: https://wpandajax.com
 * Description: Sample Plugin.
 * Version: 1.0.0
 * Author: MediaRon LLC
 * Author URI: https://wpandajax.com
 * License: GPL2
 * License URI: http://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain: sample-wpajax-plugin
 * Domain Path: languages
 */

You’ll see several header options, which I’ll go over below.

  • Plugin Name: The name of your plugin.
  • Plugin URI: The URL to your plugin’s homepage and/or documentation.
  • Description: The description your users will see when they upload and install your plugin.
  • Version: The current version of the plugin.
  • Author: The author of the plugin, which can be your name or organization.
  • Author URI: The URL to your domain where people can contact you. This will also show up in the plugins screen in the admin area of WordPress.
  • License: The license the plugin is under.
  • License URI: The URL to the license referenced.
  • Text Domain: The text domain for the plugin for internationalization purposes. This usually matches your slug (use your folder name). In the above example, I have a folder called sample-wpajax-plugin and a file inside it called sample-wpajax-plugin.php.
  • Domain Path: The directory where your language files are located.

Next up, let’s define some constants that we’ll use throughout the plugin.

Sample Plugin Constants

define( 'SAMPLE_WPAJAX_VERSION', '1.0.0' );
define( 'SAMPLE_WPAJAX_PLUGIN_NAME', 'Sample WPAjax Plugin' );
define( 'SAMPLE_WPAJAX_DIR', plugin_dir_path( __FILE__ ) );
define( 'SAMPLE_WPAJAX_URL', plugins_url( '/', __FILE__ ) );
define( 'SAMPLE_WPAJAX_SLUG', plugin_basename( __FILE__ ) );
define( 'SAMPLE_WPAJAX_FILE', __FILE__ );

We define several constants above that can be used throughout the plugin to get version information, the plugin’s slug, the absolute path to the plugin, the URL for the plugin, and where to find the plugin’s file location.

Setting Up an Autoloader

Autoloaders are necessary in order to programmatically tell which file should be included and at just the right time and place. Without an autoloader, we’d have to manually include each PHP file you need and that’s no fun! It should be done for us. We’ll be using PHP namespaces, because honestly it makes the autoloading process cleaner and is a more modern PHP development practice.

For this, let’s introduce our plugin’s structure. There’s a structure I’ve been fond of which allows for PHP namespaces and can scale as your plugin grows larger in scope. For now, we’ll just be instantiating an asset loader which we’ll build onto as you progress through this book. You can find the sample code at https://github.com/wpajax/chapter-3 or simply go to https://github.com/wpajax to see all of the available code samples.

First, let’s go over folder structure. I have an empty CSS and JS folder, which we’ll use later to load any JavaScript or CSS that your plugin needs. I then have a PHP folder which houses the autoloader, and a plugin launch-point which we’ll use to extend the plugin.

As a convenience, here’s a graphic of the folder structure.

Plugin Folder Structure
Plugin Folder Structure

This plugin is housed in the wp-content/plugins folder and can simply be activated if you download it from the GitHub repository.

Finally, here’s the autoloader code:

<?php
/**
 * Plugin Autoloader
 *
 * @package   Sample_WPAjax_Plugin
 */

/**
 * Register Autoloader
 */
spl_autoload_register(
	function ( $class ) {
		// Assume we're using namespaces (because that's how the plugin is structured).
		$namespace = explode( '\\', $class );
		$root      = array_shift( $namespace );

		// If a class ends with "Trait" then prefix the filename with 'trait-', else use 'class-'.
		$class_trait = preg_match( '/Trait$/', $class ) ? 'trait-' : 'class-';

		// If we're not in the plugin's namespace then just return.
		if ( 'Sample_WPAjax_Plugin' !== $root ) {
			return;
		}

		// Class name is the last part of the FQN.
		$class_name = array_pop( $namespace );

		// Remove "Trait" from the class name.
		if ( 'trait-' === $class_trait ) {
			$class_name = str_replace( 'Trait', '', $class_name );
		}

		$filename = $class_trait . $class_name . '.php';

		// For file naming, the namespace is everything but the class name and the root namespace.
		$namespace = trim( implode( DIRECTORY_SEPARATOR, $namespace ) );

		// Because WordPress file naming conventions are odd.
		$filename  = strtolower( str_replace( '_', '-', $filename ) );
		$namespace = strtolower( str_replace( '_', '-', $namespace ) );

		// Get the path to our files.
		$directory = dirname( __FILE__ );
		if ( ! empty( $namespace ) ) {
			$directory .= DIRECTORY_SEPARATOR . $namespace;
		}

		$file = $directory . DIRECTORY_SEPARATOR . $filename;

		if ( file_exists( $file ) ) {
			require_once $file;
		}
	}
);

You’ll want to take note of the Sample_WPAjax_Plugin portion, which we use as the plugin’s default package and PHP namespace. You’re welcome to change this to something more generic, but you’ll need to update the plugin’s namespaces. I’ll leave that as an exercise for the reader.

Finalizing the Plugin Initialization

Assuming you have copied the class-plugin-abstract.php, class-plugin-interface.php, and class-plugin.php over from the GitHub repo, we’re ready to initialize the autoloader and main plugin file, which will be class-plugin.php.

Here’s what we have in class-plugin.php, which is located in the PHP folder.

<?php
/**
 * Primary plugin file.
 *
 * @package   Sample_WPAjax_Plugin
 */

namespace Sample_WPAjax_Plugin;

/**
 * Class Plugin
 */
class Plugin extends Plugin_Abstract {
	/**
	 * Execute this once plugins are loaded.
	 */
	public function plugin_loaded() {
		// Custom code here.
	}
}

Let’s go back to the main plugin file, sample-wpajax-plugin.php, and ensure the plugin_loaded method is executed via our autoloader.

define( 'SAMPLE_WPAJAX_VERSION', '1.0.0' );
define( 'SAMPLE_WPAJAX_PLUGIN_NAME', 'Sample WPAjax Plugin' );
define( 'SAMPLE_WPAJAX_DIR', plugin_dir_path( __FILE__ ) );
define( 'SAMPLE_WPAJAX_URL', plugins_url( '/', __FILE__ ) );
define( 'SAMPLE_WPAJAX_SLUG', plugin_basename( __FILE__ ) );
define( 'SAMPLE_WPAJAX_FILE', __FILE__ );

// Setup the plugin auto loader.
require_once 'php/autoloader.php';

Now let’s do some checks to make sure the PHP version is high enough. What we’re going to do is an admin notice WordPress action and then output a warning if a minimum PHP version isn’t recognized.

/**
 * Admin notice if Sample Plugin isn't an adequate version.
 */
function sample_wpajax_version_error() {
	printf(
		'<div class="error"><p>%s</p></div>',
		esc_html__( 'Sample WPAjax requires a PHP version of 5.6 or above.', 'sample-wpajax-plugin' )
	);
}

// If the PHP version is too low, show warning and return.
if ( version_compare( phpversion(), '5.6', '<' ) ) {
	add_action( 'admin_notices', 'sample_wpajax_version_error' );
	return;
}

From there, we create a function which will hold the plugin’s class instance. We’ll also set up internationalization and make sure the main plugin file is running.

/**
 * Get the plugin object.
 *
 * @return \Sample_WPAjax\Plugin
 */
function sample_wpajax() {
	static $instance;

	if ( null === $instance ) {
		$instance = new \Sample_WPAjax_Plugin\Plugin();
	}

	return $instance;
}

/**
 * Setup the plugin instance.
 */
sample_wpajax()
	->set_basename( plugin_basename( __FILE__ ) )
	->set_directory( plugin_dir_path( __FILE__ ) )
	->set_file( __FILE__ )
	->set_slug( 'sample-wpajax-plugin' )
	->set_url( plugin_dir_url( __FILE__ ) )
	->set_version( __FILE__ );

/**
 * Sometimes we need to do some things after the plugin is loaded, so call the Plugin_Interface::plugin_loaded().
 */
add_action( 'plugins_loaded', array( sample_wpajax(), 'plugin_loaded' ), 20 );
add_action( 'init', 'sample_wpajax_add_i18n' );

/**
 * Add i18n to Sample Plugin.
 */
function sample_wpajax_add_i18n() {
	load_plugin_textdomain( 'sample-wpajax-plugin', false, dirname( plugin_basename( __FILE__ ) ) . '/languages/' );
}

And, We’re Done!

You should now have a working and installable plugin, which we’ll build upon in later chapters. For reference, the code for this chapter can be found at https://github.com/wpajax/chapter-3. Do I expect you to understand it all? Not really. But from a plugin architecture point, it’s a decent way to get started on your custom code.

Ronald Huereca
Developer at MediaRon
Ronald Huereca is the CEO of MediaRon LLC and enjoys WordPress plugin and theme development.

Connect with the Author

Posted in

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top