Created
December 18, 2018 21:02
-
-
Save johnalarcon/0f2081333657fe56c4d2e2c92b597b9b to your computer and use it in GitHub Desktop.
Autoloader for ClassicPress/WordPress Plugin Classes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Autoload classes | |
* | |
* Autoloaded classes? Yes. Support for namespaces, too? Of course! It helps | |
* to have a good understanding of how this autoloader works, so you can use | |
* it to your benefit, and not as a reason to pull out your hair. ;) Classes | |
* do not have to be namespaced, but it is recommended they are, for maximum | |
* plays-well-with-others...ness. Let's start with the super-basics... | |
* | |
* Say you had a class named ImageProcessor, under the same namespace as the | |
* plugin, the filename would be: | |
* | |
* ImageProcessor.class.php | |
* | |
* ...and, at the very least, it would need to contain: | |
* | |
* namespace ThePluginsNamespace; // same namespace as your plugin | |
* | |
* class ImageProcessor{} | |
* | |
* ...and it would go into: | |
* | |
* ../your-plugin-name/classes/ | |
* | |
* The above example handles autoloading classes under the same namespace as | |
* the plugin and they are stored in a ../classes/ directory in your plugin. | |
* Note that, even if your class isn't namespaced, the above still works. | |
* | |
* To use deeper namespacing, just let your class names reflect their server | |
* path, relative to the ../classes/ directory. For example, if the variable | |
* $class contains underscores (or backslashes) those are first converted to | |
* forward slashes (/) and then a path is built. In other words, this object | |
* instantiation: | |
* | |
* new Snazzy_Thing; | |
* | |
* ...would make the autoloader search for a class called "Thing", which was | |
* namespaced under "Snazzy", and located in the directory: | |
* | |
* ../classes/Snazzy/Thing.class.php | |
* | |
* The contents of Thing.class.php would be something like: | |
* | |
* namespace Snazzy; | |
* | |
* class Thing{} | |
* | |
* As another example, just to help this stick, let's say you wanted to have | |
* a class of "Test" and you wanted to place it under the "Admin" namespace, | |
* which would be a sub-namespace of the plugin's primary namespace. Phew! | |
* | |
* 1. Create a directory called "Admin" inside the ../classes/ directory. | |
* 2. In the new directory, create a new file called "Test.class.php" | |
* 3. In the new file, add the namespace at the top, like this: | |
* namespace = Admin; | |
* 4. In the new file, add a class definition for the Test class, like this: | |
* class Test{} | |
* 5. Create a new Test object elsewhere in the plugin, with, say: | |
* $myclass = new Admin\Test; | |
* 6. Marvel in your awesomeness. | |
* | |
* Ok, great. But, what if you wanted to house your new namespace within the | |
* plugin's primary namespace, but you also wanted your own classes to be in | |
* directories that make sense to your project? Easy. Just change your code, | |
* in steps 3 and 5 (above) to: | |
* | |
* 3. note: primary namespace IS NOT preceded by backslash | |
* namespace = ThePluginsNamespace\Admin; | |
* | |
* 5. note: primary namespace IS preceded by backslash | |
* $myclass = new \ThePluginsNamespace\Admin\Test; | |
* | |
* See the big comment block below for specific examples of where the plugin | |
* is going to look for classes, and in what order. Note that extensions can | |
* filter in their own custom directories, if they choose. This is supported | |
* by the plugin. | |
* | |
* @author John Alarcon | |
* | |
* @since 0.1.0 | |
* | |
* @param string $class The class name, fully qualified or not. | |
*/ | |
public function autoload_classes($class) { | |
// Initialization. | |
$path = ''; | |
$parts = []; | |
// Replace underscores with backslashes. | |
$class = str_replace('_', '\\', $class); | |
// Separate qualified class name on backslashes. | |
$parts = explode('\\', $class); | |
// If dealing with the same (ie, this) namespace, no need to include it. | |
if (isset($parts[0]) && $parts[0] === __NAMESPACE__) { | |
unset($parts[0]); | |
} | |
if (isset($parts[1]) && $parts[1] === __NAMESPACE__) { | |
unset($parts[1]); | |
} | |
// If anything is left in the array, build a path out of it. | |
if (!empty($parts)) { | |
foreach($parts as $part) { | |
$path .= '/'.$part; | |
} | |
} | |
// First: load internal utility classes; ie, InputHandler, Navigation. | |
if (file_exists($file = PATH_CLASSES.$path.'.class.php')) { | |
require_once $file; | |
return; | |
} | |
// Next, deal with internal extensions; ie, Settings, Help, etc. | |
/** | |
* INTERNAL extensions' classes should be checked for in 3 ways. This is | |
* done to accommodate common approaches in structuring an extension. If | |
* an extension has only a single file, it can be dropped right into the | |
* ../extensions/ directory. When an extension has scripts or styles, it | |
* can be stored in its own directory and dropped into the ../extensions | |
* directory, as well. Finally, if an extension holds its classes inside | |
* a dedicated ../classes/ directory, that's fine, too. Note that all of | |
* these checks are ONLY for internal extensions. | |
* | |
* 1) single-file classes stored in ../extensions/ directory | |
* /plugins/my-plugin-name/extensions/MyClass.class.php | |
* | |
* 2) directory-based classes in ../extensions/ directory | |
* /plugins/my-plugin-name/extensions/MyClass/MyClass.class.php | |
* | |
* 3) directory-based extensions with a ../classes/ directory inside | |
* /plugins/my-plugin-name/extensions/MyClass/classes/MyClass.class.php | |
* | |
* EXTERNAL extensions are those that get installed later – sometimes as | |
* a premium add-on. If your plugin gets popular, other developers might | |
* even write extensions to enhance it. Awesome! | |
* | |
* 4) directory-based; by other authors, premium add-ons, etc. | |
* /plugins/your-extension/YourExtension.class.php | |
* /plugins/your-extension/classes/YourExtension.class.php | |
* | |
*/ | |
// Load internal extension classes; for single-file extensions. | |
if (file_exists($file = PATH_EXTENSIONS.'/'.$part.'.class.php')) { | |
require_once $file; | |
return; | |
} | |
// Load internal extension classes; for directory-stored extensions. | |
if (file_exists($file = PATH_EXTENSIONS.'/'.$part.'/'.$part.'.class.php')) { | |
require_once $file; | |
return; | |
} | |
// Load internal extension classes; directory-stored, /class directory. | |
if (file_exists($file = PATH_EXTENSIONS.'/'.$part.'/classes/'.$part.'.class.php')) { | |
require_once $file; | |
return; | |
} | |
// Note to self: Self, does this prevent breakage during core updates? | |
// Self: Why, yes, it does. Otherwise, this autoloader hijacks the show. | |
if (!isset($parts[1]) || empty($parts[1])) { | |
return; | |
} | |
// Class not found? Check external extensions' ../classes/ directory. | |
$extensions = get_option(PLUGIN_PREFIX.'_extensions_active', []); | |
foreach ($extensions as $file_slug) { | |
if (file_exists($file = PATH_PLUGINS.'/'.substr($file_slug, 0, strlen($file_slug) - (strlen($file_slug) - strrpos($file_slug, '/'))).'/classes/'.$parts[1].'.class.php')) { | |
require_once $file; | |
$class_found = true; | |
break; | |
} | |
} | |
if (isset($class_found)) { | |
return; | |
} | |
// Still not found? Last chance! Did an extension add an include path? | |
$autoload_paths = apply_filters(PLUGIN_PREFIX.'_autoload_paths', []); | |
foreach ($autoload_paths as $path) { | |
$file = $path.'/'.$parts[1].'.class.php'; | |
if (file_exists($file)) { | |
require_once $file; | |
break; | |
} | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment