$sanitized_line) { $line_number = $line_num + 1; // Skip if the line is empty in sanitized version if (trim($sanitized_line) === '') { continue; } // Check for protected static function or private static function if (preg_match('/\b(protected|private)\s+static\s+function\s+([a-zA-Z][a-zA-Z0-9_]*)\s*\(/', $sanitized_line, $matches)) { $visibility = $matches[1]; $function_name = $matches[2]; // Check if function name doesn't start with double underscore if (!str_starts_with($function_name, '__')) { // For private methods, ALWAYS require double underscore if ($visibility === 'private') { $original_line = $original_lines[$line_num] ?? $sanitized_line; $this->add_violation( $file_path, $line_number, "Private static method '{$function_name}' in RSpade framework must start with double underscore.", trim($original_line), "Add double underscore prefix to the method name (change '{$function_name}' to '__{$function_name}'). " . "All private static methods in the app/RSpade directory must start with double underscores " . "to clearly indicate they are internal-only methods. After renaming, refactor all references to this method " . "throughout the class to use the new name.", 'medium' ); } // For protected methods, check if they're overriding a parent method from outside App/RSpade elseif ($visibility === 'protected') { // Use reflection to check parent class hierarchy if ($this->is_overriding_external_method($full_class_name, $function_name)) { // This method overrides a parent method from outside App/RSpade, so it's exempt continue; } $original_line = $original_lines[$line_num] ?? $sanitized_line; $this->add_violation( $file_path, $line_number, "Protected static method '{$function_name}' in RSpade framework must start with double underscore.", trim($original_line), "Add double underscore prefix to the method name (change '{$function_name}' to '__{$function_name}'). " . "Protected static methods in the app/RSpade directory must start with double underscores " . "unless they override a parent class method from outside App/RSpade. After renaming, refactor all references " . "to this method throughout the class to use the new name.", 'medium' ); } } } } } /** * Check if a method is overriding a parent method from outside App/RSpade * * @PHP-REFLECT-02-EXCEPTION: This method needs ReflectionClass to check parent methods * from Laravel framework classes which are not tracked in the manifest. The manifest * only contains RSX application classes, not vendor dependencies, and we need to check * if specific methods exist in parent classes using hasMethod(). * * @param string $class_name Fully qualified class name * @param string $method_name Method name to check * @return bool True if the method overrides a parent method from outside App/RSpade */ private function is_overriding_external_method(string $class_name, string $method_name): bool { try { // Check if class exists if (!class_exists($class_name)) { return false; } $reflection = new \ReflectionClass($class_name); // Walk up the parent class hierarchy $parent = $reflection->getParentClass(); while ($parent !== false) { $parent_name = $parent->getName(); // Check if this parent class has the method if ($parent->hasMethod($method_name)) { // If the parent class is outside App/RSpade, this method is exempt if (!str_starts_with($parent_name, 'App\\RSpade\\')) { return true; } } // Move up to the next parent $parent = $parent->getParentClass(); } return false; } catch (\ReflectionException $e) { // If we can't reflect the class, be conservative and don't flag it return false; } } }