scanner = new CodeQualityScanner( $fileScanner, $reflectionService, [ new RequireStringableImplementationRule(), new FinalClassRule(), new ReadonlyPropertyRule(), new ReadonlyGetterRule(), new DisallowInheritanceRule(), ], $logger ); } #[ConsoleCommand('quality:scan', 'Check framework classes against predefined rules')] public function scan(ConsoleInput $input, ConsoleOutputInterface $output): ExitCode { $pathArgument = $input->getArgument(0); $directory = $pathArgument !== null ? FilePath::create($pathArgument) : FilePath::create(getcwd() . '/src'); $skipErrors = $input->hasOption('skip-errors'); if (! $directory->exists() || ! $directory->isDirectory()) { $output->writeLine( sprintf('โŒ Directory "%s" does not exist or is not a directory.', $directory->toString()), ConsoleColor::RED ); return ExitCode::INVALID_INPUT; } $output->writeLine( sprintf('๐Ÿ” Scanning %s for code quality violations...', $directory->toString()), ConsoleColor::BRIGHT_CYAN ); $report = $this->scanner->scan($directory, $skipErrors); if ($report->hasErrors()) { $output->newLine(); $headerColor = $skipErrors ? ConsoleColor::YELLOW : ConsoleColor::BRIGHT_RED; $headerIcon = $skipErrors ? 'โš ๏ธ' : 'โŒ'; $output->writeLine( sprintf( '%s Encountered %d file/class error(s) while scanning.', $headerIcon, count($report->errors()) ), $headerColor ); foreach ($report->errors() as $error) { $output->writeLine(' โ€ข ' . $error, $headerColor); } if (! $skipErrors) { return ExitCode::FAILURE; } } $this->renderReport($report, $output); return $report->hasViolations() ? ExitCode::FAILURE : ExitCode::SUCCESS; } private function renderReport(CodeQualityReport $report, ConsoleOutputInterface $output): void { $output->newLine(); $summaryColor = $report->hasViolations() ? ConsoleColor::BRIGHT_RED : ConsoleColor::GREEN; $summaryIcon = $report->hasViolations() ? 'โŒ' : 'โœ…'; $summaryMessage = $report->hasViolations() ? sprintf('Found %d violation(s).', $report->countViolations()) : 'No violations found.'; $output->writeLine( sprintf( '%s %s Scanned %d files, %d classes in %.2fs', $summaryIcon, $summaryMessage, $report->inspectedFiles(), $report->inspectedClasses(), $report->durationSeconds() ), $summaryColor ); if (! $report->hasViolations()) { return; } $output->newLine(); foreach ($report->groupedByRule() as $ruleName => $violations) { $output->writeLine( sprintf('%s (%d)', $ruleName, count($violations)), ConsoleColor::BRIGHT_YELLOW ); foreach ($violations as $violation) { $lineInfo = $violation->line() !== null ? ':' . $violation->line() : ''; $output->writeLine( sprintf( ' โ€ข %s', $violation->className()->getFullyQualified() ), ConsoleColor::WHITE ); $output->writeLine( sprintf( ' โ†ณ %s%s', $violation->filePath()->toString(), $lineInfo ), ConsoleColor::GRAY ); $output->writeLine( sprintf(' %s', $violation->message()), ConsoleColor::BRIGHT_RED ); $output->newLine(); } } } }