argument('old_class'); $new_class = $this->argument('new_class'); $dry_run = $this->option('dry-run'); // Initialize manifest Manifest::init(); // Step 1: Validate source class exists in manifest $this->info("Validating source class: {$old_class}"); try { $source_path = Manifest::php_find_class($old_class); $source_metadata = Manifest::get_file($source_path); } catch (\RuntimeException $e) { $this->error("Source class '{$old_class}' not found in manifest"); return 1; } $source_fqcn = $source_metadata['namespace'] . '\\' . $source_metadata['class']; // Step 2: Validate source class is in allowed directories if (!str_starts_with($source_path, 'rsx/') && !str_starts_with($source_path, 'app/RSpade/')) { $this->error("Source class must be in ./rsx or ./app/RSpade directories"); $this->error("Found in: {$source_path}"); return 1; } $this->info("Source class found: {$source_path}"); // Step 3: Validate destination class does NOT exist in manifest $this->info("Validating destination class: {$new_class}"); try { $conflicting_path = Manifest::php_find_class($new_class); $this->error("Destination class '{$new_class}' already exists in manifest"); $this->error("Conflicting file: {$conflicting_path}"); return 1; } catch (\RuntimeException $e) { // Good - destination class doesn't exist $this->info("Destination class available"); } // Step 4: Scan all PHP and Blade files for references $this->info(""); $this->info("Scanning for references to '{$old_class}'..."); $scanner = new ClassReferenceScanner(); $references = $scanner->find_all_references($old_class, $source_fqcn); if (empty($references)) { $this->warn("No references found to '{$old_class}'"); return 0; } // Step 5: Check for framework file modifications when not in framework developer mode if (!config('rsx.code_quality.is_framework_developer', false)) { $framework_files = []; foreach ($references as $file_path => $occurrences) { if (str_starts_with($file_path, 'app/RSpade/')) { $framework_files[] = $file_path; } } if (!empty($framework_files)) { $this->error(""); $this->error("FATAL: This refactor would modify " . count($framework_files) . " framework file(s) in app/RSpade/"); $this->error(""); $this->error("Framework files that would be modified:"); foreach ($framework_files as $file) { $this->error(" - {$file}"); } $this->error(""); $this->error("Refactoring core framework files is only available to RSpade framework maintainers."); $this->error("Set IS_FRAMEWORK_DEVELOPER=true in .env if you are maintaining the framework."); return 1; } } // Step 6: Display changes preview $this->info(""); $this->info("Found " . count($references) . " file(s) with references:"); $this->info(""); foreach ($references as $file_path => $occurrences) { $this->line(" {$file_path}"); $this->line(" " . count($occurrences) . " occurrence(s)"); } if ($dry_run) { $this->info(""); $this->info("DRY RUN - No files modified"); return 0; } // Step 7: Apply changes $this->info(""); $this->info("Applying changes..."); $updater = new FileUpdater(); $updated_count = $updater->update_class_references($references, $old_class, $new_class, $source_fqcn); $this->info(""); $this->info("Successfully updated {$updated_count} file(s)"); // Step 8: Log refactor operation for upstream tracking RefactorLog::log_refactor( "rsx:refactor:rename_php_class {$old_class} {$new_class}", $source_path ); // Step 9: Auto-rename the source file if needed if (!$this->option('skip-rename-file')) { $this->info(""); $this->info("Checking if source file needs renaming..."); $this->rename_source_file_if_needed($source_path, $new_class, $source_metadata); } $this->info(""); $this->info("Next steps:"); $this->info(" 1. Review changes: git diff"); $this->info(" 2. Test application"); return 0; } /** * Rename the source file to match the new class name if needed * * @param string $source_path Current relative file path * @param string $new_class New class name * @param array $metadata File metadata from manifest */ protected function rename_source_file_if_needed(string $source_path, string $new_class, array $metadata): void { $extension = $metadata['extension'] ?? 'php'; $is_rspade = str_starts_with($source_path, 'app/RSpade/'); // Get suggested filename $suggested_filename = Filename_Suggester::get_suggested_class_filename( $source_path, $new_class, $extension, $is_rspade, false // Not a Jqhtml component ); $current_filename = basename($source_path); // Check if rename is needed if ($current_filename === $suggested_filename) { $this->info("✓ Source filename already correct: {$current_filename}"); return; } // Build new path $dir_path = dirname($source_path); $new_path = $dir_path . '/' . $suggested_filename; // Check if destination already exists if (file_exists(base_path($new_path))) { $this->warn("⚠ Auto-rename skipped: Destination file already exists: {$new_path}"); return; } // Perform rename $old_absolute = base_path($source_path); $new_absolute = base_path($new_path); rename($old_absolute, $new_absolute); $this->info("✓ Renamed: {$current_filename} → {$suggested_filename}"); } }