Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/parallelization #414

Merged
merged 20 commits into from
Oct 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions config/routes/console.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@

use NunoMaduro\PhpInsights\Application\Console\Commands\AnalyseCommand;
use NunoMaduro\PhpInsights\Application\Console\Commands\FixCommand;
use NunoMaduro\PhpInsights\Application\Console\Commands\InternalProcessorCommand;
use NunoMaduro\PhpInsights\Application\Console\Commands\InvokableCommand;
use NunoMaduro\PhpInsights\Application\Console\Definitions\AnalyseDefinition;
use NunoMaduro\PhpInsights\Application\Console\Definitions\FixDefinition;
use NunoMaduro\PhpInsights\Application\Console\Definitions\InternalProcessorDefinition;

return (static function (): array {
$container = require dirname(__DIR__) . DIRECTORY_SEPARATOR . 'container.php';
Expand All @@ -23,8 +25,16 @@
FixDefinition::get()
);

$internalProcessorCommand = new InvokableCommand(
InternalProcessorCommand::NAME,
$container->get(InternalProcessorCommand::class),
InternalProcessorDefinition::get()
);
$internalProcessorCommand->setHidden(true);

return [
$analyseCommand,
$fixCommand,
$internalProcessorCommand,
];
})();
6 changes: 6 additions & 0 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ parameters:
- '#NunoMaduro\\PhpInsights\\Domain\\File::addFixableError#'
- '#iterable type PhpCsFixer\\Tokenizer\\Tokens#'
- '#Method NunoMaduro\\PhpInsights\\Application\\Console\\Definitions\\BaseDefinition::get\(\) is not final#'
-
message: "#^Method Tests\\\\TestCase\\:\\:.*\\(\\) is not final, but since the containing class is abstract, it should be\\.$#"
count: 2
path: tests/TestCase.php


autoload_files:
- %rootDir%/../../squizlabs/php_codesniffer/autoload.php
reportUnmatchedIgnoredErrors: false
Expand Down
213 changes: 213 additions & 0 deletions src/Application/Console/Commands/InternalProcessorCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
<?php

declare(strict_types=1);

namespace NunoMaduro\PhpInsights\Application\Console\Commands;

use NunoMaduro\PhpInsights\Domain\Collector;
use NunoMaduro\PhpInsights\Domain\Configuration;
use NunoMaduro\PhpInsights\Domain\Container;
use NunoMaduro\PhpInsights\Domain\Contracts\DetailsCarrier;
use NunoMaduro\PhpInsights\Domain\Contracts\FileProcessor as FileProcessorContract;
use NunoMaduro\PhpInsights\Domain\Contracts\Fixable;
use NunoMaduro\PhpInsights\Domain\Contracts\HasInsights;
use NunoMaduro\PhpInsights\Domain\Contracts\InsightLoader as InsightLoaderContract;
use NunoMaduro\PhpInsights\Domain\Details;
use NunoMaduro\PhpInsights\Domain\InsightLoader\InsightLoader;
use NunoMaduro\PhpInsights\Domain\MetricsFinder;
use Psr\SimpleCache\CacheInterface;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Finder\SplFileInfo;

/**
* @internal
*/
final class InternalProcessorCommand
{
public const NAME = 'internal:processors';

private CacheInterface $cache;

private Configuration $configuration;

/**
* @var array<InsightLoaderContract>
*/
private array $insightsLoaders = [];

/**
* @var array<\NunoMaduro\PhpInsights\Domain\Contracts\Insight>
*/
private array $allInsights = [];

/**
* @var array<FileProcessorContract>
*/
private array $filesProcessors = [];

public function __construct(CacheInterface $cache, Configuration $configuration)
{
$this->cache = $cache;
$this->configuration = $configuration;
}

public function __invoke(InputInterface $input, OutputInterface $output): int
{
$this->initialize();

$cacheKey = $input->getArgument('cache-key');
if (! \is_string($cacheKey)) {
return 0;
}

$files = $this->cache->get($cacheKey, []);
if (! \is_array($files) || \count($files) === 0) {
return 0;
}

$metrics = MetricsFinder::find();
$insightsClasses = [];
foreach ($metrics as $metricClass) {
$insightsClasses = [...$insightsClasses, ...$this->loadInsights($metricClass)];
}
$this->allInsights = $insightsClasses;

set_error_handler(static function (int $errno, string $errstr): bool {
throw new \RuntimeException($errstr, $errno);
}, E_NOTICE);

foreach ($files as $file) {
$fileInfo = pathinfo($file);
$this->processFile(new SplFileInfo($file, $fileInfo['dirname'], $file));
}

return 0;
}

/**
* Retrieve filesProcessors and insightsLoaders.
*/
private function initialize(): void
{
$container = Container::make();

$this->filesProcessors = $container->get(FileProcessorContract::FILE_PROCESSOR_TAG);
$loaders = $container->get(InsightLoaderContract::INSIGHT_LOADER_TAG);

// exclude InsightLoader, not used here
$this->insightsLoaders = array_filter($loaders, static function (InsightLoaderContract $loader): bool {
return ! $loader instanceof InsightLoader;
});
}

/**
* Returns the `Insights` from the given metric class.
*
* @return array<\NunoMaduro\PhpInsights\Domain\Contracts\Insight>
*/
private function loadInsights(string $metricClass): array
{
/** @var HasInsights $metric */
$metric = new $metricClass();

$insights = \array_key_exists(HasInsights::class, class_implements($metricClass))
? $metric->getInsights()
: [];

$toAdd = $this->configuration->getAddedInsightsByMetric($metricClass);
$insights = [...$insights, ...$toAdd];

// Remove insights based on config.
$insights = array_diff($insights, $this->configuration->getRemoves());

$insightsAdded = [];
$path = (string) (getcwd() ?? $this->configuration->getCommonPath());
$collector = new Collector([], $path);
foreach ($insights as $insight) {
/** @var InsightLoader $loader */
foreach ($this->insightsLoaders as $loader) {
if ($loader->support($insight)) {
$insightsAdded[] = $loader->load(
$insight,
$path,
$this->configuration->getConfigForInsight($insight),
$collector
);
}
}
}

foreach ($insightsAdded as $insight) {
/** @var FileProcessorContract $processor */
foreach ($this->filesProcessors as $processor) {
if ($processor->support($insight)) {
$processor->addChecker($insight);
}
}
}

return $insightsAdded;
}

private function processFile(SplFileInfo $file): void
{
$cacheKey = 'insights.' . $this->configuration->getCacheKey() . '.' . md5($file->getContents());
// Do not use cache if fix is enabled to force processors to handle it
if ($this->configuration->hasFixEnabled() === false && $this->cache->has($cacheKey)) {
return;
}
/** @var FileProcessorContract $fileProcessor */
foreach ($this->filesProcessors as $fileProcessor) {
$fileProcessor->processFile($file);
}

if ($this->configuration->hasFixEnabled() === true) {
// regenerate cache key in case fixer change contents
$cacheKey = 'insights.' . $this->configuration->getCacheKey() . '.' . md5($file->getContents());
}

$this->cacheDetailsForFile($cacheKey, $file);

if ($this->configuration->hasFixEnabled()) {
$cacheKey = str_replace('insights.', 'fix.', $cacheKey);
$this->cacheFixForFile($cacheKey, $file);
}
}

private function cacheDetailsForFile(string $cacheKey, SplFileInfo $file): void
{
$detailsByInsights = [];
/** @var \NunoMaduro\PhpInsights\Domain\Contracts\Insight $insight */
foreach ($this->allInsights as $insight) {
if (! $insight instanceof DetailsCarrier || ! $insight->hasIssue()) {
continue;
}
$details = array_filter(
$insight->getDetails(),
static fn (Details $detail): bool => $detail->getFile() === $file->getRealPath()
);
$detailsByInsights[$insight->getInsightClass()] = $details;
}

$this->cache->set($cacheKey, $detailsByInsights);
}

private function cacheFixForFile(string $cacheKey, SplFileInfo $file): void
{
$fixByInsights = [];
/** @var \NunoMaduro\PhpInsights\Domain\Contracts\Insight $insight */
foreach ($this->allInsights as $insight) {
if (! $insight instanceof Fixable || $insight->getTotalFix() === 0) {
continue;
}
$details = array_filter(
$insight->getFixPerFile(),
static fn (Details $detail): bool => $detail->getFile() === $file->getRealPath()
);
$fixByInsights[$insight->getInsightClass()] = $details;
}

$this->cache->set($cacheKey, $fixByInsights);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

namespace NunoMaduro\PhpInsights\Application\Console\Definitions;

use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;

/**
* @internal
*/
final class InternalProcessorDefinition
{
public static function get(): InputDefinition
{
return new InputDefinition([
new InputArgument(
'cache-key',
InputArgument::REQUIRED,
'Cache key containing files to analyse'
),
]);
}
}
10 changes: 10 additions & 0 deletions src/Application/Injectors/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
namespace NunoMaduro\PhpInsights\Application\Injectors;

use NunoMaduro\PhpInsights\Application\ConfigResolver;
use NunoMaduro\PhpInsights\Application\Console\Commands\InternalProcessorCommand;
use NunoMaduro\PhpInsights\Application\Console\Definitions\DefinitionBinder;
use NunoMaduro\PhpInsights\Domain\Configuration as DomainConfiguration;
use NunoMaduro\PhpInsights\Domain\Container;
use Psr\SimpleCache\CacheInterface;
use Symfony\Component\Console\Input\ArgvInput;

/**
Expand All @@ -26,6 +29,13 @@ public function __invoke(): array
return [
DomainConfiguration::class => static function (): DomainConfiguration {
$input = new ArgvInput();
if (
$input->getFirstArgument() === InternalProcessorCommand::NAME &&
Container::make()->get(CacheInterface::class)->has('current_configuration')
) {
// Use cache only for internal:processor, not other commands
return Container::make()->get(CacheInterface::class)->get('current_configuration');
}

DefinitionBinder::bind($input);
$configPath = ConfigResolver::resolvePath($input);
Expand Down
Loading