$findings What it reported. * @param string|null $error Why it could not run, or null. */ public function __construct( public readonly string $id, public readonly string $label, public readonly array $findings = array(), public readonly ?string $error = null ) { } /** * Whether this check was unable to run. */ public function failed(): bool { return null !== $this->error; } /** * Findings collapsed by what they say. * * Consecutive findings sharing severity, title and explanation become one * group carrying the prose once and the affected objects as a list. Three * application passwords nobody claims are three things to act on but one * paragraph; on a site with ten, repeating it is the difference between a * report an agency hands a client and one nobody finishes. * * Severity is part of the identity on purpose: the same sentence at two * severities is two statements, and merging them would quietly promote * or demote one. * * @return list}> */ public function groups(): array { $groups = array(); foreach ( $this->findings as $finding ) { $last = array() === $groups ? null : $groups[ count( $groups ) - 1 ]; if ( null !== $last && $last['severity'] === $finding->severity && $last['title'] === $finding->title && $last['detail'] === $finding->detail ) { $groups[ count( $groups ) - 1 ]['findings'][] = $finding; continue; } $groups[] = array( 'severity' => $finding->severity, 'title' => $finding->title, 'detail' => $finding->detail, 'findings' => array( $finding ), ); } return $groups; } }