Skip to content

Commit

Permalink
Merge pull request #59 from bmitch/bmitchell-42
Browse files Browse the repository at this point in the history
Fixes #42
  • Loading branch information
bmitch authored Aug 23, 2017
2 parents c1ea2c1 + 75c7057 commit b59c951
Show file tree
Hide file tree
Showing 10 changed files with 212 additions and 19 deletions.
51 changes: 43 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,36 +8,45 @@ Helps discover good candidates for refactoring.
* [Compatibility](#compatibility)
* [How to Install?](#how-to-install)
* [How to Use?](#how-to-use)
* [How to Configure?](#how-to-configure)
* [Similar Packages](#similar-packages)
* [Contributing](#contributing)
* [License](#license)

## What is it? ##
`churn-php` is a package that helps you identify files in your project that could be good candidates for refactoring. It examines each PHP file in the path it is provided and:
`churn-php` is a package that helps you identify php files in your project that could be good candidates for refactoring. It examines each PHP file in the path it is provided and:
* Checks how many commits it has.
* Calculates the cyclomatic complexity.
* Creates a score based on these two values.

The results are displayed in a table:
```
___ _ _ __ __ ____ _ _ ____ _ _ ____
/ __)( )_( )( )( )( _ \( \( )___( _ \( )_( )( _ \
( (__ ) _ ( )(__)( ) / ) ((___))___/ ) _ ( )___/
\___)(_) (_)(______)(_)\_)(_)\_) (__) (_) (_)(__) https://github.com/bmitch/churn-php
+---------------------------------------------------------------------+---------------+------------+-------+
| File | Times Changed | Complexity | Score |
+---------------------------------------------------------------------+---------------+------------+-------+
| src/Managers/FileManager.php | 5 | 4 | 9 |
| src/Assessors/CyclomaticComplexity/CyclomaticComplexityAssessor.php | 4 | 4 | 8 |
| src/Assessors/GitCommitCount/GitCommitCountAssessor.php | 3 | 4 | 7 |
| src/Commands/ChurnCommand.php | 3 | 2 | 5 |
| src/Managers/FileManager.php | 2 | 3 | 5 |
| src/Results/ResultsGenerator.php | 2 | 2 | 4 |
| src/Results/ResultsParser.php | 3 | 3 | 6 |
| src/Results/Result.php | 2 | 1 | 3 |
| src/Services/CommandService.php | 2 | 1 | 3 |
| src/Factories/ProcessFactory.php | 2 | 1 | 3 |
| src/Results/ResultCollection.php | 1 | 1 | 2 |
| src/Values/File.php | 1 | 1 | 2 |
| src/Collections/FileCollection.php | 1 | 1 | 2 |
| src/Values/Config.php | 1 | 1 | 2 |
| src/Processes/ChurnProcess.php | 1 | 1 | 2 |
+---------------------------------------------------------------------+---------------+------------+-------+
10 files analysed in 0.24276995658875 seconds using 15 parallel jobs.
```


A file that changes a lot and has a high complexity might be a higher candidate for refactoring than a file that doesn't change a lot and has a low complexity.
A file that changes a lot and has a high complexity might be a better candidate for refactoring than a file that doesn't change a lot and has a low complexity.

`churn-php` only intends to assist the developer identifying files for refactoring. It's best to use the results in addition to your own judgement to decide which files you may want to refactor.
`churn-php` only assists the developer to identify files for refactoring. It's best to use the results in addition to your own judgment to decide which files you may want to refactor.

## Compatibility ##
* PHP 7+
Expand All @@ -53,6 +62,32 @@ composer require bmitch/churn-php --dev
vendor/bin/churn run <path to source code>
```

## How to Configure?
You may add an optional `churn.yml` file to the root of your project which can be used to configure churn-php. A sample `churm.yml` file looks like:

```yml
# The maximum number of files to display in the results table.
# Default: 10
filesToShow: 10

# The number of parallel jobs to use when processing files.
# Default 10:
parallelJobs: 10

# How far back in the git history to count the number of commits to a file
# Can be a human readable date like 'One week ago' or a date like '2017-07-12'
# Default '10 Years ago'
commitsSince: One year ago

# Files to ignore when processing. The full path to the file relative to the root of your project is required
# Default: All PHP files in the path provided to churn-php are processed.
filesToIgnore:
- src/Commands/ChurnCommand.php
- src/Results/ResultsParser.php
```
If a `churm.yml` file is omitted or an individual setting is omitted the default values above will be used.

## Similar Packages
* https://github.com/danmayer/churn (Ruby)

Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
"php": ">=7.0.0",
"symfony/console": "~3.2",
"tightenco/collect": "^5.4",
"symfony/process": "^3.3"
"symfony/process": "^3.3",
"symfony/yaml": "^3.3"
},
"require-dev": {
"phpunit/phpunit": "^5.7",
Expand Down
14 changes: 8 additions & 6 deletions src/Commands/ChurnCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Churn\Commands;

use Churn\Factories\ProcessFactory;
use Churn\Values\Config;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
Expand All @@ -12,6 +13,7 @@
use Churn\Results\ResultCollection;
use Illuminate\Support\Collection;
use Churn\Results\ResultsParser;
use Symfony\Component\Yaml\Yaml;

class ChurnCommand extends Command
{
Expand Down Expand Up @@ -51,9 +53,10 @@ class ChurnCommand extends Command
public function __construct()
{
parent::__construct();
$this->fileManager = new FileManager;
$this->config = new Config(Yaml::parse(@file_get_contents(getcwd() . '/churn.yml')) ?? []);
$this->fileManager = new FileManager($this->config);
$this->processFactory = new ProcessFactory($this->config);
$this->resultsParser = new ResultsParser;
$this->processFactory = new ProcessFactory;
}

/**
Expand Down Expand Up @@ -97,7 +100,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
*/
private function getProcessResults()
{
for ($index = $this->runningProcesses->count(); $this->filesCollection->hasFiles() > 0 && $index < 10; $index++) {
for ($index = $this->runningProcesses->count(); $this->filesCollection->hasFiles() > 0 && $index < $this->config->getParallelJobs(); $index++) {
$file = $this->filesCollection->getNextFile();

$process = $this->processFactory->createGitCommitProcess($file);
Expand Down Expand Up @@ -135,11 +138,10 @@ protected function displayResults(OutputInterface $output, ResultCollection $res

$table = new Table($output);
$table->setHeaders(['File', 'Times Changed', 'Complexity', 'Score']);

foreach ($results->orderByScoreDesc()->take(10) as $result) {
foreach ($results->orderByScoreDesc()->take($this->config->getFilesToShow()) as $result) {
$table->addRow($result->toArray());
}
$table->render();
echo " " . $this->filesCount . " files analysed in {$totalTime} seconds.\n\n";
echo " " . $this->filesCount . " files analysed in {$totalTime} seconds using " . $this->config->getParallelJobs() . " parallel jobs.\n\n";
}
}
12 changes: 11 additions & 1 deletion src/Factories/ProcessFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,21 @@
namespace Churn\Factories;

use Churn\Processes\ChurnProcess;
use Churn\Values\Config;
use Churn\Values\File;
use Symfony\Component\Process\Process;

class ProcessFactory
{
/**
* ProcessFactory constructor.
* @param Config $config Configuration Settings.
*/
public function __construct(Config $config)
{
$this->config = $config;
}

/**
* Creates a Git Commit Process that will run on $file.
* @param File $file File that the process will execute on.
Expand All @@ -16,7 +26,7 @@ class ProcessFactory
public function createGitCommitProcess(File $file): ChurnProcess
{
$process = new Process(
'git -C ' . getcwd() . " log --name-only --pretty=format: " . $file->getFullPath(). " | sort | uniq -c | sort -nr"
'git -C ' . getcwd() . " log --since=\"" . $this->config->getCommitsSince() . "\" --name-only --pretty=format: " . $file->getFullPath(). " | sort | uniq -c | sort -nr"
);

return new ChurnProcess($file, $process, 'GitCommitProcess');
Expand Down
14 changes: 14 additions & 0 deletions src/Managers/FileManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,22 @@
namespace Churn\Managers;

use Churn\Collections\FileCollection;
use Churn\Values\Config;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use Churn\Values\File;

class FileManager
{
/**
* FileManager constructor.
* @param Config $config Configuration Settings.
*/
public function __construct(Config $config)
{
$this->config = $config;
}

/**
* Recursively finds all files with the .php extension in the provided
* $path and returns list as array.
Expand All @@ -24,6 +34,10 @@ public function getPhpFiles(string $path): FileCollection
continue;
}

if (in_array($file->getPathname(), $this->config->getFilesToIgnore())) {
continue;
}

$files->push(new File(['displayPath' => $file->getPathName(), 'fullPath' => $file->getRealPath()]));
}

Expand Down
79 changes: 79 additions & 0 deletions src/Values/Config.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php declare(strict_types = 1);


namespace Churn\Values;

class Config
{
/**
* The number of files to display in the results table.
* @var integer
*/
private $filesToShow;

/**
* The number of parallel jobs to use to process the files.
* @var integer
*/
private $parallelJobs;

/**
* How far back in the git history to go to count commits.
* @var string
*/
private $commitsSince;

/**
* The paths to files to ignore when processing.
* @var array
*/
private $filesToIgnore;

/**
* Config constructor.
* @param array $rawData Raw config data.
*/
public function __construct(array $rawData = [])
{
$this->filesToShow = $rawData['filesToShow'] ?? 10;
$this->parallelJobs = $rawData['parallelJobs'] ?? 10;
$this->commitsSince = $rawData['commitsSince'] ?? '10 years ago';
$this->filesToIgnore = $rawData['filesToIgnore'] ?? [];
}

/**
* Get the number of files to display in the results table.
* @return integer
*/
public function getFilesToShow(): int
{
return $this->filesToShow;
}

/**
* Get the number of parallel jobs to use to process the files.
* @return integer
*/
public function getParallelJobs(): int
{
return $this->parallelJobs;
}

/**
* Get how far back in the git history to go to count commits.
* @return string
*/
public function getCommitsSince(): string
{
return $this->commitsSince;
}

/**
* Get the paths to files to ignore when processing.
* @return array
*/
public function getFilesToIgnore(): array
{
return $this->filesToIgnore;
}
}
3 changes: 2 additions & 1 deletion tests/Integration/Managers/FileManagerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Churn\Managers\FileManager;
use Churn\Tests\BaseTestCase;
use Churn\Values\Config;
use Churn\Values\File;
use Illuminate\Support\Collection;

Expand Down Expand Up @@ -35,6 +36,6 @@ public function setUp()
{
parent::setup();

$this->fileManager = new FileManager;
$this->fileManager = new FileManager(new Config);
}
}
3 changes: 2 additions & 1 deletion tests/Unit/Factories/ProcessFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Churn\Tests\BaseTestCase;
use Churn\Factories\ProcessFactory;
use Churn\Values\File;
use Churn\Values\Config;

class ProcessFactoryTest extends BaseTestCase
{
Expand Down Expand Up @@ -38,6 +39,6 @@ public function it_can_create_a_cyclomatic_complexity_process()

public function setup()
{
$this->processFactory = new ProcessFactory;
$this->processFactory = new ProcessFactory(new Config);
}
}
3 changes: 2 additions & 1 deletion tests/Unit/Managers/FileManagerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Churn\Tests\BaseTestCase;
use Churn\Managers\FileManager;
use Churn\Values\Config;

class FileManagerTest extends BaseTestCase
{
Expand All @@ -27,6 +28,6 @@ public function it_can_get_the_php_files_in_a_filter()

public function setup()
{
$this->fileManager = new FileManager();
$this->fileManager = new FileManager(new Config);
}
}
49 changes: 49 additions & 0 deletions tests/Unit/Values/ConfigTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php


namespace Churn\Tests\Unit\Values;


use Churn\Tests\BaseTestCase;
use Churn\Values\Config;

class ConfigTest extends BaseTestCase
{
/** @test **/
public function it_can_be_instantiated()
{
$this->assertInstanceOf(Config::class, new Config([]));
}

/** @test **/
public function it_can_be_instantiated_without_any_parameters()
{
$this->assertInstanceOf(Config::class, new Config);
}

/** @test **/
public function it_can_return_its_default_values_when_instantiated_without_any_parameters()
{
$config = new Config;
$this->assertSame(10, $config->getFilesToShow());
$this->assertSame(10, $config->getParallelJobs());
$this->assertSame('10 years ago', $config->getCommitsSince());
$this->assertSame([], $config->getFilesToIgnore());
}

/** @test **/
public function it_can_return_its_values_when_instantiated_parameters()
{
$config = new Config([
'filesToShow' => 13,
'parallelJobs' => 7,
'commitsSince' => '4 years ago',
'filesToIgnore' => ['foo.php', 'bar.php', 'baz.php']
]);
$this->assertSame(13, $config->getFilesToShow());
$this->assertSame(7, $config->getParallelJobs());
$this->assertSame('4 years ago', $config->getCommitsSince());
$this->assertSame(['foo.php', 'bar.php', 'baz.php'], $config->getFilesToIgnore());
}

}

0 comments on commit b59c951

Please sign in to comment.