Skip to content

Instantly share code, notes, and snippets.

@rheinardkorf
Last active August 29, 2015 14:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rheinardkorf/a43389b02a0586632522 to your computer and use it in GitHub Desktop.
Save rheinardkorf/a43389b02a0586632522 to your computer and use it in GitHub Desktop.
One approach to implement a class autoloader for WordPress plugin development. This file will for the most part be untouched once the structure is in place. The real development starts in the library folder.
<?php
/**
* @package MyPlugin
*/
//Before playing with this file, see the other file in the gist. Read the comments.
if( ! class_exists( 'MyPlugin' ) ) {
class MyPlugin {
public static function setup_plugin( $file, $header ) {
error_log( print_r( $header, true ) );
}
}
}
<?php
/**
* @package MyPlugin
*/
/*
Plugin Name: MyPlugin
Plugin URI: http://rheinardkorf.com
Description: MyPlugin Description
Version: 1.0.0
Author: Rheinard Korf
Author URI: http://rheinardkorf.com
License: GPLv2 or later
Text Domain: myplugin
*/
/*
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/**
* Class MyPluginLauncher
*
* This class is responsible for setting up the autoloader of the plugin.
* Once the autoloader is in place it will call $plugin_prefix::setup_plugin() that is found in the $lib_path directory
* within the $plugin_prefix directory.
*
* None of the functionality of this plugin should be added to THIS file. $plugin_prefix::setup_plugin() becomes the
* primary entry point of the plugin.
*/
class MyPluginLauncher {
/**
* @var string The path of containing the plugin. AKA, the library.
*/
private static $lib_path = 'lib';
/**
* @var string This prefix will be used in THIS file to add filters.
*/
private static $filter_prefix = 'myplugin';
/**
* @var string The prefix denotes the folder in the library for this plugin. As well as the $plugin_prefix.php file.
*/
private static $plugin_prefix = 'MyPlugin'; // Also the class prefix
/**
* @var array Contains the plugin header information at the top of THIS file as well as other data.
*/
private static $plugin_data;
/**
* Construct the launcher and launch the $pluging_prefix.php file.
*/
public function __construct() {
/**
* Work out the path to use for the plugin within the $lib_path directory. Becomes the primary path for the
* class autoloader.
*/
$library_path = dirname( __FILE__ ) . DIRECTORY_SEPARATOR . self::$lib_path . DIRECTORY_SEPARATOR;
/**
* Get data from the plugin header. Its unlikely that the required core file is loaded, so we'll load it now.
*/
if ( ! function_exists( 'get_plugin_data' ) ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
self::$plugin_data = get_plugin_data( __FILE__ );
self::$plugin_data['LibraryPath'] = $library_path;
/**
* Determine plugin paths. Handy if custom paths have been identified or the plugin is installed in mu-plugins.
*/
if ( defined( 'WP_PLUGIN_URL' ) && defined( 'WP_PLUGIN_DIR' ) && file_exists( plugin_dir_path( __FILE__ ) . basename( __FILE__ ) ) ) {
self::$plugin_data['Location'] = 'plugins';
self::$plugin_data['PluginDirectory'] = plugin_dir_path( __FILE__ ) . self::$lib_path . DIRECTORY_SEPARATOR;
self::$plugin_data['PluginURL'] = plugins_url( '/' . self::$lib_path . '/', __FILE__ );
self::$plugin_data['BaseDirectory'] = plugin_dir_path( __FILE__ );
self::$plugin_data['BaseURL'] = plugins_url( '/', __FILE__ );
} else if ( defined( 'WPMU_PLUGIN_URL' ) && defined( 'WPMU_PLUGIN_DIR' ) && file_exists( WPMU_PLUGIN_DIR . DIRECTORY_SEPARATOR . basename( __FILE__ ) ) ) {
self::$plugin_data['Location'] = 'mu-plugins';
self::$plugin_data['PluginDirectory'] = WPMU_PLUGIN_DIR . DIRECTORY_SEPARATOR . self::$lib_path . DIRECTORY_SEPARATOR;
self::$plugin_data['PluginURL'] = WPMU_PLUGIN_URL . '/' . self::$lib_path . '/';
self::$plugin_data['BaseDirectory'] = WPMU_PLUGIN_DIR . DIRECTORY_SEPARATOR;
self::$plugin_data['BaseURL'] = WPMU_PLUGIN_URL . '/';
} else {
wp_die( sprintf( __( 'An issue occurred when determining where %s was installed. Please install again.', self::$plugin_data['TextDomain'] ), self::$plugin_data['Name'] ) );
}
/**
* Add a filter to specify which classes to autoload.
*/
add_filter( self::$filter_prefix . '_class_loader_classes', array( get_class(), 'class_loader_classes' ) );
/**
* Some classes might need to be overridden. So add a filter to specify which classes to map to what paths.
*/
add_filter( self::$filter_prefix . '_class_loader_overrides', array( get_class(), 'class_loader_overrides' ) );
/**
* The heart of the plugin really starts here. No more require or require_once calls. Huzzah!
* But it does follow a strict structure in the file naming -- based on PSR0.
*/
spl_autoload_register( array( get_class(), 'class_loader' ) );
/**
* Setup the plugin (using call_user_func for PHP 5.2 compatibility)
* This is calls the entry point of the plugin. This called function will be where we really hook into WordPress.
*/
call_user_func( self::$plugin_prefix . '::setup_plugin', __FILE__, self::$plugin_data );
}
/**
* Add the classes that the autoloader need to recognize.
*
* We do this so that there are no conflicts with other autoloaders. BUT, more importantly, we only want to handle
* classes that belong to this plugin as the structure is specific to this library path and class structure.
*
* NOTE: Each entry in the array is a simple regular expression or very specific class.
*
* @param $classes Array passed in by the filter.
*
* @return array
*/
public static function class_loader_classes( $classes ) {
$added_classes = array(
// The primary plugin class
self::$plugin_prefix,
// Setup MVC structure
// All classes beginning with $plugin_prefix_Model. E.g. MyPlugin_Model_Example
'^' . self::$plugin_prefix . '_Model',
// All classes beginning with $plugin_prefix_View.
'^' . self::$plugin_prefix . '_View',
// All classes beginning with $plugin_prefix_Controller.
'^' . self::$plugin_prefix . '_Controller',
// All classes beginning with $plugin_prefix_Helper.
'^' . self::$plugin_prefix . '_Helper',
// Other patterns below
);
return array_unique( array_merge( $classes, $added_classes ) );
}
/**
* Add the classes that the autoloader need to override.
*
* Sometimes we might want to add a specific file path for a given glass. Add those entries in here.
*
* Note: These classes will also need to fit the pattern in class_loader_classes or it will be ignored.
*
* @param $overrides Array that contains the pattern 'class_name' => 'class_file_path'
*
* @return array
*/
public static function class_loader_overrides( $overrides ) {
$added_overrides = array(
// Example
//'My_Class' => str_replace( '/', DIRECTORY_SEPARATOR, '/custom/path/file.php'),
);
return array_unique( array_merge( $overrides, $added_overrides ) );
}
/**
* This is where the magic happens to avoid seas of require and require_once statements.
*
* How it works: PHP is pretty clever with determining which classes to load. If, however, a class does not
* exist we will get a fatal error. We want to jump in before that happens and give PHP a lifeline and say
* "If you cannot find the class by your normal means, you might be able to find it here."
*
* Now we will look at the called class and if it fits any of the patterns in our $includes_classes array, then
* we will attempt to include the file if it exists. If it is not one of our classes, we hope that somewhere else
* responsibility will be taken to prevent the fatal error, but we've done our job.
*
* @param $class The class called by PHP.
*
* @return bool
*/
public static function class_loader( $class ) {
$basedir = self::$plugin_data['LibraryPath'];
$class = trim( $class );
// Add included classes using a filter.
$included_classes = apply_filters( self::$filter_prefix . '_class_loader_classes', array() );
// Add class overrides using a filter.
$class_overrides = apply_filters( self::$filter_prefix . '_class_loader_overrides', array() );
$override_keys = array_keys( $class_overrides );
// Get the regular expression to determine if we need to handle the called class.
$pattern = '/' . implode( '|', $included_classes ) . '/';
// Yes we do...
if ( preg_match( $pattern, $class ) ) {
// If its not overridden, try this first...
if ( ! in_array( $class, $override_keys ) ) {
if ( self::$plugin_prefix == $class ) {
// Is it our main class?
$filename = $basedir . $class . DIRECTORY_SEPARATOR . $class . '.php';
} else {
$filename = $basedir . str_replace( '_', DIRECTORY_SEPARATOR, $class ) . '.php';
}
} else {
// This class has a different filename, so try this one.
$filename = $basedir . $class_overrides[ $class ];
}
// One last chance to override the filename.
$filename = apply_filters( self::$filter_prefix . '_class_file_override', $filename, $class );
// Include it if it exists and we have access.
if ( is_readable( $filename ) ) {
include_once $filename;
return true;
}
}
return false;
}
}
/**
* LAUNCH!
*/
new MyPluginLauncher();
/**
* For this example to work, create the following structure within this plugin's root folder.
* /myplugin.php
* /lib/MyPlugin
* /lib/MyPlugin/MyPlugin.php
* /lib/MyPlugin/Controller/
* /lib/MyPlugin/Helper/
* /lib/MyPlugin/Model/
* /lib/MyPlugin/View/
*
* See next file in the gist for contents of /lib/MyPlugin/MyPlugin.php
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment