extract_use_statements($original_content); foreach ($implements as $interface) { // Check if it's a PHP built-in interface if (in_array($interface, $php_builtin_interfaces)) { $vendor_interfaces[] = $interface; continue; } // Check if it's a fully qualified name if (str_contains($interface, '\\')) { // Already FQCN $fqcn = $interface; } else { // Look up in use statements $fqcn = $this->resolve_class_name($interface, $use_statements, $metadata['namespace'] ?? ''); } // Check if this interface is from local code if (str_starts_with($fqcn, 'Rsx\\') || str_starts_with($fqcn, 'App\\RSpade\\')) { $local_interfaces[] = $interface; } else { $vendor_interfaces[] = $interface; } } // If there are no local interfaces, no violation if (empty($local_interfaces)) { return; } // Generate violation for local interface usage $lines = explode("\n", $contents); $line_number = 0; $code_snippet = ''; // Find the line with class declaration and implements foreach ($lines as $i => $line) { if (preg_match('/\bclass\s+' . preg_quote($class_name) . '\b.*\bimplements\b/i', $line)) { $line_number = $i + 1; $code_snippet = $this->get_code_snippet($lines, $i); break; } } $resolution = "CRITICAL: The 'implements' keyword is not allowed in RSpade framework code.\n\n"; $resolution .= "INTERFACES DETECTED:\n"; $resolution .= '- Local interfaces (PROHIBITED): ' . implode(', ', $local_interfaces) . "\n"; if (!empty($vendor_interfaces)) { $resolution .= '- Vendor interfaces (allowed): ' . implode(', ', $vendor_interfaces) . "\n"; } $resolution .= "\n"; $resolution .= "WHY THIS IS PROHIBITED:\n"; $resolution .= "The RSpade framework philosophy requires using abstract classes instead of interfaces because:\n"; $resolution .= "1. Abstract classes can provide default implementations, reducing boilerplate\n"; $resolution .= "2. Abstract classes can have protected helper methods for subclasses\n"; $resolution .= "3. Single inheritance enforces simpler, more maintainable design\n"; $resolution .= "4. If a class needs multiple behaviors, it indicates the class is too complex\n\n"; $resolution .= "HOW TO FIX:\n"; $resolution .= "1. Convert the interface to an abstract class:\n"; $resolution .= " - Change 'interface' to 'abstract class'\n"; $resolution .= " - Add default implementations where appropriate\n"; $resolution .= " - Add protected helper methods if needed\n\n"; $resolution .= "2. Update this class to extend the abstract class:\n"; $resolution .= " - Change 'implements " . implode(', ', $local_interfaces) . "' to 'extends '\n"; $resolution .= " - If the class already extends something, you need to refactor\n\n"; $resolution .= "3. If the class truly needs multiple behaviors:\n"; $resolution .= " - This indicates the class has too many responsibilities\n"; $resolution .= " - Break it into separate classes with single responsibilities\n"; $resolution .= " - Use composition instead of inheritance where appropriate\n\n"; $resolution .= "EXCEPTION:\n"; $resolution .= "Model classes (subclasses of Eloquent Model or Rsx_Model_Abstract) ARE allowed to implement interfaces\n"; $resolution .= "because they often need to implement Laravel contracts like Authenticatable, etc.\n\n"; $resolution .= "If this is genuinely needed (extremely rare), add '@PHP-IMPLEMENTS-01-EXCEPTION' comment."; $this->add_violation( $file_path, $line_number, "Class '{$class_name}' uses 'implements' keyword with local interfaces: " . implode(', ', $local_interfaces), $code_snippet, $resolution, 'critical' ); } /** * Extract use statements from PHP file content */ protected function extract_use_statements(string $content): array { $use_statements = []; // Match use statements if (preg_match_all('/^\s*use\s+([^;]+);/m', $content, $matches)) { foreach ($matches[1] as $use_statement) { // Handle aliased imports if (str_contains($use_statement, ' as ')) { list($fqcn, $alias) = explode(' as ', $use_statement); $fqcn = trim($fqcn); $alias = trim($alias); $use_statements[$alias] = $fqcn; } else { $fqcn = trim($use_statement); // Get the simple name from FQCN $parts = explode('\\', $fqcn); $simple_name = end($parts); $use_statements[$simple_name] = $fqcn; } } } return $use_statements; } /** * Resolve a simple class name to FQCN using use statements */ protected function resolve_class_name(string $simple_name, array $use_statements, string $namespace): string { // Check if it's in use statements if (isset($use_statements[$simple_name])) { return $use_statements[$simple_name]; } // If not in use statements and we have a namespace, assume it's in the same namespace if ($namespace) { return $namespace . '\\' . $simple_name; } // Otherwise return as-is return $simple_name; } /** * Check if a file in /app/RSpade/ is in an allowed subdirectory * Based on scan_directories configuration */ private function is_in_allowed_rspade_directory(string $file_path): bool { // Get allowed subdirectories from config $scan_directories = config('rsx.manifest.scan_directories', []); // Extract allowed RSpade subdirectories $allowed_subdirs = []; foreach ($scan_directories as $scan_dir) { if (str_starts_with($scan_dir, 'app/RSpade/')) { $subdir = substr($scan_dir, strlen('app/RSpade/')); if ($subdir) { $allowed_subdirs[] = $subdir; } } } // Check if file is in any allowed subdirectory foreach ($allowed_subdirs as $subdir) { if (str_contains($file_path, '/app/RSpade/' . $subdir . '/') || str_contains($file_path, '/app/RSpade/' . $subdir)) { return true; } } return false; } }