Files
rspade_system/app/RSpade/Commands/Refactor/RefactorPhpClass_Command.php
root f6fac6c4bc Fix bin/publish: copy docs.dist from project root
Fix bin/publish: use correct .env path for rspade_system
Fix bin/publish script: prevent grep exit code 1 from terminating script

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 02:08:33 +00:00

196 lines
7.2 KiB
PHP
Executable File

<?php
namespace App\RSpade\Commands\Refactor;
use Illuminate\Console\Command;
use App\RSpade\Core\Manifest\Manifest;
use App\RSpade\Commands\Refactor\Php\ClassReferenceScanner;
use App\RSpade\Commands\Refactor\Php\FileUpdater;
use App\RSpade\Core\PHP\Filename_Suggester;
use App\RSpade\Upstream\RefactorLog;
class RefactorPhpClass_Command extends Command
{
protected $signature = 'rsx:refactor:rename_php_class
{old_class : The current class name to rename}
{new_class : The new class name}
{--dry-run : Show what would be changed without modifying files}
{--skip-rename-file : Skip renaming the source file after class refactor}';
protected $description = 'Refactor a PHP class name across all RSX files (PHP and Blade)';
public function handle()
{
$old_class = $this->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(" <fg=cyan>{$file_path}</>");
$this->line(" <fg=gray>" . count($occurrences) . " occurrence(s)</>");
}
if ($dry_run) {
$this->info("");
$this->info("<fg=yellow>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("<fg=green>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("<fg=green>✓</> 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("<fg=yellow>⚠</> 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("<fg=green>✓</> Renamed: {$current_filename}{$suggested_filename}");
}
}