$line) { // Track multi-line comment state // Check for comment start (but not if it's closed on same line) if (preg_match('#/\*#', $line) && !preg_match('#/\*.*\*/#', $line)) { $in_multiline_comment = true; } // Check for comment end if (preg_match('#\*/#', $line)) { $in_multiline_comment = false; continue; // Skip the closing line too } // Skip lines inside multi-line comments if ($in_multiline_comment) { continue; } // Look for public non-static function declarations if (preg_match('/public\s+(?!static\s+)function\s+(\w+)\s*\(/', $line, $func_match)) { $method_name = $func_match[1]; $method_start_line = $i + 1; // Skip magic methods and Laravel lifecycle methods if (str_starts_with($method_name, '__') || in_array($method_name, ['boot', 'booted', 'booting'])) { continue; } // Find the method body (from { to }) $brace_count = 0; $in_method = false; $method_body = ''; $method_lines = []; for ($j = $i; $j < count($lines); $j++) { $current_line = $lines[$j]; // Track opening braces if (strpos($current_line, '{') !== false) { $brace_count += substr_count($current_line, '{'); $in_method = true; } if ($in_method) { $method_body .= $current_line . "\n"; $method_lines[] = $j + 1; // Store 1-based line numbers } // Track closing braces if (strpos($current_line, '}') !== false) { $brace_count -= substr_count($current_line, '}'); if ($brace_count <= 0 && $in_method) { break; } } } // Count relationship return statements $relationship_count = 0; $relationship_types = []; foreach ($this->approved_relations as $relation_type) { $pattern = '/return\s+\$this->' . preg_quote($relation_type) . '\s*\(/'; $matches = preg_match_all($pattern, $method_body); if ($matches > 0) { $relationship_count += $matches; $relationship_types[] = $relation_type; } } // Determine if this is a relationship method if ($relationship_count > 0) { if ($relationship_count > 1) { // Complex relationship with multiple return statements $complex_relationships[$method_name] = [ 'line' => $method_start_line, 'count' => $relationship_count, 'types' => $relationship_types ]; } else { // Simple relationship $relationship_methods[$method_name] = [ 'line' => $method_start_line, 'type' => $relationship_types[0] ]; } } } } // Check for complex relationships (violation) foreach ($complex_relationships as $method_name => $info) { $this->add_violation( $file_path, $info['line'], "Method '{$method_name}' is a complex relationship with {$info['count']} return statements", $lines[$info['line'] - 1] ?? '', "Split into separate methods, each returning a single relationship", 'high' ); } // Check simple relationships for #[Relationship] attribute foreach ($relationship_methods as $method_name => $info) { $has_attribute = false; // Check in manifest metadata for the attribute if (isset($manifest_metadata['public_instance_methods'][$method_name]['attributes']['Relationship'])) { $has_attribute = true; } if (!$has_attribute) { $this->add_violation( $file_path, $info['line'], "Relationship method '{$method_name}' is missing #[Relationship] attribute", $lines[$info['line'] - 1] ?? '', "Add #[Relationship] attribute above the method declaration", 'medium' ); } } // Check for methods with #[Relationship] that aren't actually relationships if (isset($manifest_metadata['public_instance_methods'])) { foreach ($manifest_metadata['public_instance_methods'] as $method_name => $method_data) { // Check if has Relationship attribute if (isset($method_data['attributes']['Relationship'])) { // Check if it's actually a relationship if (!isset($relationship_methods[$method_name]) && !isset($complex_relationships[$method_name])) { // Find the line number for this method $method_line = 1; foreach ($lines as $i => $line) { if (preg_match('/function\s+' . preg_quote($method_name) . '\s*\(/', $line)) { $method_line = $i + 1; break; } } $this->add_violation( $file_path, $method_line, "Method '{$method_name}' has #[Relationship] attribute but is not a Laravel relationship", $lines[$method_line - 1] ?? '', "Remove #[Relationship] attribute as this method doesn't return a relationship", 'high' ); } } } } } }