Skip to content

Commit

Permalink
Merge pull request #38 from akeneo/rule-builder
Browse files Browse the repository at this point in the history
Introduce rule builder and enhance documentation
  • Loading branch information
jjanvier authored Mar 28, 2018
2 parents 1bab14a + 3330469 commit 61a638e
Show file tree
Hide file tree
Showing 5 changed files with 222 additions and 129 deletions.
73 changes: 2 additions & 71 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ At the moment, 3 types of rules are supported:

## Requirements

PHP needs to be a minimum version of PHP 5.3.6.
PHP needs to be a minimum version of PHP 7.1

## Installation

Expand All @@ -38,73 +38,4 @@ you're good to go:

## Usage

The detect command detects coupling problems for a given file or directory depending on the
coupling rules that have been defined:

```bash
php bin/php-coupling-detector detect /path/to/dir
php bin/php-coupling-detector detect /path/to/file
```

The exit status of the detect command can be: 0 if no violations have been raised, 10 in case of
warnings and 99 in case of errors.

You can save the configuration in a ``.php_cd`` file in the root directory of
your project. The file must return an instance of ``Akeneo\CouplingDetector\Configuration\Configuration``,
which lets you configure the rules and the directories that need to be analyzed.
Here is an example below:

```php
<?php
use \Akeneo\CouplingDetector\Domain\Rule;
use \Akeneo\CouplingDetector\Domain\RuleInterface;

$finder = new \Symfony\Component\Finder\Finder();
$finder
->files()
->name('*.php')
->notPath('foo/bar/');

$rules = [
new Rule('foo', ['bar', 'baz'], RuleInterface::TYPE_FORBIDDEN),
new Rule('zoo', ['too'], RuleInterface::TYPE_DISCOURAGED),
new Rule('bli', ['bla', 'ble', 'blu'], RuleInterface::TYPE_ONLY),
];

return new \Akeneo\CouplingDetector\Configuration\Configuration($rules, $finder);
?>
```

You can also use the default finder implementation if you want to analyse all the PHP files
of your directory:

```php
<?php
use \Akeneo\CouplingDetector\Domain\Rule;
use \Akeneo\CouplingDetector\Domain\RuleInterface;

$rules = [
new Rule('foo', ['bar', 'baz'], RuleInterface::TYPE_FORBIDDEN),
new Rule('zoo', ['too'], RuleInterface::TYPE_DISCOURAGED),
new Rule('bli', ['bla', 'ble', 'blu'], RuleInterface::TYPE_ONLY),
];

return new \Akeneo\CouplingDetector\Configuration\Configuration(
$rules,
\Akeneo\CouplingDetector\Configuration\DefaultFinder
);
?>

```

With the ``--config-file`` option you can specify the path to the ``.php_cd`` file:

```bash
php bin/php-coupling-detector detect /path/to/dir --config-file=/path/to/my/configuration.php_cd
```

With the --format option you can specify the output format:

```bash
php %command.full_name% /path/to/dir --format=dot
```
To discover how to use this tool, please read the usage of the [detect command](doc/DETECT.md).
72 changes: 72 additions & 0 deletions doc/DETECT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Detect coupling problems

The _detect_ command detects coupling problems for a given file or directory depending on the
coupling rules that have been defined:

```bash
php bin/php-coupling-detector detect /path/to/directory
php bin/php-coupling-detector detect /path/to/file
```

The exit status of the _detect_ command can be: ``0`` if no violations have been raised, ``10`` in case of
warnings and ``99`` in case of errors.

You can save the configuration in a ``.php_cd`` file in the root directory of
your project. The file must return an instance of ``Akeneo\CouplingDetector\Configuration\Configuration``,
which lets you configure the rules and the directories that need to be analyzed.
Here is an example below:

```php
<?php

$finder = new \Symfony\Component\Finder\Finder();
$finder
->files()
->name('*.php')
->notPath('foo/bar/');

$builder = new \Akeneo\CouplingDetector\RuleBuilder();

$rules = [
$builder->forbids(['bar', 'baz'])->in('foo'),
$builder->discourages(['too'])->in('zoo'),
$builder->only(['bla', 'ble', 'blu'])->in('bli'),
];

return new \Akeneo\CouplingDetector\Configuration\Configuration($rules, $finder);
?>
```

You can also use the default finder implementation if you want to analyse all the PHP files
of your directory:

```php
<?php

$builder = new \Akeneo\CouplingDetector\RuleBuilder();

$rules = [
$builder->forbids(['bar', 'baz'])->in('foo'),
$builder->discourages(['too'])->in('zoo'),
$builder->only(['bla', 'ble', 'blu'])->in('bli'),
];

return new \Akeneo\CouplingDetector\Configuration\Configuration(
$rules,
new \Akeneo\CouplingDetector\Configuration\DefaultFinder()
);
?>

```

With the ``--config-file`` option you can specify the path to the ``.php_cd`` file:

```bash
php bin/php-coupling-detector detect /path/to/dir --config-file=/path/to/my/own_configuration_file.php
```

With the ``--format`` option you can specify the output format:

```bash
php bin/php-coupling-detector /path/to/dir --format=dot
```
72 changes: 72 additions & 0 deletions spec/Akeneo/CouplingDetector/RuleBuilderSpec.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

namespace spec\Akeneo\CouplingDetector;

use Akeneo\CouplingDetector\Domain\Rule;
use Akeneo\CouplingDetector\Domain\RuleInterface;
use Akeneo\CouplingDetector\RuleBuilder;
use PhpSpec\ObjectBehavior;

class RuleBuilderSpec extends ObjectBehavior
{
function it_is_initializable()
{
$this->shouldHaveType(RuleBuilder::class);
}

function it_does_not_create_a_rule_without_constraints()
{
$this
->shouldThrow(new \Exception('Can not create a rule without any requirement defined previously.'))
->during('in', ['baz']);
}

function it_chains_the_methods_for_building_a_rule()
{
$this->forbids(['foo', 'bar'])->shouldReturn($this);
$this->only(['foo', 'bar'])->shouldReturn($this);
$this->discourages(['foo', 'bar'])->shouldReturn($this);
}

function it_creates_a_forbidden_rule()
{
$this->forbids(['foo', 'bar']);
$actual = $this->in('baz');

$expected = new Rule('baz', ['foo', 'bar'], RuleInterface::TYPE_FORBIDDEN);
$actual->shouldBeAnInstanceOf(RuleInterface::class);
$actual->shouldBeLikeExpectedRule($expected);
}

function it_creates_an_only_rule()
{
$this->only(['foo', 'bar']);
$actual = $this->in('baz');

$expected = new Rule('baz', ['foo', 'bar'], RuleInterface::TYPE_ONLY);
$actual->shouldBeAnInstanceOf(RuleInterface::class);
$actual->shouldBeLikeExpectedRule($expected);
}

function it_creates_a_discouraged_rule()
{
$this->discourages(['foo', 'bar']);
$actual = $this->in('baz');

$expected = new Rule('baz', ['foo', 'bar'], RuleInterface::TYPE_DISCOURAGED);
$actual->shouldBeAnInstanceOf(RuleInterface::class);
$actual->shouldBeLikeExpectedRule($expected);
}

public function getMatchers(): array
{
return array(
'beLikeExpectedRule' => function ($actual, $expected) {
return
$actual->getSubject() === $expected->getSubject() &&
$actual->getRequirements() === $expected->getRequirements() &&
$actual->getType() === $expected->getType();
},
);
}
}
72 changes: 14 additions & 58 deletions src/Akeneo/CouplingDetector/Console/Command/DetectCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,64 +60,7 @@ protected function configure()
)
)
->setDescription('Detect coupling rules')
->setHelp(
<<<HELP
The <info>%command.name%</info> command detects coupling problems for a given file or directory depending on the
coupling rules that have been defined:
<info>php %command.full_name% /path/to/dir</info>
<info>php %command.full_name% /path/to/file</info>
The exit status of the <info>%command.name%</info> command can be: 0 if no rules have been raised, 10 in case of
warnings and 99 in case of errors.
You can save the configuration in a <comment>.php_cd</comment> file in the root directory of
your project. The file must return an instance of ``Akeneo\CouplingDetector\Configuration\Configuration``,
which lets you configure the rules and the directories that need to be analyzed.
Here is an example below:
<?php
use \Akeneo\CouplingDetector\Domain\Rule;
use \Akeneo\CouplingDetector\Domain\RuleInterface;
\$finder = new \Symfony\Component\Finder\Finder();
\$finder
->files()
->name('*.php')
->notPath('foo/bar/');
\$rules = [
new Rule('foo', ['bar', 'baz'], RuleInterface::TYPE_FORBIDDEN),
new Rule('zoo', ['too'], RuleInterface::TYPE_DISCOURAGED),
new Rule('bli', ['bla', 'ble', 'blu'], RuleInterface::TYPE_ONLY),
];
return new \Akeneo\CouplingDetector\Configuration\Configuration(\$rules, \$finder);
?>
You can also use the default finder implementation if you want to analyse all the PHP files
of your directory:
<?php
use \Akeneo\CouplingDetector\Domain\Rule;
use \Akeneo\CouplingDetector\Domain\RuleInterface;
\$rules = [
new Rule('foo', ['bar', 'baz'], RuleInterface::TYPE_FORBIDDEN),
new Rule('zoo', ['too'], RuleInterface::TYPE_DISCOURAGED),
new Rule('bli', ['bla', 'ble', 'blu'], RuleInterface::TYPE_ONLY),
];
return new \Akeneo\CouplingDetector\Configuration\Configuration(
\$rules,
\Akeneo\CouplingDetector\Configuration\DefaultFinder
);
?>
With the <comment>--config-file</comment> option you can specify the path to the <comment>.php_cd</comment> file:
<info>php %command.full_name% /path/to/dir --config-file=/path/to/my/configuration.php_cd</info>
With the <comment>--format</comment> option you can specify the output format:
<info>php %command.full_name% /path/to/dir --format=dot</info>
HELP
)
->setHelp($this->loadHelpContent())
;
}

Expand Down Expand Up @@ -245,4 +188,17 @@ private function initEventDispatcher(OutputInterface $output, $formatterName, $v

return $eventDispatcher;
}

private function loadHelpContent(): string
{
$content = file_get_contents(__DIR__ . '/../../../../../doc/DETECT.md');
$content = preg_replace('/^.+\n/', '', $content);
$content = str_replace('bin/php-coupling-detector', '%command.full_name%', $content);
$content = str_replace('_detect_ command', '<info>%command.name%</info> command', $content);
$content = preg_replace('/```bash(.*?)```/s', '<info>$1</info>', $content);
$content = preg_replace('/```php(.*?)```/s', '<question>$1</question>', $content);
$content = preg_replace('/``(.*?)``/s', '<comment>$1</comment>', $content);

return $content;
}
}
62 changes: 62 additions & 0 deletions src/Akeneo/CouplingDetector/RuleBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

namespace Akeneo\CouplingDetector;

use Akeneo\CouplingDetector\Domain\Rule;
use Akeneo\CouplingDetector\Domain\RuleInterface;

/**
* Builds a rule simply. Usage examples:
*
* $builder = new RuleBuilder();
* $rule1 = $builder->forbids(['foo', 'bar'])->in('baz');
* $rule2 = $builder->discouraged(['toto'])->in('titi');
* $rule3 = $builder->only(['tik'])->in('tak');
*
* @author Julien Janvier <[email protected]>
* @license http://opensource.org/licenses/MIT MIT
*/
class RuleBuilder
{
/** @var array */
private $requirements = [];

/** @var string */
private $type;

public function forbids(array $requirements): RuleBuilder
{
$this->requirements = $requirements;
$this->type = RuleInterface::TYPE_FORBIDDEN;

return $this;
}

public function only(array $requirements): RuleBuilder
{
$this->requirements = $requirements;
$this->type = RuleInterface::TYPE_ONLY;

return $this; }

public function discourages(array $requirements): RuleBuilder
{
$this->requirements = $requirements;
$this->type = RuleInterface::TYPE_DISCOURAGED;

return $this;
}

public function in(string $subject): RuleInterface
{
if (empty($this->requirements)) {
throw new \Exception('Can not create a rule without any requirement defined previously.');
}

if (null === $this->type) {
throw new \Exception('Can not create a rule without any type defined previously.');
}

return new Rule($subject, $this->requirements, $this->type);
}
}

0 comments on commit 61a638e

Please sign in to comment.