From 688a290ab7933e9cfecce6780c4c0b2af5742f41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Sat, 23 May 2026 17:00:45 +0200 Subject: [PATCH 1/4] Enhance HTML and Markdown report generation with single table configuration - Updated HtmlReport to support a single table layout for metrics when configured. - Modified MarkdownReport to include a single table format for all methods. - Added new test cases to validate the single table output for both HTML and Markdown reports. - Introduced configuration options to toggle between grouped and single table formats in reports. --- .../Report/CognitiveReportFactory.php | 2 +- src/Business/Cognitive/Report/HtmlReport.php | 169 +++++++++++++----- .../Cognitive/Report/MarkdownReport.php | 29 +++ .../Cognitive/Report/HtmlReporterContent.html | 50 +++--- .../HtmlReporterContent_SingleTable.html | 53 ++++++ .../Cognitive/Report/HtmlReporterTest.php | 134 ++++++++++++-- ...ownReporterContent_SingleTableStreaming.md | 13 ++ .../Cognitive/Report/MarkdownReporterTest.php | 22 +++ 8 files changed, 390 insertions(+), 82 deletions(-) create mode 100644 tests/Unit/Business/Cognitive/Report/HtmlReporterContent_SingleTable.html create mode 100644 tests/Unit/Business/Cognitive/Report/MarkdownReporterContent_SingleTableStreaming.md diff --git a/src/Business/Cognitive/Report/CognitiveReportFactory.php b/src/Business/Cognitive/Report/CognitiveReportFactory.php index a1fccc3..c8511d7 100644 --- a/src/Business/Cognitive/Report/CognitiveReportFactory.php +++ b/src/Business/Cognitive/Report/CognitiveReportFactory.php @@ -37,7 +37,7 @@ public function create(string $type): ReportGeneratorInterface $builtIn = match ($type) { 'json' => new JsonReport(), 'csv' => new CsvReport(), - 'html' => new HtmlReport(), + 'html' => new HtmlReport($config), 'markdown' => new MarkdownReport($config), 'checkstyle' => new CheckstyleReport($config), 'junit' => new JUnitReport($config), diff --git a/src/Business/Cognitive/Report/HtmlReport.php b/src/Business/Cognitive/Report/HtmlReport.php index 199a64b..2b0fb9d 100644 --- a/src/Business/Cognitive/Report/HtmlReport.php +++ b/src/Business/Cognitive/Report/HtmlReport.php @@ -4,10 +4,12 @@ namespace Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\Report; +use Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\CognitiveMetrics; use Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\CognitiveMetricsCollection; use Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\Delta; use Phauthentic\CognitiveCodeAnalysis\Business\Utility\Datetime; use Phauthentic\CognitiveCodeAnalysis\CognitiveAnalysisException; +use Phauthentic\CognitiveCodeAnalysis\Config\CognitiveConfig; /** * HtmlReport class for exporting metrics as an HTML file. @@ -27,15 +29,16 @@ class HtmlReport implements ReportGeneratorInterface 'Return Count', 'Variable Count', 'Property Call Count', - 'Combined Cognitive Complexity' + 'Combined Cognitive Complexity', ]; + public function __construct( + private readonly CognitiveConfig $config, + ) { + } + /** * Export metrics to an HTML file using Bootstrap 5. - * - * @param CognitiveMetricsCollection $metrics - * @param string $filename - * @return void */ public function export(CognitiveMetricsCollection $metrics, string $filename): void { @@ -69,17 +72,68 @@ public function generateMetricRow(int $count, float $weight, ?Delta $delta): str return $metricRow . ''; } - /** - * Generate HTML content using the metrics data. - * - * @param CognitiveMetricsCollection $metrics - * @return string - */ private function generateHtml(CognitiveMetricsCollection $metrics): string + { + return $this->config->groupByClass + ? $this->generateGroupedHtml($metrics) + : $this->generateSingleTableHtml($metrics); + } + + private function generateGroupedHtml(CognitiveMetricsCollection $metrics): string + { + $groupedByClass = $metrics->groupBy('class'); + + ob_start(); + $this->renderDocumentStart(count($groupedByClass)); + foreach ($groupedByClass as $class => $methods) { + ?> + + + + + + renderHeaderRow(false); ?> + + + + renderMethodRow($data, false); ?> + + +
escape((string)$class); ?>
+ renderDocumentEnd(); + + return $this->captureOutput(); + } + + private function generateSingleTableHtml(CognitiveMetricsCollection $metrics): string { $groupedByClass = $metrics->groupBy('class'); + $totalMethods = count($metrics); ob_start(); + $this->renderDocumentStart(count($groupedByClass)); + ?> +

All Methods ( total)

+ + + renderHeaderRow(true); ?> + + + + renderMethodRow($data, true); ?> + + +
+ renderDocumentEnd(); + + return $this->captureOutput(); + } + + private function renderDocumentStart(int $classCount): void + { ?> @@ -93,44 +147,73 @@ private function generateHtml(CognitiveMetricsCollection $metrics): string

Cognitive Metrics Report - format('Y-m-d H:i:s') ?>

- This report contains the cognitive complexity metrics for the analyzed code in classes. + This report contains the cognitive complexity metrics for the analyzed code in classes.

+ $methods) : ?> - - - - - - - header as $column) : ?> - - - - - - - - - generateMetricRow($data->getLineCount(), $data->getLineCountWeight(), $data->getLineCountWeightDelta()); ?> - generateMetricRow($data->getArgCount(), $data->getArgCountWeight(), $data->getArgCountWeightDelta()); ?> - generateMetricRow($data->getIfCount(), $data->getIfCountWeight(), $data->getIfCountWeightDelta()); ?> - generateMetricRow($data->getIfNestingLevel(), $data->getIfNestingLevelWeight(), $data->getIfNestingLevelWeightDelta()); ?> - generateMetricRow($data->getElseCount(), $data->getElseCountWeight(), $data->getElseCountWeightDelta()); ?> - generateMetricRow($data->getReturnCount(), $data->getReturnCountWeight(), $data->getReturnCountWeightDelta()); ?> - generateMetricRow($data->getVariableCount(), $data->getVariableCountWeight(), $data->getVariableCountWeightDelta()); ?> - generateMetricRow($data->getPropertyCallCount(), $data->getPropertyCallCountWeight(), $data->getPropertyCallCountWeightDelta()); ?> - - - - -
escape((string)$class); ?>
escape($column); ?>
escape($data->getMethod()); ?>formatNumber($data->getScore()); ?>
- - + private function renderDocumentEnd(): void + { + ?>
+ + + Class + + header as $column) : ?> + escape($column); ?> + + + + + + escape($data->getClass()); ?> + + escape($data->getMethod()); ?> + generateMetricRow($data->getLineCount(), $data->getLineCountWeight(), $data->getLineCountWeightDelta()); ?> + generateMetricRow($data->getArgCount(), $data->getArgCountWeight(), $data->getArgCountWeightDelta()); ?> + generateMetricRow($data->getIfCount(), $data->getIfCountWeight(), $data->getIfCountWeightDelta()); ?> + generateMetricRow($data->getIfNestingLevel(), $data->getIfNestingLevelWeight(), $data->getIfNestingLevelWeightDelta()); ?> + generateMetricRow($data->getElseCount(), $data->getElseCountWeight(), $data->getElseCountWeightDelta()); ?> + generateMetricRow($data->getReturnCount(), $data->getReturnCountWeight(), $data->getReturnCountWeightDelta()); ?> + generateMetricRow($data->getVariableCount(), $data->getVariableCountWeight(), $data->getVariableCountWeightDelta()); ?> + generateMetricRow($data->getPropertyCallCount(), $data->getPropertyCallCountWeight(), $data->getPropertyCallCountWeightDelta()); ?> + formatNumber($data->getScore()); ?> + + config->groupByClass) { + $header .= "## All Methods\n\n"; + $header .= $this->buildTableHeaderWithClass() . "\n"; + $header .= $this->buildTableSeparatorWithClass() . "\n"; + } + fwrite($this->fileHandle, $header); } @@ -462,6 +468,11 @@ public function writeMetricBatch(CognitiveMetricsCollection $batch): void // Type guard: fileHandle is guaranteed to be resource at this point assert($this->fileHandle !== false); + if (!$this->config->groupByClass) { + $this->writeSingleTableBatch($batch); + return; + } + $groupedByClass = $batch->groupBy('class'); foreach ($groupedByClass as $class => $methods) { @@ -506,6 +517,24 @@ public function writeMetricBatch(CognitiveMetricsCollection $batch): void } } + private function writeSingleTableBatch(CognitiveMetricsCollection $batch): void + { + assert($this->fileHandle !== false); + + $filteredMetrics = $this->filterMetrics($batch); + $rows = ''; + + foreach ($filteredMetrics as $data) { + $rows .= $this->buildTableRowWithClass($data) . "\n"; + } + + if ($rows === '') { + return; + } + + fwrite($this->fileHandle, $rows); + } + /** * @throws CognitiveAnalysisException */ diff --git a/tests/Unit/Business/Cognitive/Report/HtmlReporterContent.html b/tests/Unit/Business/Cognitive/Report/HtmlReporterContent.html index 15abd69..9b71551 100644 --- a/tests/Unit/Business/Cognitive/Report/HtmlReporterContent.html +++ b/tests/Unit/Business/Cognitive/Report/HtmlReporterContent.html @@ -12,34 +12,32 @@

Cognitive Metrics Report - 2023-10-01 12:00:00

This report contains the cognitive complexity metrics for the analyzed code in 1 classes.

- - - - - - - - - - - - - - - - - - - - +
TestClass
MethodLine CountArgument CountIf CountIf Nesting LevelElse CountReturn CountVariable CountProperty Call CountCombined Cognitive Complexity
+ + + + + + + + + + + + + + + + + + - - - + + + -
TestClass
MethodLine CountArgument CountIf CountIf Nesting LevelElse CountReturn CountVariable CountProperty Call CountCombined Cognitive Complexity
testMethod10 (0.500) 2 (0.300) 4 (0.600) 2 (0.500) 1 (0.200) 1 (0.200) 5 (0.400) 3 (0.300) 0.000
testMethod10 (0.500) 2 (0.300) 4 (0.600) 2 (0.500) 1 (0.200) 1 (0.200) 5 (0.400) 3 (0.300) 0.000
- - + + \ No newline at end of file diff --git a/tests/Unit/Business/Cognitive/Report/HtmlReporterContent_SingleTable.html b/tests/Unit/Business/Cognitive/Report/HtmlReporterContent_SingleTable.html new file mode 100644 index 0000000..597c46d --- /dev/null +++ b/tests/Unit/Business/Cognitive/Report/HtmlReporterContent_SingleTable.html @@ -0,0 +1,53 @@ + + + + + + Cognitive Complexity Metrics Report + + + +
+

Cognitive Metrics Report - 2023-10-01 12:00:00

+

+ This report contains the cognitive complexity metrics for the analyzed code in 2 classes. +

+

All Methods (3 total)

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ClassMethodLine CountArgument CountIf CountIf Nesting LevelElse CountReturn CountVariable CountProperty Call CountCombined Cognitive Complexity
TestClasstestMethod10 (0.500) 2 (0.300) 4 (0.600) 2 (0.500) 1 (0.200) 1 (0.200) 5 (0.400) 3 (0.300) 0.300
TestClassanotherMethod5 (0.200) 1 (0.100) 1 (0.200) 1 (0.100) 0 (0.000) 1 (0.100) 2 (0.100) 1 (0.100) 0.050
AnotherClasscomplexMethod20 (1.000) 4 (0.600) 8 (1.200) 3 (1.000) 4 (0.600) 3 (0.500) 10 (0.800) 5 (0.500) 0.800
+
+ + + \ No newline at end of file diff --git a/tests/Unit/Business/Cognitive/Report/HtmlReporterTest.php b/tests/Unit/Business/Cognitive/Report/HtmlReporterTest.php index 254ad35..908f55d 100644 --- a/tests/Unit/Business/Cognitive/Report/HtmlReporterTest.php +++ b/tests/Unit/Business/Cognitive/Report/HtmlReporterTest.php @@ -8,19 +8,21 @@ use Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\CognitiveMetricsCollection; use Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\Report\HtmlReport; use Phauthentic\CognitiveCodeAnalysis\Business\Utility\Datetime; +use Phauthentic\CognitiveCodeAnalysis\Config\ConfigLoader; +use Phauthentic\CognitiveCodeAnalysis\Config\ConfigService; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; +use Symfony\Component\Config\Definition\Processor; class HtmlReporterTest extends TestCase { - private HtmlReport $csvExporter; private string $filename; protected function setUp(): void { parent::setUp(); - $this->csvExporter = new HtmlReport(); - $this->filename = sys_get_temp_dir() . '/test_metrics.csv'; + $this->filename = sys_get_temp_dir() . '/test_metrics.html'; Datetime::$fixedDate = '2023-10-01 12:00:00'; } @@ -30,14 +32,79 @@ protected function tearDown(): void if (file_exists($this->filename)) { unlink($this->filename); } - DateTime::$fixedDate = null; + Datetime::$fixedDate = null; + } + + /** + * @return array + */ + public static function configurationProvider(): array + { + return [ + 'Grouped by class' => ['all-metrics-config.yml', 'HtmlReporterContent.html', true], + 'Single table' => ['single-table-config.yml', 'HtmlReporterContent_SingleTable.html', false], + ]; } #[Test] - public function testExportCreatesFile(): void + #[DataProvider('configurationProvider')] + public function testExportWithConfiguration(string $configFile, string $expectedFile, bool $useSimpleMetrics): void + { + $config = $this->loadConfig($configFile); + $exporter = new HtmlReport($config); + $metricsCollection = $useSimpleMetrics + ? $this->createSimpleMetricsCollection() + : $this->createMultiClassMetricsCollection(); + + $exporter->export($metricsCollection, $this->filename); + + $this->assertFileEquals(__DIR__ . '/' . $expectedFile, $this->filename); + } + + private function loadConfig(string $configFile): \Phauthentic\CognitiveCodeAnalysis\Config\CognitiveConfig + { + $configService = new ConfigService( + new Processor(), + new ConfigLoader() + ); + $configService->loadConfig(__DIR__ . '/../../../../Fixtures/' . $configFile); + + return $configService->getConfig(); + } + + private function createSimpleMetricsCollection(): CognitiveMetricsCollection + { + $metricsCollection = new CognitiveMetricsCollection(); + $metricsCollection->add(new CognitiveMetrics([ + 'class' => 'TestClass', + 'method' => 'testMethod', + 'file' => 'TestClass.php', + 'lineCount' => 10, + 'argCount' => 2, + 'returnCount' => 1, + 'variableCount' => 5, + 'propertyCallCount' => 3, + 'ifCount' => 4, + 'ifNestingLevel' => 2, + 'elseCount' => 1, + 'lineCountWeight' => 0.5, + 'argCountWeight' => 0.3, + 'returnCountWeight' => 0.2, + 'variableCountWeight' => 0.4, + 'propertyCallCountWeight' => 0.3, + 'ifCountWeight' => 0.6, + 'ifNestingLevelWeight' => 0.5, + 'elseCountWeight' => 0.2, + ])); + + return $metricsCollection; + } + + private function createMultiClassMetricsCollection(): CognitiveMetricsCollection { $metricsCollection = new CognitiveMetricsCollection(); - $metrics = new CognitiveMetrics([ + + $metrics1 = new CognitiveMetrics([ 'class' => 'TestClass', 'method' => 'testMethod', 'file' => 'TestClass.php', @@ -58,14 +125,57 @@ public function testExportCreatesFile(): void 'ifNestingLevelWeight' => 0.5, 'elseCountWeight' => 0.2, ]); + $metrics1->setScore(0.3); + $metricsCollection->add($metrics1); - $metricsCollection->add($metrics); + $metrics2 = new CognitiveMetrics([ + 'class' => 'TestClass', + 'method' => 'anotherMethod', + 'file' => 'TestClass.php', + 'lineCount' => 5, + 'argCount' => 1, + 'returnCount' => 1, + 'variableCount' => 2, + 'propertyCallCount' => 1, + 'ifCount' => 1, + 'ifNestingLevel' => 1, + 'elseCount' => 0, + 'lineCountWeight' => 0.2, + 'argCountWeight' => 0.1, + 'returnCountWeight' => 0.1, + 'variableCountWeight' => 0.1, + 'propertyCallCountWeight' => 0.1, + 'ifCountWeight' => 0.2, + 'ifNestingLevelWeight' => 0.1, + 'elseCountWeight' => 0.0, + ]); + $metrics2->setScore(0.05); + $metricsCollection->add($metrics2); - $this->csvExporter->export($metricsCollection, $this->filename); + $metrics3 = new CognitiveMetrics([ + 'class' => 'AnotherClass', + 'method' => 'complexMethod', + 'file' => 'AnotherClass.php', + 'lineCount' => 20, + 'argCount' => 4, + 'returnCount' => 3, + 'variableCount' => 10, + 'propertyCallCount' => 5, + 'ifCount' => 8, + 'ifNestingLevel' => 3, + 'elseCount' => 4, + 'lineCountWeight' => 1.0, + 'argCountWeight' => 0.6, + 'returnCountWeight' => 0.5, + 'variableCountWeight' => 0.8, + 'propertyCallCountWeight' => 0.5, + 'ifCountWeight' => 1.2, + 'ifNestingLevelWeight' => 1.0, + 'elseCountWeight' => 0.6, + ]); + $metrics3->setScore(0.8); + $metricsCollection->add($metrics3); - $this->assertFileEquals( - __DIR__ . '/HtmlReporterContent.html', - $this->filename - ); + return $metricsCollection; } } diff --git a/tests/Unit/Business/Cognitive/Report/MarkdownReporterContent_SingleTableStreaming.md b/tests/Unit/Business/Cognitive/Report/MarkdownReporterContent_SingleTableStreaming.md new file mode 100644 index 0000000..43e6c1c --- /dev/null +++ b/tests/Unit/Business/Cognitive/Report/MarkdownReporterContent_SingleTableStreaming.md @@ -0,0 +1,13 @@ +# Cognitive Metrics Report + +**Generated:** 2023-10-01 12:00:00 + +--- + +## All Methods + +| Class | Method | Lines | Args | Returns | Variables | Property Calls | If | If Nesting | Else | Cognitive Complexity | Halstead Volume | Halstead Difficulty | Halstead Effort | Cyclomatic Complexity | +|--------|--------|--------|--------|--------|--------|--------|--------|--------|--------|--------|--------|--------|--------|--------| +| TestClass | testMethod | 10 (0.500) | 2 (0.300) | 1 (0.200) | 5 (0.400) | 3 (0.300) | 4 (0.600) | 2 (0.500) | 1 (0.200) | 0.300 | 573.211 | 12.500 | 7,165.138 | 5 (low) | +| TestClass | anotherMethod | 5 (0.200) | 1 (0.100) | 1 (0.100) | 2 (0.100) | 1 (0.100) | 1 (0.200) | 1 (0.100) | 0 (0.000) | 0.050 | 185.470 | 6.250 | 1,159.188 | 2 (low) | +| AnotherClass | complexMethod | 20 (1.000) | 4 (0.600) | 3 (0.500) | 10 (0.800) | 5 (0.500) | 8 (1.200) | 3 (1.000) | 4 (0.600) | 0.800 | 1,357.824 | 25.000 | 33,945.600 | 12 (medium) | diff --git a/tests/Unit/Business/Cognitive/Report/MarkdownReporterTest.php b/tests/Unit/Business/Cognitive/Report/MarkdownReporterTest.php index 263d49c..5576578 100644 --- a/tests/Unit/Business/Cognitive/Report/MarkdownReporterTest.php +++ b/tests/Unit/Business/Cognitive/Report/MarkdownReporterTest.php @@ -72,6 +72,28 @@ public function testExportWithConfiguration(string $configFile, string $expected ); } + #[Test] + public function testStreamingExportWithSingleTableConfiguration(): void + { + $configService = new ConfigService( + new Processor(), + new ConfigLoader() + ); + $configService->loadConfig(__DIR__ . '/../../../../Fixtures/single-table-config.yml'); + $config = $configService->getConfig(); + + $metricsCollection = $this->createTestMetricsCollection(); + $exporter = new MarkdownReport($config); + $exporter->startReport($this->filename); + $exporter->writeMetricBatch($metricsCollection); + $exporter->finalizeReport(); + + $this->assertFileEquals( + __DIR__ . '/MarkdownReporterContent_SingleTableStreaming.md', + $this->filename + ); + } + private function createTestMetricsCollection(): CognitiveMetricsCollection { $metricsCollection = new CognitiveMetricsCollection(); From 7c0187a333f21a0e9b380186a21b3d89df614fb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Sat, 23 May 2026 17:22:41 +0200 Subject: [PATCH 2/4] Refactor MarkdownReport to streamline file handling - Updated file handling in MarkdownReport to use a local variable for the file handle, improving clarity and reducing potential errors. - Removed unnecessary type assertions and adjusted docblocks for better type hinting. - Ensured consistent usage of the file handle across methods for writing report data. --- .../Cognitive/Report/MarkdownReport.php | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/src/Business/Cognitive/Report/MarkdownReport.php b/src/Business/Cognitive/Report/MarkdownReport.php index ab16f71..1b8a408 100644 --- a/src/Business/Cognitive/Report/MarkdownReport.php +++ b/src/Business/Cognitive/Report/MarkdownReport.php @@ -20,7 +20,7 @@ class MarkdownReport implements ReportGeneratorInterface, StreamableReportInterf use MarkdownFormatterTrait; private CognitiveConfig $config; - /** @var resource|false|null */ + /** @var resource|null */ private $fileHandle = null; private bool $isStreaming = false; /** @var array */ @@ -428,11 +428,12 @@ private function generateSingleTableMarkdown(CognitiveMetricsCollection $metrics */ public function startReport(string $filename): void { - $this->fileHandle = fopen($filename, 'wb'); - if ($this->fileHandle === false) { + $fileHandle = fopen($filename, 'wb'); + if ($fileHandle === false) { throw new CognitiveAnalysisException(sprintf('Could not open file %s for writing', $filename)); } + $this->fileHandle = $fileHandle; $this->isStreaming = true; $this->processedClasses = []; @@ -453,7 +454,7 @@ public function startReport(string $filename): void $header .= $this->buildTableSeparatorWithClass() . "\n"; } - fwrite($this->fileHandle, $header); + fwrite($fileHandle, $header); } /** @@ -461,15 +462,13 @@ public function startReport(string $filename): void */ public function writeMetricBatch(CognitiveMetricsCollection $batch): void { - if (!$this->isStreaming || $this->fileHandle === null) { + $fileHandle = $this->fileHandle; + if (!$this->isStreaming || $fileHandle === null) { throw new CognitiveAnalysisException('Streaming not started. Call startReport() first.'); } - // Type guard: fileHandle is guaranteed to be resource at this point - assert($this->fileHandle !== false); - if (!$this->config->groupByClass) { - $this->writeSingleTableBatch($batch); + $this->writeSingleTableBatch($batch, $fileHandle); return; } @@ -513,14 +512,15 @@ public function writeMetricBatch(CognitiveMetricsCollection $batch): void $classSection .= "\n---\n\n"; - fwrite($this->fileHandle, $classSection); + fwrite($fileHandle, $classSection); } } - private function writeSingleTableBatch(CognitiveMetricsCollection $batch): void + /** + * @param resource $fileHandle + */ + private function writeSingleTableBatch(CognitiveMetricsCollection $batch, $fileHandle): void { - assert($this->fileHandle !== false); - $filteredMetrics = $this->filterMetrics($batch); $rows = ''; @@ -532,7 +532,7 @@ private function writeSingleTableBatch(CognitiveMetricsCollection $batch): void return; } - fwrite($this->fileHandle, $rows); + fwrite($fileHandle, $rows); } /** @@ -540,14 +540,12 @@ private function writeSingleTableBatch(CognitiveMetricsCollection $batch): void */ public function finalizeReport(): void { - if (!$this->isStreaming || $this->fileHandle === null) { + $fileHandle = $this->fileHandle; + if (!$this->isStreaming || $fileHandle === null) { throw new CognitiveAnalysisException('Streaming not started or file handle not available.'); } - // Type guard: fileHandle is guaranteed to be resource at this point - assert($this->fileHandle !== false); - - fclose($this->fileHandle); + fclose($fileHandle); $this->isStreaming = false; $this->fileHandle = null; From d7fb946f4aa7da785dda69771ac6fb6e12c7df64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Sat, 23 May 2026 17:24:50 +0200 Subject: [PATCH 3/4] Refactor MarkdownReport to improve class section handling - Introduced a new method `writeGroupedByClassBatch` to encapsulate the logic for writing class sections to the report. - Created a dedicated `buildClassSection` method to streamline the construction of class report sections, enhancing code readability and maintainability. - Removed redundant code and improved the flow of data writing to the file handle. --- .../Cognitive/Report/MarkdownReport.php | 52 +++++++++++-------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/src/Business/Cognitive/Report/MarkdownReport.php b/src/Business/Cognitive/Report/MarkdownReport.php index 1b8a408..1447fbf 100644 --- a/src/Business/Cognitive/Report/MarkdownReport.php +++ b/src/Business/Cognitive/Report/MarkdownReport.php @@ -472,6 +472,14 @@ public function writeMetricBatch(CognitiveMetricsCollection $batch): void return; } + $this->writeGroupedByClassBatch($batch, $fileHandle); + } + + /** + * @param resource $fileHandle + */ + private function writeGroupedByClassBatch(CognitiveMetricsCollection $batch, $fileHandle): void + { $groupedByClass = $batch->groupBy('class'); foreach ($groupedByClass as $class => $methods) { @@ -481,39 +489,37 @@ public function writeMetricBatch(CognitiveMetricsCollection $batch): void continue; } - // Check if we've already processed this class if (in_array($class, $this->processedClasses, true)) { continue; } $this->processedClasses[] = $class; - // Get file path from first method in the collection - $firstMethod = null; - foreach ($filteredMethods as $method) { - $firstMethod = $method; - break; - } - - $classSection = "* **Class:** " . $this->escape((string)$class) . "\n"; - if ($firstMethod !== null) { - $classSection .= "* **File:** " . $this->escape($firstMethod->getFileName()) . "\n"; - } - $classSection .= "\n"; - - // Table header and separator - $classSection .= $this->buildTableHeader() . "\n"; - $classSection .= $this->buildTableSeparator() . "\n"; + fwrite($fileHandle, $this->buildClassSection((string) $class, $filteredMethods)); + } + } - // Table rows - foreach ($filteredMethods as $data) { - $classSection .= $this->buildTableRow($data) . "\n"; - } + private function buildClassSection(string $class, CognitiveMetricsCollection $filteredMethods): string + { + $firstMethod = null; + foreach ($filteredMethods as $method) { + $firstMethod = $method; + break; + } - $classSection .= "\n---\n\n"; + $classSection = "* **Class:** " . $this->escape($class) . "\n"; + if ($firstMethod !== null) { + $classSection .= "* **File:** " . $this->escape($firstMethod->getFileName()) . "\n"; + } + $classSection .= "\n"; + $classSection .= $this->buildTableHeader() . "\n"; + $classSection .= $this->buildTableSeparator() . "\n"; - fwrite($fileHandle, $classSection); + foreach ($filteredMethods as $data) { + $classSection .= $this->buildTableRow($data) . "\n"; } + + return $classSection . "\n---\n\n"; } /** From 8d26c41bd491461a134adb3367da8f00d2eb0ad1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Sat, 23 May 2026 17:24:56 +0200 Subject: [PATCH 4/4] Update Dockerfile to use PHP 8.5-cli - Changed base image from php:8.3-cli to php:8.5-cli to leverage new features and improvements in PHP 8.5. - Updated package installation to ensure compatibility with the new PHP version. --- docker/php/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/php/Dockerfile b/docker/php/Dockerfile index d6bcacf..fca68de 100644 --- a/docker/php/Dockerfile +++ b/docker/php/Dockerfile @@ -1,4 +1,4 @@ -FROM php:8.3-cli +FROM php:8.5-cli RUN apt-get update \ && apt-get install -y libzip-dev zip git wget gpg \