Add 100+ automated unit tests from .expect file specifications Add session system test Add rsx:constants:regenerate command test Add rsx:logrotate command test Add rsx:clean command test Add rsx:manifest:stats command test Add model enum system test Add model mass assignment prevention test Add rsx:check command test Add migrate:status command test 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
265 lines
7.6 KiB
PHP
265 lines
7.6 KiB
PHP
<?php
|
|
/**
|
|
* CODING CONVENTION:
|
|
* This file follows the coding convention where variable_names and function_names
|
|
* use snake_case (underscore_wherever_possible).
|
|
*/
|
|
|
|
namespace App\RSpade\Core;
|
|
|
|
use RuntimeException;
|
|
use App\RSpade\Core\Manifest\Manifest;
|
|
|
|
class Autoloader
|
|
{
|
|
// Manifest is now static - no instance needed
|
|
|
|
/**
|
|
* Registered class aliases
|
|
*/
|
|
protected static $aliases = [];
|
|
|
|
/**
|
|
* Whether the autoloader is registered
|
|
*/
|
|
protected static $registered = false;
|
|
|
|
/**
|
|
* Whether aliases have been loaded
|
|
*/
|
|
protected static $aliases_loaded = false;
|
|
|
|
/**
|
|
* Cache of the autoloader class map from manifest
|
|
*/
|
|
protected static ?array $class_map = null;
|
|
|
|
/**
|
|
* Register the RSX autoloader
|
|
*/
|
|
public static function register()
|
|
{
|
|
if (self::$registered) {
|
|
return;
|
|
}
|
|
|
|
// Initialize static Manifest and aliases
|
|
Manifest::init();
|
|
static::__load_aliases();
|
|
|
|
// Register with SPL after Composer
|
|
spl_autoload_register([static::class, 'load'], true, false);
|
|
|
|
self::$registered = true;
|
|
console_debug('AUTOLOADER', 'RSX Autoloader has been registered');
|
|
}
|
|
|
|
/**
|
|
* Load a class
|
|
*/
|
|
public static function load($class)
|
|
{
|
|
// Remove leading backslash
|
|
$requested_class = ltrim($class, '\\');
|
|
|
|
// Extract simple class name (after last backslash)
|
|
$simple_name = substr(strrchr($requested_class, '\\'), 1) ?: $requested_class;
|
|
|
|
// Try to find the simple class name in the manifest
|
|
try {
|
|
$metadata = Manifest::php_get_metadata_by_class($simple_name);
|
|
|
|
// Load the file
|
|
$file_path = str_replace('\\', '/', $metadata['file']);
|
|
$absolute_path = base_path($file_path);
|
|
if (file_exists($absolute_path)) {
|
|
require_once $absolute_path;
|
|
|
|
// If the requested FQCN still doesn't exist but the actual class does,
|
|
// create an alias
|
|
$actual_fqcn = $metadata['fqcn'];
|
|
if (!class_exists($requested_class, false) &&
|
|
class_exists($actual_fqcn, false) &&
|
|
$requested_class !== $actual_fqcn) {
|
|
class_alias($actual_fqcn, $requested_class);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
} catch (\RuntimeException $e) {
|
|
// Class not found in manifest by simple name
|
|
}
|
|
|
|
// Check for special case class patterns that need custom handling
|
|
|
|
// Check if it's an RSX class by namespace
|
|
if (strpos($requested_class, 'Rsx\\') === 0) {
|
|
return static::__load_rsx_namespaced_class($requested_class);
|
|
}
|
|
|
|
// Check if it's a non-namespaced RSX class (like Rsx_Controller_Abstract)
|
|
if (strpos($requested_class, 'Rsx_') === 0) {
|
|
return static::__load_rsx_base_class($requested_class);
|
|
}
|
|
|
|
// Check aliases
|
|
if (isset(static::$aliases[$requested_class])) {
|
|
return static::__load_aliased_class($requested_class);
|
|
}
|
|
|
|
// Check for non-namespaced classes in the autoloader class map
|
|
if (strpos($requested_class, '\\') === false) {
|
|
return static::__load_from_class_map($requested_class);
|
|
}
|
|
|
|
// Not our responsibility
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Load an RSX namespaced class
|
|
*/
|
|
protected static function __load_rsx_namespaced_class($class)
|
|
{
|
|
// First, try to find the class in the manifest by full class name
|
|
$manifest_data = Manifest::get_all();
|
|
|
|
// Search through all PHP files in the manifest
|
|
foreach ($manifest_data as $file_path => $file_info) {
|
|
if (!isset($file_info['namespace']) || !isset($file_info['class'])) {
|
|
continue;
|
|
}
|
|
|
|
// Check if this is the class we're looking for
|
|
$full_class_name = $file_info['namespace'] . '\\' . $file_info['class'];
|
|
if ($full_class_name === $class) {
|
|
// Found it! Load the file (convert relative to absolute)
|
|
// Convert backslashes to forward slashes for path
|
|
$file_path = str_replace('\\', '/', $file_path);
|
|
$absolute_path = base_path($file_path);
|
|
if (file_exists($absolute_path)) {
|
|
require_once $absolute_path;
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Class not found in manifest - return false to let other autoloaders try
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Load RSX base classes
|
|
*/
|
|
protected static function __load_rsx_base_class($class)
|
|
{
|
|
// Map of base classes to their locations
|
|
$base_classes = [
|
|
'Rsx_Controller_Abstract' => 'app/RSpade/Core/Base/Rsx_Controller_Abstract.php',
|
|
];
|
|
|
|
if (isset($base_classes[$class])) {
|
|
$file = base_path($base_classes[$class]);
|
|
if (file_exists($file)) {
|
|
require_once $file;
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Load an aliased class
|
|
*/
|
|
protected static function __load_aliased_class($class)
|
|
{
|
|
$actual_class = static::$aliases[$class];
|
|
|
|
// Try to load the actual class
|
|
if (class_exists($actual_class, true)) {
|
|
class_alias($actual_class, $class);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Load a class from the autoloader class map
|
|
* @param string $class Simple class name without namespace
|
|
*/
|
|
protected static function __load_from_class_map($class)
|
|
{
|
|
// Load class map from manifest if not cached
|
|
if (static::$class_map === null) {
|
|
static::$class_map = Manifest::get_autoloader_class_map();
|
|
}
|
|
|
|
// Check if the class exists in the map
|
|
if (!isset(static::$class_map[$class])) {
|
|
return false;
|
|
}
|
|
|
|
$fqcns = static::$class_map[$class];
|
|
|
|
// If multiple FQCNs exist for this simple name, throw an error
|
|
if (count($fqcns) > 1) {
|
|
$error_msg = "Fatal error: Ambiguous class name '{$class}'. Multiple classes found:\n";
|
|
foreach ($fqcns as $fqcn) {
|
|
$error_msg .= " - {$fqcn}\n";
|
|
}
|
|
$error_msg .= 'Please use the fully qualified class name (FQCN) to resolve ambiguity.';
|
|
|
|
throw new RuntimeException($error_msg);
|
|
}
|
|
|
|
// Single FQCN found, try to load it
|
|
$fqcn = $fqcns[0];
|
|
|
|
// Try to autoload the actual class
|
|
if (class_exists($fqcn, true) || interface_exists($fqcn, true) || trait_exists($fqcn, true)) {
|
|
// Create an alias for the simple name
|
|
class_alias($fqcn, $class);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Load class aliases from configuration
|
|
*/
|
|
protected static function __load_aliases()
|
|
{
|
|
if (static::$aliases_loaded) {
|
|
return;
|
|
}
|
|
|
|
// Future: Load from config/rsx.php
|
|
static::$aliases = [
|
|
// Example: 'UserModel' => 'Rsx\Models\User_Model'
|
|
];
|
|
|
|
static::$aliases_loaded = true;
|
|
}
|
|
|
|
/**
|
|
* Refresh the manifest and retry loading
|
|
*/
|
|
public static function refresh_and_retry($class)
|
|
{
|
|
// // In development mode, rebuild manifest if class not found
|
|
// if (config('app.env') === 'local') {
|
|
// Manifest::rebuild();
|
|
// return static::load($class);
|
|
// }
|
|
|
|
return false;
|
|
}
|
|
}
|