952 days continuous production uptime, 40k+ tp/s single node. Original corpo Bitbucket history not included — clean archive commit.
226 lines
11 KiB
PHP
226 lines
11 KiB
PHP
<?php
|
|
/**
|
|
* This autoloader takes care of class loading dynamically as well as maintaining scope
|
|
* and registration. All you have to do to use it is use the java style _import
|
|
* EX: _import('path.to.package.*');
|
|
* or the path based syntax
|
|
* EX: Autoloader::register_directory('./path/to/package/');
|
|
* multiple copies of a identically named class can exist, but only one can be
|
|
* loaded during execution, so while this can be helpful when dealing with libraries
|
|
* which have colliding namespaces, it is no panacea.
|
|
***********************************************************************************
|
|
* KEY FEATURES *
|
|
****************
|
|
* 1) auto-autoload (other than setting your class folders, you do nothing)
|
|
* 2) namespace compartmentalization (use the same class name over and over)
|
|
* 3) shorter class/folder hierarchy traversals (get to the right class faster)
|
|
* 4) allows inline catching of class load errors (no un-catchable fatals)
|
|
* 5) maximally optimized awesomeness factor (not really, but it sounds nice)
|
|
***********************************************************************************
|
|
*
|
|
* HISTORY:
|
|
* ========
|
|
* 06-15-17 mks Initial check-in
|
|
* 01-17-20 mks DB-150: PHP7.4 refactor, also got rid of the native logging which didn't work and replaced it
|
|
* with consoleLog, exception wrappers on all the things, and general code clean-up
|
|
*
|
|
*/
|
|
/** @noinspection PhpUnused */
|
|
|
|
/**
|
|
* _import()
|
|
*
|
|
* validates the input parameter.
|
|
*
|
|
* registers the class directory with the application so that future class
|
|
* instantiations are autoload'd from the registered directory.
|
|
*
|
|
* @param $namespace - enforces naming convention for class files: *.class.inc
|
|
*/
|
|
|
|
function _import($namespace)
|
|
{
|
|
$ns_parts = explode('.', $namespace);
|
|
if(end($ns_parts) == '*'){
|
|
unset($ns_parts[key($ns_parts)]);
|
|
$directory = implode('/', $ns_parts);
|
|
Autoloader::register_directory($directory, 1);
|
|
}
|
|
}
|
|
|
|
class Autoloader {
|
|
private static array $registry = array();
|
|
private static array $file_registry = array();
|
|
private static array $file_types = array('.class.inc');
|
|
public static string $base_dir = '.';
|
|
private static bool $initialized = false;
|
|
public static bool $verbose = false;
|
|
private static string $res = 'AUTO: ';
|
|
private static bool $debug = false; // change this to true for more output verbosity
|
|
|
|
|
|
// the following mode is an experimental setting to shoehorn dummy classes
|
|
// that can mask multiple collided classes and resolve the linkage by the
|
|
// calling context, I have personal doubts this will ever be suitable for
|
|
// anything but tests, unless you have a very odd need to sandbox namespaces.
|
|
// it's way inefficient as it is an attempt to autoload *every* instantiation
|
|
// the idea is to define a new class that injects an instance of the fully path'd
|
|
// class in it's own place, deletes itself and triggers removal of it's own class
|
|
// definition... sexy, eh?
|
|
private static bool $class_path_verify_mode = false; //Let me reiterate: this doesn't work yet, DO NOT USE
|
|
|
|
public static function initialize() :void
|
|
{
|
|
spl_autoload_register(array('Autoloader', 'load'));
|
|
Autoloader::$base_dir = getcwd();
|
|
Autoloader::$initialized = true;
|
|
}
|
|
|
|
public static function register_directory(string $directory, int $depth=0):void
|
|
{
|
|
if (!Autoloader::$initialized) {
|
|
try {
|
|
Autoloader::initialize();
|
|
} catch (TypeError $t) {
|
|
if (static::$debug)
|
|
consoleLog(static::$res, CON_SYSTEM, sprintf(INFO_LOC, basename(__METHOD__), __FILE__));
|
|
consoleLog(static::$res, CON_ERROR, $t->getMessage());
|
|
exit();
|
|
}
|
|
}
|
|
//examine the stack to get the calling file
|
|
$stacktrace = debug_backtrace();
|
|
if (array_key_exists($depth, $stacktrace) && array_key_exists('file', $stacktrace[$depth])) {
|
|
$calling_file = $stacktrace[$depth]['file'];
|
|
if (static::$debug) {
|
|
consoleLog(static::$res, CON_SYSTEM, sprintf(INFO_LOC, basename(__METHOD__), __FILE__));
|
|
consoleLog(static::$res, CON_SYSTEM, 'Registering ' . realpath(Autoloader::$base_dir . '/' . $directory) . ' to ' . $calling_file);
|
|
}
|
|
//register the directory to the calling file
|
|
Autoloader::$file_registry[$calling_file][] = $directory;
|
|
}
|
|
//register the directory globally
|
|
if(!in_array($directory, Autoloader::$registry)) Autoloader::$registry[] = $directory;
|
|
}
|
|
|
|
public static function find_class_definition(string $directory, $class_name) : ?string
|
|
{
|
|
foreach(Autoloader::$file_types as $type) {
|
|
//$class_path = realpath(Autoloader::$base_dir.'/'.$directory.'/'.$class_name.$type);
|
|
$class_path = realpath($directory . '/' . $class_name . $type);
|
|
if (static::$debug) {
|
|
consoleLog(static::$res, CON_SYSTEM, sprintf(INFO_LOC, basename(__METHOD__), __LINE__));
|
|
consoleLog(static::$res, CON_SYSTEM, 'Checking for class ' . $class_name . ' in directory: ' . $directory);
|
|
consoleLog(static::$res, CON_SYSTEM, 'Class Path: (' . $class_path . ')');
|
|
}
|
|
if (file_exists($class_path)) {
|
|
if (static::$debug) {
|
|
consoleLog(static::$res, CON_SYSTEM, sprintf(INFO_LOC, basename(__METHOD__), __LINE__));
|
|
consoleLog(static::$res, CON_SYSTEM, 'Found class ' . $class_name);
|
|
}
|
|
return $class_path;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public static function load(string $class_name, int $depth = 1) :void
|
|
{
|
|
if(!Autoloader::$initialized) Autoloader::initialize();
|
|
//get the context file we called from
|
|
$stacktrace = debug_backtrace();
|
|
@$calling_file = $stacktrace[$depth]['file'];
|
|
//attempt to load from the local context
|
|
$checked_dirs = array();
|
|
if(array_key_exists($calling_file, Autoloader::$file_registry) && is_array(Autoloader::$file_registry[$calling_file])) {
|
|
foreach(Autoloader::$file_registry[$calling_file] as $directory) {
|
|
if (static::$debug) {
|
|
consoleLog(static::$res, CON_SYSTEM, sprintf(INFO_LOC, basename(__METHOD__), __FILE__));
|
|
consoleLog(static::$res, CON_SYSTEM, 'Local Seek [' . $directory . ']');
|
|
}
|
|
try {
|
|
$definition = Autoloader::find_class_definition($directory, $class_name);
|
|
} catch (TypeError $t) {
|
|
if (static::$debug)
|
|
consoleLog(static::$res, CON_ERROR, sprintf(INFO_LOC, basename(__METHOD__), __FILE__));
|
|
consoleLog(static::$res, CON_ERROR, $t->getMessage());
|
|
exit();
|
|
}
|
|
if (!is_null($definition)) {
|
|
try {
|
|
/** @noinspection PhpIncludeInspection */
|
|
require_once($definition);
|
|
} catch (Throwable $t) {
|
|
if (static::$debug)
|
|
consoleLog(static::$res, CON_SYSTEM, sprintf(INFO_LOC, basename(__METHOD__), __FILE__));
|
|
consoleLog(static::$res, CON_ERROR, $t->getMessage());
|
|
exit();
|
|
}
|
|
return;
|
|
}
|
|
$checked_dirs[] = $directory;
|
|
}
|
|
}
|
|
//TODO: chain scopes so you have proper scope inheritance (not just local to the calling file)
|
|
// foreach depth we trim one link off the stack, then we walk through the stack. looking for scope
|
|
// attempt to load from the global context
|
|
foreach(Autoloader::$registry as $directory) {
|
|
if (static::$debug)
|
|
consoleLog(static::$res, CON_SYSTEM,'Global Seek ['.$directory.']');
|
|
try {
|
|
$definition = Autoloader::find_class_definition($directory, $class_name);
|
|
if (!is_null($definition)) {
|
|
/** @noinspection PhpIncludeInspection */
|
|
require_once($definition);
|
|
return;
|
|
}
|
|
} catch (TypeError $t) {
|
|
if (static::$debug)
|
|
consoleLog(static::$res, CON_SYSTEM, sprintf(INFO_LOC, basename(__METHOD__), __FILE__));
|
|
consoleLog(static::$res, CON_ERROR, $t->getMessage());
|
|
return;
|
|
}
|
|
}
|
|
// uh oh, we can't find the class, we're going to have to return a clean crash-dummy, so we can catch the error
|
|
consoleLog(static::$res, CON_ERROR, 'Could not find the class: ' . $directory . SLASH . $class_name . ' Creating a dummy.');
|
|
try {
|
|
Autoloader::load_text(Autoloader::create_crash_dummy($class_name), $class_name);
|
|
} catch (TypeError $t) {
|
|
if (static::$debug)
|
|
consoleLog(static::$res, CON_SYSTEM, sprintf(INFO_LOC, basename(__METHOD__), __FILE__));
|
|
consoleLog(static::$res, CON_ERROR, $t->getMessage());
|
|
}
|
|
}
|
|
|
|
public static function load_text(string $class_text, ?string $class=null, ?string $namespace=null) :void
|
|
{
|
|
try {
|
|
eval('?>' . $class_text . '<?php ');
|
|
//require_once($class_text);
|
|
consoleLog(static::$res, CON_SYSTEM,'Loaded class definition ['.$class.']');
|
|
if (Autoloader::$class_path_verify_mode && $class != null && $namespace != null) {
|
|
$class_with_package_text = preg_replace('~ '.$class.'~', ' '.$namespace.'_'.$class, $class_text);
|
|
eval('?>'.$class_with_package_text.'<?php ');
|
|
if (static::$debug)
|
|
consoleLog(static::$res, CON_ERROR, 'Loaded package specific class definition ['.$class_with_package_text.']');
|
|
}
|
|
} catch (Throwable $t) {
|
|
if (static::$debug) {
|
|
consoleLog(static::$res, CON_SYSTEM, sprintf(INFO_LOC, basename(__METHOD__), __FILE__));
|
|
consoleLog(static::$res, CON_ERROR, $t->getMessage());
|
|
consoleLog(static::$res, CON_ERROR, $class_text);
|
|
}
|
|
}
|
|
}
|
|
|
|
public static function create_crash_dummy(string $class_name): string {
|
|
return '<?php
|
|
class '.$class_name.' {
|
|
function __construct($a=null, $b=null, $c=null, $d=null, $e=null, $f=null, $g=null, $h=null, $i=null, $j=null, $k=null, $l=null, $m=null, $n=null, $o=null, $p=null) {
|
|
throw new Exception("AUTOLOADER: Class '.$class_name.' not found!");
|
|
}
|
|
}
|
|
?>';
|
|
}
|
|
}
|