Files
rspade_system/app/RSpade/CodeQuality/Rules/PHP/ReflectionIsAbstract_CodeQualityRule.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

161 lines
6.0 KiB
PHP
Executable File

<?php
namespace App\RSpade\CodeQuality\Rules\PHP;
use App\RSpade\CodeQuality\Rules\CodeQualityRule_Abstract;
/**
* ReflectionIsAbstract_CodeQualityRule - Detects native PHP ReflectionClass isAbstract() usage
*
* This rule identifies code that uses PHP's ReflectionClass to check if a class is abstract,
* and suggests using the more efficient Manifest methods instead. The Manifest provides
* pre-computed data that avoids the overhead of reflection.
*
* What it detects:
* - new ReflectionClass($class)->isAbstract()
* - $reflection->isAbstract() patterns
*
* Why it matters:
* - ReflectionClass is expensive, especially in loops
* - Manifest data is pre-computed during build time
* - Avoids runtime overhead and improves performance
*
* Exceptions:
* - Manifest.php itself (where the data is extracted)
* - Exception handlers (which may need reflection for error reporting)
*/
class ReflectionIsAbstract_CodeQualityRule extends CodeQualityRule_Abstract
{
/**
* Get the unique rule identifier
*/
public function get_id(): string
{
return 'PHP-REFLECT-01';
}
/**
* Get human-readable rule name
*/
public function get_name(): string
{
return 'ReflectionClass isAbstract() Usage';
}
/**
* Get rule description
*/
public function get_description(): string
{
return 'Detects usage of ReflectionClass->isAbstract() and suggests using Manifest methods';
}
/**
* Get file patterns this rule applies to
*/
public function get_file_patterns(): array
{
return ['*.php'];
}
/**
* Check the file for ReflectionClass isAbstract() usage
*/
public function check(string $file_path, string $contents, array $metadata = []): void
{
// Skip if not in rsx or app/RSpade directories
if (!str_contains($file_path, '/rsx/') && !str_starts_with($file_path, 'rsx/') &&
!str_contains($file_path, '/app/RSpade/') && !str_starts_with($file_path, 'app/RSpade/')) {
return;
}
// Skip Manifest.php itself
if (str_ends_with($file_path, 'Manifest.php')) {
return;
}
// Skip exception handlers
if (str_contains($file_path, 'Exception') || str_contains($file_path, 'Handler')) {
return;
}
$lines = explode("\n", $contents);
foreach ($lines as $line_number => $line) {
$actual_line_number = $line_number + 1;
// Skip if line has exception comment
if (str_contains($line, '@PHP-REFLECT-01-EXCEPTION')) {
continue;
}
// Pattern 1: Direct usage - new ReflectionClass(...)->isAbstract()
if (preg_match('/\(?\s*new\s+\\\\?ReflectionClass\s*\([^)]+\)\s*\)?\s*->\s*isAbstract\s*\(\)/', $line)) {
$this->add_violation(
$file_path,
$actual_line_number,
"Using ReflectionClass->isAbstract() instead of Manifest::php_is_abstract()",
trim($line),
$this->get_remediation_message(),
'high'
);
continue;
}
// Pattern 2: Variable usage - $reflection->isAbstract()
if (preg_match('/\$\w+\s*->\s*isAbstract\s*\(\)/', $line)) {
// Check if this variable was created from ReflectionClass in nearby lines
$start = max(0, $line_number - 5);
$end = min(count($lines) - 1, $line_number);
for ($i = $start; $i <= $end; $i++) {
if (preg_match('/\$\w+\s*=\s*new\s+\\\\?ReflectionClass/', $lines[$i])) {
$this->add_violation(
$file_path,
$actual_line_number,
"Using ReflectionClass->isAbstract() instead of Manifest::php_is_abstract()",
trim($line),
$this->get_remediation_message(),
'high'
);
break;
}
}
}
}
}
/**
* Get the remediation message with examples
*/
private function get_remediation_message(): string
{
return "Replace ReflectionClass->isAbstract() with Manifest methods for better performance:\n\n" .
"Example 1 - Check if a specific class is abstract:\n" .
" // Before:\n" .
" \$reflection = new ReflectionClass(\$class_name);\n" .
" if (\$reflection->isAbstract()) { ... }\n\n" .
" // After:\n" .
" if (\\App\\RSpade\\Core\\Manifest\\Manifest::php_is_abstract(\$class_name)) { ... }\n\n" .
"Example 2 - Get all non-abstract subclasses:\n" .
" // Before:\n" .
" \$subclasses = [];\n" .
" foreach (\$all_classes as \$class) {\n" .
" if (is_subclass_of(\$class, \$base_class)) {\n" .
" \$reflection = new ReflectionClass(\$class);\n" .
" if (!\$reflection->isAbstract()) {\n" .
" \$subclasses[] = \$class;\n" .
" }\n" .
" }\n" .
" }\n\n" .
" // After:\n" .
" \$subclasses = \\App\\RSpade\\Core\\Manifest\\Manifest::php_get_subclasses_of(\$base_class, false);\n\n" .
"Example 3 - Get only concrete extending classes:\n" .
" // Use php_get_extending() with include_abstract = false:\n" .
" \$concrete = \\App\\RSpade\\Core\\Manifest\\Manifest::php_get_extending(\$base_class, false);\n\n" .
"Benefits:\n" .
"- Avoids expensive reflection operations\n" .
"- Uses pre-computed manifest data (O(1) lookup)\n" .
"- Improves performance in loops and bulk operations";
}
}