events = $events; } /** * Execute the §12.5 audit-events-summary query. * * Calls {@see AuditEventRepository::countByAuditKind()} for * the per-kind histogram, then * {@see AuditEventRepository::findPaginated()} with no * filter and `per_page = $recentLimit, page = 1` for the * newest-first recent slice. Each returned record is * projected through the §12.4 per-row helper * {@see ListAuditEventsUseCase::projectRow()} so the summary * row shape exactly matches the list row shape. * * The histogram is default-filled against * {@see AuditEventListQuery::VALID_AUDIT_KINDS} before being * handed to the result VO so REST consumers always observe a * stable map shape regardless of which kinds the persistence * layer happened to omit. The `total` axis is computed as the * sum of the histogram values to keep the summary surface to * a single round-trip per axis. * * @param int $recentLimit Recent-entries limit (1 .. {@see self::MAX_RECENT_LIMIT}). * * @throws InvalidArgumentException When `$recentLimit` is out * of the [1, MAX_RECENT_LIMIT] * range. */ public function execute( int $recentLimit = self::DEFAULT_RECENT_LIMIT ): GetAuditEventsSummaryResult { if ( $recentLimit < 1 || $recentLimit > self::MAX_RECENT_LIMIT ) { $message = sprintf( 'GetAuditEventsSummaryUseCase: recentLimit must be 1..%d; got %d.', self::MAX_RECENT_LIMIT, $recentLimit ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Developer-facing message; not user output. throw new InvalidArgumentException( $message ); } $rawCounts = $this->events->countByAuditKind(); $byKind = self::defaultFillCounts( $rawCounts ); $page = $this->events->findPaginated( new AuditEventListQuery( null, null, null, null, 1, $recentLimit ) ); $recent = array(); foreach ( $page->items() as $record ) { $recent[] = ListAuditEventsUseCase::projectRow( $record ); } $total = array_sum( $byKind ); return new GetAuditEventsSummaryResult( $byKind, $recent, $recentLimit, $total ); } /** * Default-fill the supplied per-kind count map against * {@see AuditEventListQuery::VALID_AUDIT_KINDS} so every * known kind has a numeric entry. * * Kinds the persistence layer omitted (zero occurrences) * carry `0` in the returned map; kinds the persistence layer * reported are preserved verbatim. Unknown kinds in * `$rawCounts` (defensive: should never happen because the * persistence layer enforces the closed vocabulary) are * passed through so an operator inspecting the histogram can * still see anomalous data rather than have it silently * dropped. * * @param array $rawCounts Per-kind counts as * reported by * {@see AuditEventRepository::countByAuditKind()}. * * @return array */ private static function defaultFillCounts( array $rawCounts ): array { $filled = array(); foreach ( AuditEventListQuery::VALID_AUDIT_KINDS as $kind ) { $filled[ $kind ] = $rawCounts[ $kind ] ?? 0; } // Preserve any unexpected kinds the persistence layer // surfaced so an anomaly does not get silently dropped. foreach ( $rawCounts as $kind => $count ) { if ( ! array_key_exists( $kind, $filled ) ) { $filled[ $kind ] = $count; } } return $filled; } }