matches); } /** * Get number of matches */ public function getMatchCount(): int { return count($this->matches); } /** * Get matches by severity */ public function getMatchesBySeverity(string $severity): array { return array_filter( $this->matches, fn (RuleMatch $match) => $match->severity->value === $severity ); } /** * Get critical matches */ public function getCriticalMatches(): array { return $this->getMatchesBySeverity('critical'); } /** * Get high severity matches */ public function getHighSeverityMatches(): array { return $this->getMatchesBySeverity('high'); } /** * Get blocking matches (matches that should block the request) */ public function getBlockingMatches(): array { return array_filter( $this->matches, fn (RuleMatch $match) => $match->shouldBlock() ); } /** * Get matches that should trigger alerts */ public function getAlertingMatches(): array { return array_filter( $this->matches, fn (RuleMatch $match) => $match->shouldAlert() ); } /** * Get OWASP Top 10 matches */ public function getOwaspTop10Matches(): array { return array_filter( $this->matches, fn (RuleMatch $match) => $match->category->isOwaspTop10() ); } /** * Get matches by category */ public function getMatchesByCategory(string $category): array { return array_filter( $this->matches, fn (RuleMatch $match) => $match->category->value === $category ); } /** * Check if any blocking matches exist */ public function shouldBlock(): bool { return ! empty($this->getBlockingMatches()); } /** * Check if any alerting matches exist */ public function shouldAlert(): bool { return ! empty($this->getAlertingMatches()); } /** * Check if evaluation had errors */ public function hasErrors(): bool { return $this->errorCount > 0; } /** * Get highest severity level from matches */ public function getHighestSeverity(): ?string { if (empty($this->matches)) { return null; } $severityOrder = ['critical', 'high', 'medium', 'low', 'info']; foreach ($severityOrder as $severity) { if (! empty($this->getMatchesBySeverity($severity))) { return $severity; } } return null; } /** * Get categories of all matches */ public function getMatchedCategories(): array { $categories = []; foreach ($this->matches as $match) { $categories[$match->category->value] = $match->category; } return array_values($categories); } /** * Get rule IDs of all matches */ public function getMatchedRuleIds(): array { return array_map( fn (RuleMatch $match) => $match->ruleId->value, $this->matches ); } /** * Calculate overall confidence score */ public function getOverallConfidence(): Percentage { if (empty($this->matches)) { return Percentage::from(0.0); } $totalConfidence = 0.0; $confidenceCount = 0; foreach ($this->matches as $match) { if ($match->confidence !== null) { $totalConfidence += $match->confidence->getValue(); $confidenceCount++; } } if ($confidenceCount === 0) { return Percentage::from(0.0); } return Percentage::from($totalConfidence / $confidenceCount); } /** * Calculate threat score based on matches */ public function getThreatScore(): Percentage { if (empty($this->matches)) { return Percentage::from(0.0); } $score = 0.0; $maxScore = 0.0; foreach ($this->matches as $match) { $matchScore = match ($match->severity->value) { 'critical' => 25.0, 'high' => 20.0, 'medium' => 10.0, 'low' => 5.0, 'info' => 1.0, default => 0.0 }; // Apply confidence modifier if ($match->confidence !== null) { $matchScore *= ($match->confidence->getValue() / 100.0); } $score += $matchScore; $maxScore = max($maxScore, $matchScore); } // Use weighted combination: 70% cumulative + 30% maximum $finalScore = ($score * 0.7) + ($maxScore * 0.3); return Percentage::from(min(100.0, $finalScore)); } /** * Get evaluation performance summary */ public function getPerformanceSummary(): array { $efficiency = 0.0; if ($this->evaluatedRules > 0) { $rulesPerMs = $this->evaluatedRules / max(1, $this->evaluationTime->toMilliseconds()); $efficiency = $rulesPerMs * 1000; // Rules per second } return [ 'evaluation_time_ms' => $this->evaluationTime->toMilliseconds(), 'evaluated_rules' => $this->evaluatedRules, 'skipped_rules' => $this->skippedRules, 'error_count' => $this->errorCount, 'rules_per_second' => round($efficiency, 2), 'error_rate' => $this->evaluatedRules > 0 ? round(($this->errorCount / $this->evaluatedRules) * 100, 2) : 0.0, ]; } /** * Convert to array for serialization */ public function toArray(): array { return [ 'timestamp' => $this->timestamp->toIsoString(), 'evaluation_time_ms' => $this->evaluationTime->toMilliseconds(), 'evaluated_rules' => $this->evaluatedRules, 'skipped_rules' => $this->skippedRules, 'error_count' => $this->errorCount, 'match_count' => $this->getMatchCount(), 'has_matches' => $this->hasMatches(), 'should_block' => $this->shouldBlock(), 'should_alert' => $this->shouldAlert(), 'highest_severity' => $this->getHighestSeverity(), 'threat_score' => $this->getThreatScore()->getValue(), 'overall_confidence' => $this->getOverallConfidence()->getValue(), 'matched_categories' => array_map(fn ($cat) => $cat->value, $this->getMatchedCategories()), 'matched_rule_ids' => $this->getMatchedRuleIds(), 'owasp_top10_matches' => count($this->getOwaspTop10Matches()), 'critical_matches' => count($this->getCriticalMatches()), 'high_severity_matches' => count($this->getHighSeverityMatches()), 'blocking_matches' => count($this->getBlockingMatches()), 'alerting_matches' => count($this->getAlertingMatches()), 'performance_summary' => $this->getPerformanceSummary(), 'matches' => array_map(fn (RuleMatch $match) => $match->toArray(), $this->matches), 'errors' => $this->errors, ]; } }