Framework pull: auto-revert use statement changes before update

Class override system: .upstream file handling and restore logic
Php_Fixer: Redirect use statements to correct manifest FQCN
Fix: Only match PHP files in __find_class_fqcn_in_manifest
Complete Php_Fixer use statement redirection implementation (checkpoint 2)
WIP: Php_Fixer use statement redirection for class overrides (checkpoint)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2025-12-10 07:04:05 +00:00
parent 0a479ed5a2
commit f02a04f37a
6 changed files with 307 additions and 51 deletions

View File

@@ -1355,23 +1355,17 @@ class Manifest
// but we don't need to log it as it's not an error condition
}
// Validate class names are unique (also handles rsx/ overriding app/RSpade/ classes)
static::__check_unique_base_class_names();
// If a class override was detected (rsx/ overriding app/RSpade/), restart manifest build
if (static::$_needs_manifest_restart) {
console_debug('MANIFEST', 'Class override detected, restarting manifest build');
goto manifest_start;
}
// ==================================================================================
// PHP FIXER INTEGRATION POINT
// ==================================================================================
// This is where automatic code fixes are applied before Phase 2 parsing.
// CRITICAL: Php_Fixer MUST run BEFORE __check_unique_base_class_names() so that
// when a class override is detected and framework files are renamed to .upstream,
// all use statements have already been updated to point to the rsx/ location.
// This prevents autoloader failures when the manifest restarts.
//
// WHAT PHP_FIXER DOES:
// 1. Fixes namespaces to match file paths
// 2. Removes/rebuilds use statements (strips Rsx\ and App\RSpade\ prefixes)
// 2. Redirects use statements to correct FQCN based on manifest (rsx/ takes priority)
// 3. Replaces FQCNs like \Rsx\Models\User_Model with simple names User_Model
// 4. Adds #[Relationship] attributes to model ORM methods
// 5. Removes leading backslashes from attributes: #[\Route] → #[Route]
@@ -1381,11 +1375,6 @@ class Manifest
// - If structure changed: Fixes ALL files (cascading updates needed)
// - If structure unchanged: Fixes ONLY $files_to_process (incremental)
//
// WHY BEFORE PHASE 2:
// - Phase 2 parses metadata from file content
// - If we fix AFTER parsing, manifest would have old/incorrect metadata
// - By fixing BEFORE, we parse the corrected content
//
// RE-PARSING LOOP BELOW:
// - If Php_Fixer modified files, we MUST re-parse them
// - This updates manifest with corrected namespace/class/FQCN data
@@ -1422,6 +1411,21 @@ class Manifest
}
}
// ==================================================================================
// CLASS OVERRIDE DETECTION
// ==================================================================================
// Check for duplicate class names. When rsx/ contains a class that also exists in
// app/RSpade/, rename the framework file to .upstream and restart the manifest.
// At this point, Php_Fixer has already updated use statements to point to rsx/.
// ==================================================================================
static::__check_unique_base_class_names();
// If a class override was detected (rsx/ overriding app/RSpade/), restart manifest build
if (static::$_needs_manifest_restart) {
console_debug('MANIFEST', 'Class override detected, restarting manifest build');
goto manifest_start;
}
// Phase 2 complete. At this point we have a list of all files, and for php and js, their class data
// =======================================================
@@ -1706,14 +1710,25 @@ class Manifest
/**
* Check for duplicate base class names within the same file type
*
* When a class exists in both rsx/ and app/RSpade/, this is a developer override.
* The framework version is renamed to .upstream and removed from indexing.
* This method handles two scenarios:
* 1. RESTORE: If a .upstream file exists but no active override exists, restore it
* 2. OVERRIDE: When a class exists in both rsx/ and app/RSpade/, rename framework to .upstream
*
* Throws a fatal error if duplicates exist within the same area (both rsx/ or both app/RSpade/)
*/
protected static function __check_unique_base_class_names(): void
{
// Group classes by extension first, then by class name
// ==================================================================================
// STEP 1: Restore orphaned .upstream files
// ==================================================================================
// Check all php.upstream files - if their class no longer has an override in rsx/,
// restore the framework file by renaming .upstream back to .php
// ==================================================================================
static::__restore_orphaned_upstream_files();
// ==================================================================================
// STEP 2: Group classes by extension, then by class name
// ==================================================================================
$classes_by_extension = [];
foreach (static::$data['data']['files'] as $file => $metadata) {
@@ -1721,6 +1736,11 @@ class Manifest
$base_class_name = $metadata['class'];
$extension = $metadata['extension'] ?? '';
// Skip php.upstream files - they're tracked separately for restoration
if ($extension === 'php.upstream') {
continue;
}
// Group JavaScript-like files together
if (in_array($extension, ['js', 'jsx', 'ts', 'tsx'])) {
$extension = 'js';
@@ -1738,7 +1758,9 @@ class Manifest
}
}
// Check for duplicates within each extension type
// ==================================================================================
// STEP 3: Check for duplicates and create overrides
// ==================================================================================
foreach ($classes_by_extension as $extension => $base_class_files) {
foreach ($base_class_files as $class_name => $files) {
if (count($files) > 1) {
@@ -1779,6 +1801,58 @@ class Manifest
}
}
/**
* Restore orphaned .upstream files when their override no longer exists
*
* Scans all php.upstream files in the manifest. For each one, checks if a .php file
* with the same class name exists. If not, the override was removed and we should
* restore the framework file.
*/
protected static function __restore_orphaned_upstream_files(): void
{
// Collect all PHP class names currently in the manifest
$active_php_classes = [];
foreach (static::$data['data']['files'] as $file => $metadata) {
if (isset($metadata['extension']) && $metadata['extension'] === 'php' &&
isset($metadata['class']) && !empty($metadata['class'])) {
$active_php_classes[$metadata['class']] = $file;
}
}
// Check each .upstream file
foreach (static::$data['data']['files'] as $file => $metadata) {
if (!isset($metadata['extension']) || $metadata['extension'] !== 'php.upstream') {
continue;
}
if (!isset($metadata['class']) || empty($metadata['class'])) {
continue;
}
$class_name = $metadata['class'];
// Check if an active .php file with this class exists
if (isset($active_php_classes[$class_name])) {
// Override still exists - keep .upstream as-is
continue;
}
// No active override - restore this framework file
$upstream_path = base_path($file);
$restored_path = preg_replace('/\.upstream$/', '', $upstream_path);
if (file_exists($upstream_path) && !file_exists($restored_path)) {
rename($upstream_path, $restored_path);
console_debug('MANIFEST', "Class restore: {$class_name} - restored {$file} to .php");
// Remove the .upstream entry from manifest
unset(static::$data['data']['files'][$file]);
static::$_needs_manifest_restart = true;
}
}
}
/**
* Collate files by class names and build inheritance indices
*
@@ -2515,9 +2589,11 @@ class Manifest
$stat = stat($absolute_path);
$extension = strtolower(pathinfo($file_path, PATHINFO_EXTENSION));
// Handle .blade.php as special case
// Handle compound extensions as special cases
if (str_ends_with($file_path, '.blade.php')) {
$extension = 'blade.php';
} elseif (str_ends_with($file_path, '.php.upstream')) {
$extension = 'php.upstream';
}
$data = [
@@ -2550,6 +2626,14 @@ class Manifest
}
break;
case 'php.upstream':
// Parse upstream files to extract class metadata
// These are framework files that were renamed when an rsx/ override was created
// Php_Fixer only runs on extension 'php', so upstream files are safe
$php_metadata = \App\RSpade\Core\PHP\Php_Parser::parse($absolute_path, static::$data);
$data = array_merge($data, $php_metadata);
break;
case 'js':
console_debug('BUILD', "Parsing JS file: {$file_path}");
$js_metadata = \App\RSpade\Core\JsParsers\Js_Parser::extract_metadata($absolute_path);