John Russell Blog

Using a PHP Framework or Library in WordPress Plugins and Themes

Anyone who has spent a fair amount of time creating WordPress Plugins or Themes has likely also come across the need to package a PHP framework or library inside a plugin or theme. On the surface this is a fairly routine thing to do – simply copy the file into your plugin or theme, or use composer. There are lots of plugins out there using various PHP libraries and frameworks.

The Problems Begin

I’ve personally started including a PHP framework that I’ve created to help make development easier and add some features that I repeatedly use from project to project. At first this is great, and development is humming along, but eventually you’ll likely run into some problems, like I did. When WordPress loads it loads each activated plugin alphabetically (based on the plugin directory), so let’s say we have two plugins – one called “Analytics” and one called “Cache” and both include the same “Utilities” framework. When Analytics is loaded it will load the Utilities framework, and when Cache is loaded (depending on how the Utilities framework is built) it will either skip loading Utilities because it’s already been loaded or everything will come crashing down due to “Fatal error: Cannot redeclare class/function”. Assuming everything loaded properly, without any fatal errors, the Utilities framework is now loaded and available for use by Analytics and Cache, and you move on with development.

In a closed system where you control the entire stack, all dependencies, plugins, and themes this isn’t a problem. However, with WordPress the idea is that any number of plugins or themes may be installed by the end user, and in this case the developer of a plugin or theme doesn’t have control over what other dependencies, plugins, or themes get installed. So, back to the example from above, what if Analytics needs version 1.0.0 of the Utilities framework and Cache needs version 2.1.0 of the Utilities framework? Knowing the order in which plugins are loaded we know that the Analytics plugin will successfully load the 1.0.0 version of Utilities that is packaged with it, but Cache will be stuck with version 1.0.0 as well.

Assuming that the framework is just a simple class, one way around this problem is to rename the class based on it’s version, or namespace the framework based on it’s version. The problem with this solution is the implementation within your plugin or theme – each time the framework updates you have to update the calls to the framework, which makes maintenance a complete mess (not only for your plugin or theme but also for the developer of the framework). Making things worse, what if the framework is a package in itself, containing dozens of interface and class files?

As a quick side note, one could argue that the chances of this exact situation happening is quite unlikely. However, I think there’s a strong case to be made for plugin developers who make multiple plugins that are relevant to the same audience actually running into this problem. Especially if the plugin end-users happen to update one plugin (that includes a framework) but not another plugin (thus leaving one of the plugins with an outdated framework).

WordPress Hooks Are Our Friend

The solution that I came up with is similar to what the CMB2 toolkit uses, but is instantiated in a different way and uses namespacing as part of the solution. In my specific situation (using my own framework) I had the following requirements:

  • Ability load two separate version of the framework simultaneously.
  • Allow for the newest version to load in a way that guaranteed backwards compatibility.
  • No need for changing the framework namespace or class names with each version.

What I ended up with is a framework that adheres to the following versioning scheme:

  • Major version (1.0.0 to 2.0.0) releases are namespaced by including /v1/ or /v2/ in the namespace to allow for major version releases to run side-by-side with no conflicts. This allows one plugin to use version 1 while another uses version 2.
  • Minor version (1.0.0 to 1.1.0) releases only add new features (never remove) and do not change the interface of existing classes and methods to guarantee backwards compatibility. This allows plugins that expect an old version of the framework to still run properly with a newer version of the framework.
  • Dot version (1.0.0. to 1.0.1) releases only fix bugs and security issues and never change functionality.

The Code

This addresses version dependency issues, but in order to force the highest version of the framework to load I needed to add an init file. So, in the section of my plugin where all other dependencies are loaded I simply add a line such as this:

require_once 'includes/laubsterboy-sdk/v1/init.php';

Which loads the init.php:

<?php

namespace laubsterboy_sdk\v1;

if (!class_exists('\laubsterboy_sdk\v1\Laubsterboy_SDK_Init_1_0_0')) {
	/**
	 * Laubsterboy SDK Init class
	 *
	 * This class is responsible for loading the latest minor version of the SDK.
	 * This is needed since there is a possibility for multiple plugins including
	 * the SDK and the first plugin activated may not include the newest version.
	 * Anything that breaks backwards compatibility must be placed into a new
	 * major version, and namespaced as such, to allow for multiple versions to
	 * run side by side.
	 */
	class Laubsterboy_SDK_Init_1_0_0 {
		protected static $instance = null;
		protected $version = '1.0.0';

		/**
		 * Laubsterboy SDK Init Constructor
		 *
		 * Is responsible for comparing its version versus the global version and modifying as necessary
		 *
		 * @since 1.0.0
		 */
		protected function __construct() {
			global $laubsterboy_sdk_v1_version;

			if (empty($laubsterboy_sdk_v1_version) || version_compare($laubsterboy_sdk_v1_version, $this->version, '<')) {
				$laubsterboy_sdk_v1_version = $this->version;
			}

			add_action('after_setup_theme', array($this, 'load'), 1);
		}



		/**
		 * Laubsterboy SDK Init Singleton
		 *
		 * Retrieves an instance of this Laubsterboy SDK Init class
		 *
		 * @since 1.0.0
		 *
		 * @return Laubsterboy_SDK_Init_1_0_0
		 */
		public static function get_instance() {
			if (is_null(static::$instance)) {
				static::$instance = new static();
			}

			return static::$instance;
		}



		/**
		 * load
		 *
		 * Performs various checks to see if this version of the SDK should be loaded or skipped
		 *
		 * @since 1.0.0
		 *
		 * @return void
		 */
		public function load() {
			global $laubsterboy_sdk_v1_version;

			// Check to make sure this load method is being called via the after_setup_theme action
			if (!doing_action('after_setup_theme')) return;

			// Check if the global version is the same as this version
			if ($laubsterboy_sdk_v1_version !== $this->version) return;

			// Check if this version has already been loaded
			if (defined(__NAMESPACE__ . '\LAUBSTERBOY_SDK_LOADED')) return;

			// Define the version
			define(__NAMESPACE__ . '\LAUBSTERBOY_SDK_LOADED', $this->version);

			// Load the Laubsterboy_SDK class
			require_once 'laubsterboy-sdk.php';
		}
	}

	Laubsterboy_SDK_Init_1_0_0::get_instance();
}

Lastly, in order to actually use the framework all I need to do is hook into the ‘after_setup_theme’ action, such as this:

function my_plugin_after_setup_theme() {
	// Create SDK instance
	$sdk = new \laubsterboy_sdk\v1\Laubsterboy_SDK();
}
add_action('after_setup_theme', 'my_plugin_after_setup_theme');

The way all of this works is by each plugin or theme loading the framework init.php file which creates an instance of itself, using the singleton pattern. In the constructor it checks for the existence of a full version global number to compare against. If the current version of the framework is greater than the global version then it hooks into the ‘after_setup_theme’ action and sets the global version to be the same as its version. Lastly the callback for the ‘after_setup_theme’ performs a few checks to make sure that it’s attempting to load the highest version number and that the framework hasn’t been loaded yet, and then finally loads the main framework class.

Ultimately, it’s a fairly simple setup for using a PHP framework or library in a WordPress plugin that’s easy to implement and maintain. This is the solution that worked for me, but I’m sure there are other solutions out there so please share your thoughts, and what you’ve done, in the comments.


Posted

in

by

Comments

Leave a Reply

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