Skip to content

Commit b037229

Browse files
Implementing the sorting feature
1 parent 4f7c51a commit b037229

27 files changed

+2303
-297
lines changed

config.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ cognitive:
55
showOnlyMethodsExceedingThreshold: false
66
showHalsteadComplexity: false
77
showCyclomaticComplexity: false
8+
groupByClass: true
89
metrics:
910
lineCount:
1011
threshold: 60

src/Application.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use Phauthentic\CognitiveCodeAnalysis\Business\Churn\ChurnCalculator;
99
use Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\Baseline;
1010
use Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\CognitiveMetricsCollector;
11+
use Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\CognitiveMetricsSorter;
1112
use Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\Events\FileProcessed;
1213
use Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\Events\ParserFailed;
1314
use Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\Events\SourceFilesFound;
@@ -97,6 +98,9 @@ private function registerServices(): void
9798
$this->containerBuilder->register(Baseline::class, Baseline::class)
9899
->setPublic(true);
99100

101+
$this->containerBuilder->register(CognitiveMetricsSorter::class, CognitiveMetricsSorter::class)
102+
->setPublic(true);
103+
100104
$this->containerBuilder->register(Processor::class, Processor::class)
101105
->setPublic(true);
102106

@@ -218,6 +222,7 @@ private function registerCommands(): void
218222
new Reference(CognitiveMetricTextRendererInterface::class),
219223
new Reference(Baseline::class),
220224
new Reference(CognitiveMetricsReportHandler::class),
225+
new Reference(CognitiveMetricsSorter::class),
221226
])
222227
->setPublic(true);
223228

src/Business/Cognitive/CognitiveMetricsCollection.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@
1010
use InvalidArgumentException;
1111
use IteratorAggregate;
1212
use JsonSerializable;
13+
use Traversable;
1314

1415
/**
1516
* CognitiveMetricsCollection class
1617
*
17-
* @implements IteratorAggregate<int, CognitiveMetrics>
18+
* @implements IteratorAggregate<string, CognitiveMetrics>
1819
*/
1920
class CognitiveMetricsCollection implements IteratorAggregate, Countable, JsonSerializable
2021
{
@@ -51,9 +52,10 @@ public function filter(Closure $callback): self
5152
/**
5253
* Get an iterator for the collection
5354
*
54-
* @return ArrayIterator<int, CognitiveMetrics>
55+
* @return Traversable<string, CognitiveMetrics>
5556
*/
56-
public function getIterator(): ArrayIterator
57+
#[\ReturnTypeWillChange]
58+
public function getIterator(): Traversable
5759
{
5860
return new ArrayIterator($this->metrics);
5961
}

src/Business/Cognitive/CognitiveMetricsCollector.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@
2121
*/
2222
class CognitiveMetricsCollector
2323
{
24+
/**
25+
* @var array<string, array<string, string>>|null Cached ignored items from the last parsing operation
26+
*/
27+
private ?array $ignoredItems = null;
28+
2429
public function __construct(
2530
protected readonly Parser $parser,
2631
protected readonly DirectoryScanner $directoryScanner,
@@ -82,6 +87,9 @@ private function findMetrics(iterable $files): CognitiveMetricsCollection
8287
$metrics = $this->parser->parse(
8388
$this->getCodeFromFile($file)
8489
);
90+
91+
// Store ignored items from the parser
92+
$this->ignoredItems = $this->parser->getIgnored();
8593
} catch (Throwable $exception) {
8694
$this->messageBus->dispatch(new ParserFailed(
8795
$file,
@@ -162,4 +170,34 @@ private function findSourceFiles(string $path, array $exclude = []): iterable
162170
{
163171
return $this->directoryScanner->scan([$path], ['^(?!.*\.php$).+'] + $exclude);
164172
}
173+
174+
/**
175+
* Get all ignored classes and methods from the last parsing operation.
176+
*
177+
* @return array<string, array<string, string>> Array with 'classes' and 'methods' keys
178+
*/
179+
public function getIgnored(): array
180+
{
181+
return $this->ignoredItems ?? ['classes' => [], 'methods' => []];
182+
}
183+
184+
/**
185+
* Get ignored classes from the last parsing operation.
186+
*
187+
* @return array<string, string> Array of ignored class FQCNs
188+
*/
189+
public function getIgnoredClasses(): array
190+
{
191+
return $this->ignoredItems['classes'] ?? [];
192+
}
193+
194+
/**
195+
* Get ignored methods from the last parsing operation.
196+
*
197+
* @return array<string, string> Array of ignored method keys (ClassName::methodName)
198+
*/
199+
public function getIgnoredMethods(): array
200+
{
201+
return $this->ignoredItems['methods'] ?? [];
202+
}
165203
}
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Phauthentic\CognitiveCodeAnalysis\Business\Cognitive;
6+
7+
use InvalidArgumentException;
8+
9+
/**
10+
* Service class for sorting CognitiveMetricsCollection
11+
*/
12+
class CognitiveMetricsSorter
13+
{
14+
/**
15+
* Available sortable fields
16+
*/
17+
private const SORTABLE_FIELDS = [
18+
'score',
19+
'halstead',
20+
'cyclomatic',
21+
'class',
22+
'method',
23+
'file',
24+
'lineCount',
25+
'argCount',
26+
'returnCount',
27+
'variableCount',
28+
'propertyCallCount',
29+
'ifCount',
30+
'ifNestingLevel',
31+
'elseCount',
32+
'lineCountWeight',
33+
'argCountWeight',
34+
'returnCountWeight',
35+
'variableCountWeight',
36+
'propertyCallCountWeight',
37+
'ifCountWeight',
38+
'ifNestingLevelWeight',
39+
'elseCountWeight'
40+
];
41+
42+
/**
43+
* Sort the metrics collection by the specified field and order
44+
*
45+
* @param CognitiveMetricsCollection $collection
46+
* @param string $sortBy
47+
* @param string $sortOrder
48+
* @return CognitiveMetricsCollection
49+
* @throws InvalidArgumentException
50+
*/
51+
public function sort(
52+
CognitiveMetricsCollection $collection,
53+
string $sortBy,
54+
string $sortOrder = 'asc'
55+
): CognitiveMetricsCollection {
56+
if (!in_array($sortBy, self::SORTABLE_FIELDS, true)) {
57+
throw new InvalidArgumentException(
58+
sprintf(
59+
'Invalid sort field "%s". Available fields: %s',
60+
$sortBy,
61+
implode(', ', self::SORTABLE_FIELDS)
62+
)
63+
);
64+
}
65+
66+
if (!in_array(strtolower($sortOrder), ['asc', 'desc'], true)) {
67+
throw new InvalidArgumentException('Sort order must be "asc" or "desc"');
68+
}
69+
70+
$metrics = iterator_to_array($collection, true);
71+
72+
// Convert to indexed array for sorting
73+
$metricsArray = array_values($metrics);
74+
75+
// Sort by values
76+
usort($metricsArray, function (CognitiveMetrics $alpha, CognitiveMetrics $beta) use ($sortBy, $sortOrder) {
77+
$valueA = $this->getFieldValue($alpha, $sortBy);
78+
$valueB = $this->getFieldValue($beta, $sortBy);
79+
80+
$comparison = $this->compareValues($valueA, $valueB);
81+
82+
return strtolower($sortOrder) === 'desc' ? -$comparison : $comparison;
83+
});
84+
85+
$sortedCollection = new CognitiveMetricsCollection();
86+
foreach ($metricsArray as $metric) {
87+
$sortedCollection->add($metric);
88+
}
89+
90+
return $sortedCollection;
91+
}
92+
93+
/**
94+
* Get the value of a field from a CognitiveMetrics object
95+
*
96+
* @param CognitiveMetrics $metrics
97+
* @param string $field
98+
* @return mixed
99+
*/
100+
private function getFieldValue(CognitiveMetrics $metrics, string $field): mixed
101+
{
102+
return match ($field) {
103+
'score' => $metrics->getScore(),
104+
'halstead' => $metrics->getHalstead()?->getVolume() ?? 0.0,
105+
'cyclomatic' => $metrics->getCyclomatic()?->complexity ?? 0, // @phpstan-ignore-line
106+
'class' => $metrics->getClass(),
107+
'method' => $metrics->getMethod(),
108+
'file' => $metrics->getFileName(),
109+
'lineCount' => $metrics->getLineCount(),
110+
'argCount' => $metrics->getArgCount(),
111+
'returnCount' => $metrics->getReturnCount(),
112+
'variableCount' => $metrics->getVariableCount(),
113+
'propertyCallCount' => $metrics->getPropertyCallCount(),
114+
'ifCount' => $metrics->getIfCount(),
115+
'ifNestingLevel' => $metrics->getIfNestingLevel(),
116+
'elseCount' => $metrics->getElseCount(),
117+
'lineCountWeight' => $metrics->getLineCountWeight(),
118+
'argCountWeight' => $metrics->getArgCountWeight(),
119+
'returnCountWeight' => $metrics->getReturnCountWeight(),
120+
'variableCountWeight' => $metrics->getVariableCountWeight(),
121+
'propertyCallCountWeight' => $metrics->getPropertyCallCountWeight(),
122+
'ifCountWeight' => $metrics->getIfCountWeight(),
123+
'ifNestingLevelWeight' => $metrics->getIfNestingLevelWeight(),
124+
'elseCountWeight' => $metrics->getElseCountWeight(),
125+
default => throw new InvalidArgumentException("Unknown field: $field")
126+
};
127+
}
128+
129+
/**
130+
* Compare two values for sorting
131+
*
132+
* @param mixed $alpha
133+
* @param mixed $beta
134+
* @return int
135+
*/
136+
private function compareValues(mixed $alpha, mixed $beta): int
137+
{
138+
if (is_numeric($alpha) && is_numeric($beta)) {
139+
return $alpha <=> $beta;
140+
}
141+
142+
if (is_string($alpha) && is_string($beta)) {
143+
return strcasecmp($alpha, $beta);
144+
}
145+
146+
// Handle mixed types by converting to string
147+
return strcasecmp((string) $alpha, (string) $beta);
148+
}
149+
150+
/**
151+
* Get all available sortable fields
152+
*
153+
* @return array<string>
154+
*/
155+
public function getSortableFields(): array
156+
{
157+
return self::SORTABLE_FIELDS;
158+
}
159+
}

0 commit comments

Comments
 (0)