Skip to content

Commit

Permalink
Add --json output (#19)
Browse files Browse the repository at this point in the history
* add --json to output data in json

* add json output
  • Loading branch information
TomasVotruba authored May 20, 2024
1 parent 0eb9bd7 commit 37ad1e7
Show file tree
Hide file tree
Showing 9 changed files with 203 additions and 40 deletions.
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,35 @@ To get errors count per level:

<br>

## Do you need a JSON format?

We got you covered:

```bash
vendor/bin/phpstan-bodyscan --json
```


```bash
[
{
"level": 0,
"error_count": 0
},
{
"level": 1,
"error_count": 5
},
{
"level": 2,
"error_count": 25
}
]
```

<br>

### Limit level count

Are you interested only in a few levels? You can limit ranges by the options:
Expand Down
14 changes: 13 additions & 1 deletion bin/phpstan-bodyscan.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Style\SymfonyStyle;
use TomasVotruba\PHPStanBodyscan\Command\RunCommand;
use TomasVotruba\PHPStanBodyscan\OutputFormatter\JsonOutputFormatter;
use TomasVotruba\PHPStanBodyscan\OutputFormatter\TableOutputFormatter;
use TomasVotruba\PHPStanBodyscan\PHPStanConfigFactory;
use TomasVotruba\PHPStanBodyscan\Process\AnalyseProcessFactory;

Expand All @@ -21,7 +23,17 @@

// 1. setup dependencies
$symfonyStyle = new SymfonyStyle(new ArrayInput([]), new ConsoleOutput());
$runCommand = new RunCommand($symfonyStyle, new AnalyseProcessFactory(), new PHPStanConfigFactory());

$jsonOutputFormatter = new JsonOutputFormatter($symfonyStyle);
$tableOutputFormatter = new TableOutputFormatter($symfonyStyle);

$runCommand = new RunCommand(
$symfonyStyle,
new AnalyseProcessFactory(),
new PHPStanConfigFactory(),
$jsonOutputFormatter,
$tableOutputFormatter
);

$application = new Application();
$application->add($runCommand);
Expand Down
3 changes: 0 additions & 3 deletions phpstan-bodyscan.neon

This file was deleted.

64 changes: 29 additions & 35 deletions src/Command/RunCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
namespace TomasVotruba\PHPStanBodyscan\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\TableStyle;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
Expand All @@ -14,19 +13,24 @@
use Symfony\Component\Process\Process;
use TomasVotruba\PHPStanBodyscan\Exception\AnalysisFailedException;
use TomasVotruba\PHPStanBodyscan\Logger;
use TomasVotruba\PHPStanBodyscan\OutputFormatter\JsonOutputFormatter;
use TomasVotruba\PHPStanBodyscan\OutputFormatter\TableOutputFormatter;
use TomasVotruba\PHPStanBodyscan\PHPStanConfigFactory;
use TomasVotruba\PHPStanBodyscan\Process\AnalyseProcessFactory;
use TomasVotruba\PHPStanBodyscan\Utils\ComposerLoader;
use TomasVotruba\PHPStanBodyscan\Utils\FileLoader;
use TomasVotruba\PHPStanBodyscan\Utils\JsonLoader;
use TomasVotruba\PHPStanBodyscan\ValueObject\PHPStanLevelResult;
use TomasVotruba\PHPStanBodyscan\ValueObject\BodyscanResult;
use TomasVotruba\PHPStanBodyscan\ValueObject\LevelResult;

final class RunCommand extends Command
{
public function __construct(
private readonly SymfonyStyle $symfonyStyle,
private readonly AnalyseProcessFactory $analyseProcessFactory,
private readonly PHPStanConfigFactory $phpStanConfigFactory
private readonly PHPStanConfigFactory $phpStanConfigFactory,
private readonly JsonOutputFormatter $jsonOutputFormatter,
private readonly TableOutputFormatter $tableOutputFormatter,
) {
parent::__construct();
}
Expand All @@ -41,13 +45,20 @@ protected function configure(): void
$this->addOption('max-level', null, InputOption::VALUE_REQUIRED, 'Max PHPStan level to run', 8);

$this->addOption('env-file', null, InputOption::VALUE_REQUIRED, 'Path to project .env file');
$this->addOption('json', null, InputOption::VALUE_NONE, 'Show result in JSON');
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
$minPhpStanLevel = (int) $input->getOption('min-level');
$maxPhpStanLevel = (int) $input->getOption('max-level');
$projectDirectory = $input->getArgument('directory');
$isJson = (bool) $input->getOption('json');

// silence output till the end to avoid invalid json format
if ($isJson) {
$this->symfonyStyle->setVerbosity(OutputInterface::VERBOSITY_QUIET);
}

$vendorBinDirectory = ComposerLoader::getBinDirectory($projectDirectory);

Expand All @@ -56,7 +67,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int

$envVariables = $this->loadEnvVariables($input);

$phpStanLevelResults = [];
$levelResults = [];

// 1. prepare empty phpstan config
// no baselines, ignores etc. etc :)
Expand All @@ -67,15 +78,21 @@ protected function execute(InputInterface $input, OutputInterface $output): int
for ($phpStanLevel = $minPhpStanLevel; $phpStanLevel <= $maxPhpStanLevel; ++$phpStanLevel) {
$this->symfonyStyle->section(sprintf('Running PHPStan level %d', $phpStanLevel));

$phpStanLevelResults[] = $this->measureErrorCountInLevel($phpStanLevel, $projectDirectory, $envVariables);
$levelResults[] = $this->measureErrorCountInLevel($phpStanLevel, $projectDirectory, $envVariables);

$this->symfonyStyle->newLine();
}

$bodyscanResult = new BodyscanResult($levelResults);

// 3. tidy up temporary config
unlink($projectDirectory . '/phpstan-bodyscan.neon');

$this->renderResultInTable($phpStanLevelResults);
if ($isJson) {
$this->jsonOutputFormatter->outputResult($bodyscanResult);
} else {
$this->tableOutputFormatter->outputResult($bodyscanResult);
}

return self::SUCCESS;
}
Expand All @@ -87,7 +104,7 @@ private function measureErrorCountInLevel(
int $phpStanLevel,
string $projectDirectory,
array $envVariables
): PHPStanLevelResult {
): LevelResult {
$analyseLevelProcess = $this->analyseProcessFactory->create($projectDirectory, $phpStanLevel, $envVariables);

$this->symfonyStyle->writeln('Running: <fg=green>' . $analyseLevelProcess->getCommandLine() . '</>');
Expand Down Expand Up @@ -122,31 +139,7 @@ private function measureErrorCountInLevel(
$fileErrorCount
));

return new PHPStanLevelResult($phpStanLevel, $fileErrorCount);
}

/**
* @param PHPStanLevelResult[] $phpStanLevelResults
*/
private function renderResultInTable(array $phpStanLevelResults): void
{
// convert to symfony table data
$tableRows = [];
foreach ($phpStanLevelResults as $phpStanLevelResult) {
$tableRows[] = [$phpStanLevelResult->getLevel(), $phpStanLevelResult->getErrorCount()];
}

$tableStyle = new TableStyle();
$tableStyle->setPadType(STR_PAD_LEFT);

$this->symfonyStyle->newLine(2);

$this->symfonyStyle->createTable()
->setHeaders(['Level', 'Error count'])
->setRows($tableRows)
// align right
->setStyle($tableStyle)
->render();
return new LevelResult($phpStanLevel, $fileErrorCount);
}

private function ensurePHPStanIsInstalled(string $projectDirectory, string $vendorBinDirectory): void
Expand All @@ -161,10 +154,11 @@ private function ensurePHPStanIsInstalled(string $projectDirectory, string $vend
], $projectDirectory);

$requirePHPStanProcess->mustRun();
} else {
$this->symfonyStyle->note('PHPStan found in the project, lets run it!');
$this->symfonyStyle->newLine(2);
return;
}

$this->symfonyStyle->note('PHPStan found in the project, lets run it!');
$this->symfonyStyle->newLine(2);
}

/**
Expand Down
12 changes: 12 additions & 0 deletions src/Contract/OutputFormatterInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace TomasVotruba\PHPStanBodyscan\Contract;

use TomasVotruba\PHPStanBodyscan\ValueObject\BodyscanResult;

interface OutputFormatterInterface
{
public function outputResult(BodyscanResult $bodyscanResult): void;
}
46 changes: 46 additions & 0 deletions src/OutputFormatter/JsonOutputFormatter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

declare(strict_types=1);

namespace TomasVotruba\PHPStanBodyscan\OutputFormatter;

use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use TomasVotruba\PHPStanBodyscan\Contract\OutputFormatterInterface;
use TomasVotruba\PHPStanBodyscan\ValueObject\BodyscanResult;

final readonly class JsonOutputFormatter implements OutputFormatterInterface
{
public function __construct(
private SymfonyStyle $symfonyStyle
) {
}

public function outputResult(BodyscanResult $bodyscanResult): void
{
// restore verbosity
$this->symfonyStyle->setVerbosity(OutputInterface::VERBOSITY_NORMAL);

$rawData = $this->createRawData($bodyscanResult);

$jsonOutput = json_encode($rawData, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR);
$this->symfonyStyle->writeln($jsonOutput);
}

/**
* @return mixed[]
*/
private function createRawData(BodyscanResult $bodyscanResult): array
{
$rawData = [];

foreach ($bodyscanResult->getLevelResults() as $levelResult) {
$rawData[] = [
'level' => $levelResult->getLevel(),
'error_count' => $levelResult->getErrorCount(),
];
}

return $rawData;
}
}
49 changes: 49 additions & 0 deletions src/OutputFormatter/TableOutputFormatter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

declare(strict_types=1);

namespace TomasVotruba\PHPStanBodyscan\OutputFormatter;

use Symfony\Component\Console\Helper\TableStyle;
use Symfony\Component\Console\Style\SymfonyStyle;
use TomasVotruba\PHPStanBodyscan\Contract\OutputFormatterInterface;
use TomasVotruba\PHPStanBodyscan\ValueObject\BodyscanResult;

final readonly class TableOutputFormatter implements OutputFormatterInterface
{
public function __construct(
private SymfonyStyle $symfonyStyle,
) {
}

public function outputResult(BodyscanResult $bodyscanResult): void
{
// convert to symfony table data
$tableRows = $this->createRawData($bodyscanResult);

$tableStyle = new TableStyle();
$tableStyle->setPadType(STR_PAD_LEFT);

$this->symfonyStyle->newLine(2);

$this->symfonyStyle->createTable()
->setHeaders(['Level', 'Error count'])
->setRows($tableRows)
// align right
->setStyle($tableStyle)
->render();
}

/**
* @return mixed[]
*/
private function createRawData(BodyscanResult $bodyscanResult): array
{
$tableRows = [];
foreach ($bodyscanResult->getLevelResults() as $levelResult) {
$tableRows[] = [$levelResult->getLevel(), $levelResult->getErrorCount()];
}

return $tableRows;
}
}
24 changes: 24 additions & 0 deletions src/ValueObject/BodyscanResult.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace TomasVotruba\PHPStanBodyscan\ValueObject;

final readonly class BodyscanResult
{
/**
* @param LevelResult[] $levelResults
*/
public function __construct(
private array $levelResults,
) {
}

/**
* @return LevelResult[]
*/
public function getLevelResults(): array
{
return $this->levelResults;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace TomasVotruba\PHPStanBodyscan\ValueObject;

final readonly class PHPStanLevelResult
final readonly class LevelResult
{
public function __construct(
private int $level,
Expand Down

0 comments on commit 37ad1e7

Please sign in to comment.