diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index dc3cd9c..af88d14 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -69,3 +69,6 @@ jobs:
- name: Run phpstan
run: bin/phpstan -V && bin/phpstan --error-format=github
+
+ - name: Run phpmd
+ run: bin/phpmd --version && composer phpmd
diff --git a/composer.json b/composer.json
index 4f2524b..b097c41 100644
--- a/composer.json
+++ b/composer.json
@@ -8,7 +8,8 @@
"symfony/console": "^7.1",
"symfony/config": "^7.1",
"symfony/yaml": "^7.1",
- "symfony/dependency-injection": "^7.1"
+ "symfony/dependency-injection": "^7.1",
+ "symfony/messenger": "^7.1"
},
"autoload": {
"psr-4": {
@@ -69,7 +70,7 @@
"phpstan analyse src/"
],
"phpmd": [
- "bin/phpmd ./src text cleancode,codesize,controversial,design"
+ "bin/phpmd ./src/ text phpmd.xml"
],
"benchmark": [
"bin/phpbench run tests/Benchmark/ --report=aggregate"
@@ -77,6 +78,7 @@
"all": [
"@cscheck",
"@analyze",
+ "@phpmd",
"@test"
],
"build-phar": [
diff --git a/composer.lock b/composer.lock
index 08d3626..8719206 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "66b078c5c8d9a33a83c5ad72ce006f9b",
+ "content-hash": "b911dc6cd3703e4851a12882d818361c",
"packages": [
{
"name": "nikic/php-parser",
@@ -64,6 +64,54 @@
},
"time": "2024-09-15T16:40:33+00:00"
},
+ {
+ "name": "psr/clock",
+ "version": "1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/clock.git",
+ "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d",
+ "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0 || ^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Psr\\Clock\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for reading the clock.",
+ "homepage": "https://github.com/php-fig/clock",
+ "keywords": [
+ "clock",
+ "now",
+ "psr",
+ "psr-20",
+ "time"
+ ],
+ "support": {
+ "issues": "https://github.com/php-fig/clock/issues",
+ "source": "https://github.com/php-fig/clock/tree/1.0.0"
+ },
+ "time": "2022-11-25T14:36:26+00:00"
+ },
{
"name": "psr/container",
"version": "2.0.2",
@@ -117,6 +165,130 @@
},
"time": "2021-11-05T16:47:00+00:00"
},
+ {
+ "name": "psr/log",
+ "version": "3.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/log.git",
+ "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
+ "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.0.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Log\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for logging libraries",
+ "homepage": "https://github.com/php-fig/log",
+ "keywords": [
+ "log",
+ "psr",
+ "psr-3"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/log/tree/3.0.2"
+ },
+ "time": "2024-09-11T13:17:53+00:00"
+ },
+ {
+ "name": "symfony/clock",
+ "version": "v7.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/clock.git",
+ "reference": "3dfc8b084853586de51dd1441c6242c76a28cbe7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/clock/zipball/3dfc8b084853586de51dd1441c6242c76a28cbe7",
+ "reference": "3dfc8b084853586de51dd1441c6242c76a28cbe7",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "psr/clock": "^1.0",
+ "symfony/polyfill-php83": "^1.28"
+ },
+ "provide": {
+ "psr/clock-implementation": "1.0"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "Resources/now.php"
+ ],
+ "psr-4": {
+ "Symfony\\Component\\Clock\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Decouples applications from the system clock",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "clock",
+ "psr20",
+ "time"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/clock/tree/v7.1.1"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-05-31T14:57:53+00:00"
+ },
{
"name": "symfony/config",
"version": "v7.1.1",
@@ -498,6 +670,92 @@
],
"time": "2024-09-17T09:16:35+00:00"
},
+ {
+ "name": "symfony/messenger",
+ "version": "v7.1.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/messenger.git",
+ "reference": "e1c0ced845e3dac12ab428c5ed42dbe7a58ca576"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/messenger/zipball/e1c0ced845e3dac12ab428c5ed42dbe7a58ca576",
+ "reference": "e1c0ced845e3dac12ab428c5ed42dbe7a58ca576",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "psr/log": "^1|^2|^3",
+ "symfony/clock": "^6.4|^7.0"
+ },
+ "conflict": {
+ "symfony/console": "<6.4",
+ "symfony/event-dispatcher": "<6.4",
+ "symfony/event-dispatcher-contracts": "<2.5",
+ "symfony/framework-bundle": "<6.4",
+ "symfony/http-kernel": "<6.4",
+ "symfony/serializer": "<6.4"
+ },
+ "require-dev": {
+ "psr/cache": "^1.0|^2.0|^3.0",
+ "symfony/console": "^6.4|^7.0",
+ "symfony/dependency-injection": "^6.4|^7.0",
+ "symfony/event-dispatcher": "^6.4|^7.0",
+ "symfony/http-kernel": "^6.4|^7.0",
+ "symfony/process": "^6.4|^7.0",
+ "symfony/property-access": "^6.4|^7.0",
+ "symfony/rate-limiter": "^6.4|^7.0",
+ "symfony/routing": "^6.4|^7.0",
+ "symfony/serializer": "^6.4|^7.0",
+ "symfony/service-contracts": "^2.5|^3",
+ "symfony/stopwatch": "^6.4|^7.0",
+ "symfony/validator": "^6.4|^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Messenger\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Samuel Roze",
+ "email": "samuel.roze@gmail.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Helps applications send and receive messages to/from other applications or via message queues",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/messenger/tree/v7.1.5"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-09-08T12:32:26+00:00"
+ },
{
"name": "symfony/polyfill-ctype",
"version": "v1.31.0",
@@ -816,6 +1074,82 @@
],
"time": "2024-09-09T11:45:10+00:00"
},
+ {
+ "name": "symfony/polyfill-php83",
+ "version": "v1.31.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-php83.git",
+ "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/2fb86d65e2d424369ad2905e83b236a8805ba491",
+ "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Php83\\": ""
+ },
+ "classmap": [
+ "Resources/stubs"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-php83/tree/v1.31.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-09-09T11:45:10+00:00"
+ },
{
"name": "symfony/service-contracts",
"version": "v3.5.0",
@@ -2738,56 +3072,6 @@
],
"time": "2024-09-19T10:54:28+00:00"
},
- {
- "name": "psr/log",
- "version": "3.0.2",
- "source": {
- "type": "git",
- "url": "https://github.com/php-fig/log.git",
- "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
- "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
- "shasum": ""
- },
- "require": {
- "php": ">=8.0.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "3.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Psr\\Log\\": "src"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "PHP-FIG",
- "homepage": "https://www.php-fig.org/"
- }
- ],
- "description": "Common interface for logging libraries",
- "homepage": "https://github.com/php-fig/log",
- "keywords": [
- "log",
- "psr",
- "psr-3"
- ],
- "support": {
- "source": "https://github.com/php-fig/log/tree/3.0.2"
- },
- "time": "2024-09-11T13:17:53+00:00"
- },
{
"name": "roave/security-advisories",
"version": "dev-latest",
diff --git a/config.yml b/config.yml
index 3e0731b..dd874fd 100644
--- a/config.yml
+++ b/config.yml
@@ -2,7 +2,7 @@ cognitive:
excludeFilePatterns:
excludePatterns:
scoreThreshold: 0.5
- showOnlyMethodsExceedingThreshold: false
+ showOnlyMethodsExceedingThreshold: true
metrics:
lineCount:
threshold: 60
@@ -17,11 +17,11 @@ cognitive:
scale: 5.0
enabled: true
variableCount:
- threshold: 2
+ threshold: 4
scale: 5.0
enabled: true
propertyCallCount:
- threshold: 2
+ threshold: 4
scale: 15.0
enabled: true
ifCount:
diff --git a/phpmd.xml b/phpmd.xml
new file mode 100644
index 0000000..5497422
--- /dev/null
+++ b/phpmd.xml
@@ -0,0 +1,21 @@
+
+
+
+ Phauthentic PHPMD rule set
+
+
+
+
+
+
+
+
+
+
+
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index aa1d602..ef59533 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -1,14 +1,32 @@
-
+
+
+
+
+
+
tests
- tests/Store/AbstractStoreTest.php
+
+
diff --git a/src/Application.php b/src/Application.php
index 1dc8f9f..90e410a 100644
--- a/src/Application.php
+++ b/src/Application.php
@@ -6,11 +6,15 @@
use Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\BaselineService;
use Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\CognitiveMetricsCollector;
+use Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\Events\FileProcessed;
+use Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\Events\SourceFilesFound;
+use Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\Parser;
use Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\ScoreCalculator;
use Phauthentic\CognitiveCodeAnalysis\Business\DirectoryScanner;
-use Phauthentic\CognitiveCodeAnalysis\Command\Cognitive\CognitiveCollectorShellOutputPlugin;
use Phauthentic\CognitiveCodeAnalysis\Command\CognitiveMetricsCommand;
use Phauthentic\CognitiveCodeAnalysis\Business\MetricsFacade;
+use Phauthentic\CognitiveCodeAnalysis\Command\EventHandler\ProgressBarHandler;
+use Phauthentic\CognitiveCodeAnalysis\Command\EventHandler\VerboseHandler;
use Phauthentic\CognitiveCodeAnalysis\Command\Presentation\CognitiveMetricTextRenderer;
use Phauthentic\CognitiveCodeAnalysis\Config\ConfigLoader;
use Phauthentic\CognitiveCodeAnalysis\Config\ConfigService;
@@ -22,9 +26,14 @@
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\ConsoleOutput;
+use Symfony\Component\Console\Output\NullOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\Messenger\Handler\HandlersLocator;
+use Symfony\Component\Messenger\MessageBus;
+use Symfony\Component\Messenger\MessageBusInterface;
+use Symfony\Component\Messenger\Middleware\HandleMessageMiddleware;
/**
*
@@ -77,16 +86,17 @@ private function registerServices(): void
$this->containerBuilder->register(NodeTraverserInterface::class, NodeTraverser::class)
->setPublic(true);
- $this->containerBuilder->register(OutputInterface::class, ConsoleOutput::class)
+ $outputClass = getenv('APP_ENV') === 'test' ? NullOutput::class : ConsoleOutput::class;
+ $this->containerBuilder->register(OutputInterface::class, $outputClass)
->setPublic(true);
$this->containerBuilder->register(InputInterface::class, ArgvInput::class)
->setPublic(true);
- $this->containerBuilder->register(CognitiveCollectorShellOutputPlugin::class, CognitiveCollectorShellOutputPlugin::class)
+ $this->containerBuilder->register(Parser::class, Parser::class)
->setArguments([
- new Reference(InputInterface::class),
- new Reference(OutputInterface::class)
+ new Reference(ParserFactory::class),
+ new Reference(NodeTraverserInterface::class),
])
->setPublic(true);
}
@@ -94,6 +104,7 @@ private function registerServices(): void
private function bootstrap(): void
{
$this->registerServices();
+ $this->configureEventBus();
$this->bootstrapMetricsCollectors();
$this->configureConfigService();
$this->registerMetricsFacade();
@@ -105,17 +116,44 @@ private function bootstrapMetricsCollectors(): void
{
$this->containerBuilder->register(CognitiveMetricsCollector::class, CognitiveMetricsCollector::class)
->setArguments([
- new Reference(ParserFactory::class),
- new Reference(NodeTraverserInterface::class),
+ new Reference(Parser::class),
new Reference(DirectoryScanner::class),
new Reference(ConfigService::class),
- [
- $this->containerBuilder->get(CognitiveCollectorShellOutputPlugin::class),
- ]
+ new Reference(MessageBusInterface::class)
])
->setPublic(true);
}
+ private function configureEventBus(): void
+ {
+ $progressbar = new ProgressBarHandler(
+ $this->get(OutputInterface::class)
+ );
+
+ $verbose = new VerboseHandler(
+ $this->get(InputInterface::class),
+ $this->get(OutputInterface::class)
+ );
+
+ // Set up event handlers locator
+ $handlersLocator = new HandlersLocator([
+ SourceFilesFound::class => [
+ $progressbar,
+ $verbose
+ ],
+ FileProcessed::class => [
+ $progressbar,
+ $verbose
+ ],
+ ]);
+
+ $messageBus = new MessageBus([
+ new HandleMessageMiddleware($handlersLocator),
+ ]);
+
+ $this->containerBuilder->set(MessageBusInterface::class, $messageBus);
+ }
+
private function configureConfigService(): void
{
$this->containerBuilder->register(ConfigService::class, ConfigService::class)
@@ -165,8 +203,13 @@ public function run(): void
);
}
- public function get(string $id): mixed
+ public function get(string $identifier): mixed
+ {
+ return $this->containerBuilder->get($identifier);
+ }
+
+ public function getContainer(): ContainerBuilder
{
- return $this->containerBuilder->get($id);
+ return $this->containerBuilder;
}
}
diff --git a/src/Business/Cognitive/BaselineService.php b/src/Business/Cognitive/BaselineService.php
index ee0d442..e2facc9 100644
--- a/src/Business/Cognitive/BaselineService.php
+++ b/src/Business/Cognitive/BaselineService.php
@@ -25,7 +25,7 @@ public function calculateDeltas(CognitiveMetricsCollection $metricsCollection, a
continue;
}
- $previousMetrics = CognitiveMetrics::fromArray($methodData);
+ $previousMetrics = new CognitiveMetrics($methodData);
$metrics->calculateDeltas($previousMetrics);
}
}
diff --git a/src/Business/Cognitive/CognitiveMetrics.php b/src/Business/Cognitive/CognitiveMetrics.php
index 6c30c43..e828515 100644
--- a/src/Business/Cognitive/CognitiveMetrics.php
+++ b/src/Business/Cognitive/CognitiveMetrics.php
@@ -8,7 +8,7 @@
use JsonSerializable;
/**
- *
+ * @SuppressWarnings(PHPMD)
*/
class CognitiveMetrics implements JsonSerializable
{
diff --git a/src/Business/Cognitive/CognitiveMetricsCollector.php b/src/Business/Cognitive/CognitiveMetricsCollector.php
index f974839..c2e73f6 100644
--- a/src/Business/Cognitive/CognitiveMetricsCollector.php
+++ b/src/Business/Cognitive/CognitiveMetricsCollector.php
@@ -4,35 +4,27 @@
namespace Phauthentic\CognitiveCodeAnalysis\Business\Cognitive;
+use Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\Events\FileProcessed;
+use Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\Events\SourceFilesFound;
use Phauthentic\CognitiveCodeAnalysis\Business\DirectoryScanner;
use Phauthentic\CognitiveCodeAnalysis\CognitiveAnalysisException;
use Phauthentic\CognitiveCodeAnalysis\Config\CognitiveConfig;
use Phauthentic\CognitiveCodeAnalysis\Config\ConfigService;
-use Phauthentic\CognitiveCodeAnalysis\PhpParser\CognitiveMetricsVisitor;
-use PhpParser\Error;
-use PhpParser\NodeTraverserInterface;
-use PhpParser\Parser;
-use PhpParser\ParserFactory;
use SplFileInfo;
+use Symfony\Component\Messenger\Exception\ExceptionInterface;
+use Symfony\Component\Messenger\MessageBusInterface;
/**
* CognitiveMetricsCollector class that collects cognitive metrics from source files
*/
class CognitiveMetricsCollector
{
- protected Parser $parser;
-
- /**
- * @param array $findMetricsPlugins
- */
public function __construct(
- protected readonly ParserFactory $parserFactory,
- protected readonly NodeTraverserInterface $traverser,
+ protected readonly Parser $parser,
protected readonly DirectoryScanner $directoryScanner,
protected readonly ConfigService $configService,
- protected readonly array $findMetricsPlugins = []
+ protected readonly MessageBusInterface $messageBus,
) {
- $this->parser = $parserFactory->createForHostVersion();
}
/**
@@ -41,13 +33,21 @@ public function __construct(
* @param string $path
* @param CognitiveConfig $config
* @return CognitiveMetricsCollection
- * @throws CognitiveAnalysisException
+ * @throws CognitiveAnalysisException|ExceptionInterface
*/
public function collect(string $path, CognitiveConfig $config): CognitiveMetricsCollection
{
$files = $this->findSourceFiles($path, $config->excludeFilePatterns);
- return $this->findMetrics($files);
+ /** @var SplFileInfo[] $clonedFiles */
+ $clonedFiles = [];
+ foreach ($files as $file) {
+ $clonedFiles[] = clone $file;
+ }
+
+ $this->messageBus->dispatch(new SourceFilesFound($clonedFiles));
+
+ return $this->findMetrics($clonedFiles);
}
private function getCodeFromFile(SplFileInfo $file): string
@@ -66,39 +66,25 @@ private function getCodeFromFile(SplFileInfo $file): string
*
* @param iterable $files
* @return CognitiveMetricsCollection
- * @throws CognitiveAnalysisException
+ * @throws CognitiveAnalysisException|ExceptionInterface
*/
private function findMetrics(iterable $files): CognitiveMetricsCollection
{
$metricsCollection = new CognitiveMetricsCollection();
- $visitor = new CognitiveMetricsVisitor();
-
- foreach ($this->findMetricsPlugins as $plugin) {
- $plugin->beforeIteration($files);
- }
foreach ($files as $file) {
- foreach ($this->findMetricsPlugins as $plugin) {
- $plugin->beforeFindMetrics($file);
- }
-
- $code = $this->getCodeFromFile($file);
-
- $this->traverser->addVisitor($visitor);
- $this->traverseAbstractSyntaxTree($code);
-
- $methodMetrics = $visitor->getMethodMetrics();
- $this->traverser->removeVisitor($visitor);
-
- $this->processMethodMetrics($methodMetrics, $metricsCollection);
-
- foreach ($this->findMetricsPlugins as $plugin) {
- $plugin->afterFindMetrics($file);
- }
- }
-
- foreach ($this->findMetricsPlugins as $plugin) {
- $plugin->afterIteration($metricsCollection);
+ $metrics = $this->parser->parse(
+ $this->getCodeFromFile($file)
+ );
+
+ $this->processMethodMetrics(
+ $metrics,
+ $metricsCollection
+ );
+
+ $this->messageBus->dispatch(new FileProcessed(
+ $file,
+ ));
}
return $metricsCollection;
@@ -139,7 +125,7 @@ private function isExcluded(string $classAndMethod): bool
$regexes = $this->configService->getConfig()->excludePatterns;
foreach ($regexes as $regex) {
- if (preg_match('/' . $regex . '/', $classAndMethod, $matches)) {
+ if (preg_match('/' . $regex . '/', $classAndMethod)) {
return true;
}
}
@@ -158,22 +144,4 @@ private function findSourceFiles(string $path, array $exclude = []): iterable
{
return $this->directoryScanner->scan([$path], ['^(?!.*\.php$).+'] + $exclude); // Exclude non-PHP files
}
-
- /**
- * @throws CognitiveAnalysisException
- */
- private function traverseAbstractSyntaxTree(string $code): void
- {
- try {
- $ast = $this->parser->parse($code);
- } catch (Error $e) {
- throw new CognitiveAnalysisException("Parse error: {$e->getMessage()}", 0, $e);
- }
-
- if ($ast === null) {
- throw new CognitiveAnalysisException("Could not parse the code.");
- }
-
- $this->traverser->traverse($ast);
- }
}
diff --git a/src/Business/Cognitive/Events/FileProcessed.php b/src/Business/Cognitive/Events/FileProcessed.php
new file mode 100644
index 0000000..1769dad
--- /dev/null
+++ b/src/Business/Cognitive/Events/FileProcessed.php
@@ -0,0 +1,16 @@
+ $files
+ */
+ public function __construct(
+ public readonly array $files
+ ) {
+ }
+}
diff --git a/src/Business/Cognitive/Parser.php b/src/Business/Cognitive/Parser.php
new file mode 100644
index 0000000..8dd8da5
--- /dev/null
+++ b/src/Business/Cognitive/Parser.php
@@ -0,0 +1,62 @@
+parser = $parserFactory->createForHostVersion();
+ $this->visitor = new CognitiveMetricsVisitor();
+ $this->traverser->addVisitor($this->visitor);
+ }
+
+ /**
+ * @return array>
+ * @throws CognitiveAnalysisException
+ */
+ public function parse(string $code): array
+ {
+ $this->traverseAbstractSyntaxTree($code);
+
+ $methodMetrics = $this->visitor->getMethodMetrics();
+ $this->visitor->resetValues();
+
+ return $methodMetrics;
+ }
+
+ /**
+ * @throws CognitiveAnalysisException
+ */
+ private function traverseAbstractSyntaxTree(string $code): void
+ {
+ try {
+ $ast = $this->parser->parse($code);
+ } catch (Error $e) {
+ throw new CognitiveAnalysisException("Parse error: {$e->getMessage()}", 0, $e);
+ }
+
+ if ($ast === null) {
+ throw new CognitiveAnalysisException("Could not parse the code.");
+ }
+
+ $this->traverser->traverse($ast);
+ }
+}
diff --git a/src/Command/Cognitive/CognitiveCollectorShellOutputPlugin.php b/src/Command/Cognitive/CognitiveCollectorShellOutputPlugin.php
deleted file mode 100644
index a516c3d..0000000
--- a/src/Command/Cognitive/CognitiveCollectorShellOutputPlugin.php
+++ /dev/null
@@ -1,76 +0,0 @@
-startTime = microtime(true);
- }
-
- public function afterFindMetrics(SplFileInfo $fileInfo): void
- {
- if (
- $this->input->hasOption(CognitiveMetricsCommand::OPTION_DEBUG)
- && $this->input->getOption(CognitiveMetricsCommand::OPTION_DEBUG) === false
- ) {
- return;
- }
-
- $runtime = microtime(true) - $this->startTime;
-
- $this->output->writeln('Processed ' . $fileInfo->getRealPath());
- $this->output->writeln('Number: ' . $this->count . ' Memory: ' . $this->formatBytes(memory_get_usage(true)) . ' -- Runtime: ' . round($runtime, 4) . 's');
-
- $this->count++;
- }
-
- public function beforeIteration(iterable $files): void
- {
- }
-
- public function afterIteration(CognitiveMetricsCollection $metricsCollection): void
- {
- }
-
- /**
- * Converts memory size to a human-readable format (bytes, KB, MB, GB, TB).
- *
- * @param int $size Memory size in bytes.
- * @return string Human-readable memory size.
- */
- private function formatBytes(int $size): string
- {
- $units = ['B', 'KB', 'MB', 'GB', 'TB'];
- $i = 0;
-
- while ($size >= 1024 && $i < count($units) - 1) {
- $size /= 1024;
- $i++;
- }
-
- return round($size, 2) . ' ' . $units[$i];
- }
-}
diff --git a/src/Command/CognitiveMetricsCommand.php b/src/Command/CognitiveMetricsCommand.php
index 77a0518..3c34356 100644
--- a/src/Command/CognitiveMetricsCommand.php
+++ b/src/Command/CognitiveMetricsCommand.php
@@ -13,7 +13,6 @@
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
-use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
diff --git a/src/Command/EventHandler/ProgressBarHandler.php b/src/Command/EventHandler/ProgressBarHandler.php
new file mode 100644
index 0000000..79b1e33
--- /dev/null
+++ b/src/Command/EventHandler/ProgressBarHandler.php
@@ -0,0 +1,45 @@
+totalFiles = count($event->files);
+ $this->output->writeln('Found ' . $this->totalFiles . ' files. Starting analysis.');
+ $this->progressBar = new ProgressBar($this->output, $this->totalFiles);
+ }
+
+ if ($event instanceof FileProcessed) {
+ $this->progressBar->advance(1);
+ $this->processedFiles++;
+ }
+
+ if ($this->processedFiles === $this->totalFiles) {
+ $this->progressBar->finish();
+ $this->output->writeln('');
+ $this->totalFiles = 0;
+ }
+ }
+}
diff --git a/src/Command/EventHandler/VerboseHandler.php b/src/Command/EventHandler/VerboseHandler.php
new file mode 100644
index 0000000..8d6a073
--- /dev/null
+++ b/src/Command/EventHandler/VerboseHandler.php
@@ -0,0 +1,65 @@
+input->hasOption(CognitiveMetricsCommand::OPTION_DEBUG)
+ && $this->input->getOption(CognitiveMetricsCommand::OPTION_DEBUG) === false
+ ) {
+ return;
+ }
+
+ if ($event instanceof SourceFilesFound) {
+ $this->startTime = microtime(true);
+ }
+
+ if ($event instanceof FileProcessed) {
+ $runtime = (microtime(true) - $this->startTime);
+
+ $this->output->writeln('Processed ' . $event->file->getRealPath());
+ $this->output->writeln(' Memory: ' . $this->formatBytes(memory_get_usage(true)) . ' || Total Time: ' . round($runtime, 4) . 's');
+ }
+ }
+
+ /**
+ * Converts memory size to a human-readable format (bytes, KB, MB, GB, TB).
+ *
+ * @param int $size Memory size in bytes.
+ * @return string Human-readable memory size.
+ */
+ private function formatBytes(int $size): string
+ {
+ $units = ['B', 'KB', 'MB', 'GB', 'TB'];
+ $index = 0;
+
+ while ($size >= 1024 && $index < count($units) - 1) {
+ $size /= 1024;
+ $index++;
+ }
+
+ return round($size, 2) . ' ' . $units[$index];
+ }
+}
diff --git a/src/Command/Presentation/CognitiveMetricTextRenderer.php b/src/Command/Presentation/CognitiveMetricTextRenderer.php
index ca169f5..1e9535a 100644
--- a/src/Command/Presentation/CognitiveMetricTextRenderer.php
+++ b/src/Command/Presentation/CognitiveMetricTextRenderer.php
@@ -21,6 +21,13 @@ public function __construct(
) {
}
+ public function metricExceedsThreshold(CognitiveMetrics $metric, CognitiveConfig $config): bool
+ {
+ return
+ $config->showOnlyMethodsExceedingThreshold &&
+ $metric->getScore() <= $config->scoreThreshold;
+ }
+
/**
* @param CognitiveMetricsCollection $metricsCollection
*/
@@ -31,10 +38,7 @@ public function render(CognitiveMetricsCollection $metricsCollection, CognitiveC
foreach ($groupedByClass as $className => $metrics) {
$rows = [];
foreach ($metrics as $metric) {
- if (
- $config->showOnlyMethodsExceedingThreshold &&
- $metric->getScore() <= $config->scoreThreshold
- ) {
+ if ($this->metricExceedsThreshold($metric, $config)) {
continue;
}
@@ -60,6 +64,7 @@ private function renderTable(string $className, array $rows): void
$table->setStyle('box');
$table->setHeaders($this->getTableHeaders());
$this->output->writeln("Class: $className");
+
$table->setRows($rows);
$table->render();
$this->output->writeln("");
@@ -68,7 +73,7 @@ private function renderTable(string $className, array $rows): void
/**
* @return string[]
*/
- protected function getTableHeaders(): array
+ private function getTableHeaders(): array
{
return [
"Method Name",
@@ -88,7 +93,7 @@ protected function getTableHeaders(): array
* @param CognitiveMetrics $metrics
* @return array
*/
- protected function prepareTableRow(CognitiveMetrics $metrics): array
+ private function prepareTableRow(CognitiveMetrics $metrics): array
{
$row = [
'methodName' => $metrics->getMethod(),
diff --git a/src/Config/ConfigService.php b/src/Config/ConfigService.php
index c9f409d..1d4472f 100644
--- a/src/Config/ConfigService.php
+++ b/src/Config/ConfigService.php
@@ -17,6 +17,9 @@ class ConfigService
*/
private array $config;
+ /**
+ * @SuppressWarnings(PHPMD.StaticAccess)
+ */
public function __construct(
private readonly Processor $processor,
private readonly ConfigLoader $configuration
@@ -26,6 +29,9 @@ public function __construct(
]);
}
+ /**
+ * @SuppressWarnings(PHPMD.StaticAccess)
+ */
public function loadConfig(string $configFilePath): void
{
$this->config = $this->processor->processConfiguration($this->configuration, [
diff --git a/tests/Unit/Business/Cognitive/CognitiveMetricsCollectorTest.php b/tests/Unit/Business/Cognitive/CognitiveMetricsCollectorTest.php
index dff86bd..918752d 100644
--- a/tests/Unit/Business/Cognitive/CognitiveMetricsCollectorTest.php
+++ b/tests/Unit/Business/Cognitive/CognitiveMetricsCollectorTest.php
@@ -4,17 +4,25 @@
namespace Phauthentic\CognitiveCodeAnalysis\Tests\Unit\Business\Cognitive;
-use Phauthentic\CognitiveCodeAnalysis\Business\AbstractMetricCollector;
+use Phauthentic\CognitiveCodeAnalysis\Application;
use Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\CognitiveMetricsCollection;
use Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\CognitiveMetricsCollector;
+use Phauthentic\CognitiveCodeAnalysis\Business\Cognitive\Parser;
use Phauthentic\CognitiveCodeAnalysis\Business\DirectoryScanner;
use Phauthentic\CognitiveCodeAnalysis\Config\ConfigLoader;
use Phauthentic\CognitiveCodeAnalysis\Config\ConfigService;
+use PHPMD\Console\OutputInterface;
use PhpParser\NodeTraverser;
use PhpParser\ParserFactory;
use PHPUnit\Framework\TestCase;
use RuntimeException;
use Symfony\Component\Config\Definition\Processor;
+use Symfony\Component\Console\Output\NullOutput;
+use Symfony\Component\Messenger\Envelope;
+use Symfony\Component\Messenger\Handler\HandlersLocator;
+use Symfony\Component\Messenger\MessageBus;
+use Symfony\Component\Messenger\MessageBusInterface;
+use Symfony\Component\Messenger\Middleware\HandleMessageMiddleware;
/**
*
@@ -23,18 +31,33 @@ class CognitiveMetricsCollectorTest extends TestCase
{
private CognitiveMetricsCollector $metricsCollector;
private ConfigService $configService;
+ private MessageBusInterface $messageBus;
protected function setUp(): void
{
parent::setUp();
+
+ $bus = $this->getMockBuilder(MessageBusInterface::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $bus->expects($this->any())
+ ->method('dispatch')
+ ->willReturn(new Envelope(new \stdClass()));
+
+ $this->messageBus = $bus;
+
$this->metricsCollector = new CognitiveMetricsCollector(
- new ParserFactory(),
- new NodeTraverser(),
+ new Parser(
+ new ParserFactory(),
+ new NodeTraverser(),
+ ),
new DirectoryScanner(),
new ConfigService(
new Processor(),
new ConfigLoader(),
- )
+ ),
+ $bus
);
$this->configService = new ConfigService(
@@ -64,10 +87,13 @@ public function testCollectWithExcludedClasses(): void
$configService->loadConfig(__DIR__ . '/../../../Fixtures/config-with-exclude-patterns.yml');
$metricsCollector = new CognitiveMetricsCollector(
- new ParserFactory(),
- new NodeTraverser(),
+ new Parser(
+ new ParserFactory(),
+ new NodeTraverser(),
+ ),
new DirectoryScanner(),
$configService,
+ $this->messageBus
);
$path = './tests/TestCode';
diff --git a/tests/Unit/Business/DirectoryScannerTest.php b/tests/Unit/Business/DirectoryScannerTest.php
index 649d734..fd058ef 100644
--- a/tests/Unit/Business/DirectoryScannerTest.php
+++ b/tests/Unit/Business/DirectoryScannerTest.php
@@ -2,7 +2,7 @@
declare(strict_types=1);
-namespace Phauthentic\CognitiveCodeAnalysis\Tests\Unit\Business\Halstead;
+namespace Phauthentic\CognitiveCodeAnalysis\Tests\Unit\Business;
use FilesystemIterator;
use Phauthentic\CognitiveCodeAnalysis\Business\DirectoryScanner;