Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ Currently, you can check if a class:
```php
$rules[] = Rule::allClasses()
->that(new ResideInOneOfTheseNamespaces('App\Domain'))
->should(new DependsOnlyOnTheseNamespaces('App\Domain', 'Ramsey\Uuid'))
->should(new DependsOnlyOnTheseNamespaces(['App\Domain', 'Ramsey\Uuid'], ['App\Exluded']))
->because('we want to protect our domain from external dependencies except for Ramsey\Uuid');
```

Expand Down Expand Up @@ -348,7 +348,7 @@ $rules[] = Rule::allClasses()
```php
$rules[] = Rule::allClasses()
->that(new ResideInOneOfTheseNamespaces('App\Application'))
->should(new NotDependsOnTheseNamespaces('App\Infrastructure'))
->should(new NotDependsOnTheseNamespaces(['App\Infrastructure'], ['App\Infrastructure\Repository']))
->because('we want to avoid coupling between application layer and infrastructure layer');
```

Expand Down
23 changes: 16 additions & 7 deletions src/Expression/ForClasses/DependsOnlyOnTheseNamespaces.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,13 @@ class DependsOnlyOnTheseNamespaces implements Expression
/** @var array<string> */
private array $namespaces;

public function __construct(string ...$namespace)
/** @var array<string> */
private array $exclude;

public function __construct(array $namespaces = [], array $exclude = [])
{
$this->namespaces = $namespace;
$this->namespaces = $namespaces;
$this->exclude = $exclude;
}

public function describe(ClassDescription $theClass, string $because): Description
Expand All @@ -35,11 +39,16 @@ public function evaluate(ClassDescription $theClass, Violations $violations, str

/** @var ClassDependency $dependency */
foreach ($dependencies as $dependency) {
if (
'' === $dependency->getFQCN()->namespace()
|| $theClass->namespaceMatches($dependency->getFQCN()->namespace())
) {
continue;
if ('' === $dependency->getFQCN()->namespace()) {
continue; // skip root namespace
}

if ($theClass->namespaceMatches($dependency->getFQCN()->namespace())) {
continue; // skip classes in the same namespace
}

if ($dependency->matchesOneOf(...$this->exclude)) {
continue; // skip excluded namespaces
}

if (!$dependency->matchesOneOf(...$this->namespaces)) {
Expand Down
14 changes: 11 additions & 3 deletions src/Expression/ForClasses/NotDependsOnTheseNamespaces.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,13 @@ class NotDependsOnTheseNamespaces implements Expression
/** @var array<string> */
private array $namespaces;

public function __construct(string ...$namespace)
/** @var array<string> */
private array $exclude;

public function __construct(array $namespaces, array $exclude = [])
{
$this->namespaces = $namespace;
$this->namespaces = $namespaces;
$this->exclude = $exclude;
}

public function describe(ClassDescription $theClass, string $because): Description
Expand All @@ -36,7 +40,11 @@ public function evaluate(ClassDescription $theClass, Violations $violations, str
/** @var ClassDependency $dependency */
foreach ($dependencies as $dependency) {
if ('' === $dependency->getFQCN()->namespace()) {
continue;
continue; // skip root namespace
}

if ($dependency->matchesOneOf(...$this->exclude)) {
continue; // skip excluded namespaces
}

if ($dependency->matchesOneOf(...$this->namespaces)) {
Expand Down
12 changes: 6 additions & 6 deletions src/RuleBuilders/Architecture/Architecture.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,13 @@ public function rules(string $because = 'of component architecture'): iterable
$forbiddenComponents = array_diff($layerNames, [$name], $this->allowedDependencies[$name]);

if (!empty($forbiddenComponents)) {
$forbiddenSelectors = array_map(function (string $componentName): string {
$forbiddenSelectors = array_values(array_map(function (string $componentName): string {
return $this->componentSelectors[$componentName];
}, $forbiddenComponents);
}, $forbiddenComponents));

yield Rule::allClasses()
->that(new ResideInOneOfTheseNamespaces($selector))
->should(new NotDependsOnTheseNamespaces(...$forbiddenSelectors))
->should(new NotDependsOnTheseNamespaces($forbiddenSelectors))
->because($because);
}
}
Expand All @@ -105,13 +105,13 @@ public function rules(string $because = 'of component architecture'): iterable
continue;
}

$allowedDependencies = array_map(function (string $componentName): string {
$allowedDependencies = array_values(array_map(function (string $componentName): string {
return $this->componentSelectors[$componentName];
}, $this->componentDependsOnlyOnTheseNamespaces[$name]);
}, $this->componentDependsOnlyOnTheseNamespaces[$name]));

yield Rule::allClasses()
->that(new ResideInOneOfTheseNamespaces($selector))
->should(new DependsOnlyOnTheseNamespaces(...$allowedDependencies))
->should(new DependsOnlyOnTheseNamespaces($allowedDependencies))
->because($because);
}
}
Expand Down
2 changes: 1 addition & 1 deletion tests/E2E/_fixtures/configDependenciesLeak.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

$rule_1 = Rule::allClasses()
->that(new ResideInOneOfTheseNamespaces('App\DependenciesLeak\SecondModule'))
->should(new NotDependsOnTheseNamespaces('App\DependenciesLeak\FirstModule'))
->should(new NotDependsOnTheseNamespaces(['App\DependenciesLeak\FirstModule']))
->because('modules should be independent');

$config
Expand Down
2 changes: 1 addition & 1 deletion tests/E2E/_fixtures/configIgnoreBaselineLineNumbers.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
$rules = [
Rule::allClasses()
->that(new ResideInOneOfTheseNamespaces('App\Application'))
->should(new DependsOnlyOnTheseNamespaces('App\Application'))
->should(new DependsOnlyOnTheseNamespaces(['App\Application']))
->because('That is how I want it'),
];

Expand Down
2 changes: 1 addition & 1 deletion tests/Integration/CheckAttributeDependencyTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public function test_assertion_should_fail_on_invalid_dependency(): void

$rule = Rule::allClasses()
->that(new ResideInOneOfTheseNamespaces('App'))
->should(new NotDependsOnTheseNamespaces('App\Invalid'))
->should(new NotDependsOnTheseNamespaces(['App\Invalid']))
->because('i said so');

$runner->run($dir, $rule);
Expand Down
52 changes: 30 additions & 22 deletions tests/Unit/Analyzer/FileVisitorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@

class FileVisitorTest extends TestCase
{
public function test_should_parse_non_php_file(): void
{
$fp = FileParserFactory::createFileParser(TargetPhpVersion::create('7.4'));
$fp->parse('', 'path/to/class.php');

self::assertEmpty($fp->getClassDescriptions());
}

public function test_should_parse_empty_file(): void
{
$code = <<< 'EOF'
Expand Down Expand Up @@ -59,7 +67,7 @@ public function __construct(Request $request)

$violations = new Violations();

$dependsOnTheseNamespaces = new DependsOnlyOnTheseNamespaces('Foo');
$dependsOnTheseNamespaces = new DependsOnlyOnTheseNamespaces(['Foo']);
$dependsOnTheseNamespaces->evaluate($fp->getClassDescriptions()[0], $violations, 'because');

self::assertCount(2, $violations);
Expand Down Expand Up @@ -231,7 +239,7 @@ public function __construct(Request $request)

$violations = new Violations();

$dependsOnTheseNamespaces = new DependsOnlyOnTheseNamespaces('Foo', 'Symfony', 'Doctrine');
$dependsOnTheseNamespaces = new DependsOnlyOnTheseNamespaces(['Foo', 'Symfony', 'Doctrine']);
$dependsOnTheseNamespaces->evaluate($cd[0], $violations, 'we want to add this rule for our software');

self::assertCount(0, $violations);
Expand Down Expand Up @@ -300,7 +308,7 @@ public function __construct()

$violations = new Violations();

$dependsOnTheseNamespaces = new DependsOnlyOnTheseNamespaces('Foo', 'Symfony', 'Doctrine');
$dependsOnTheseNamespaces = new DependsOnlyOnTheseNamespaces(['Foo', 'Symfony', 'Doctrine']);
$dependsOnTheseNamespaces->evaluate($cd[0], $violations, 'we want to add this rule for our software');

self::assertCount(0, $violations);
Expand Down Expand Up @@ -679,7 +687,7 @@ class ApplicationLevelDto

$violations = new Violations();

$notHaveDependencyOutsideNamespace = new DependsOnlyOnTheseNamespaces('MyProject\AppBundle\Application');
$notHaveDependencyOutsideNamespace = new DependsOnlyOnTheseNamespaces(['MyProject\AppBundle\Application']);
$notHaveDependencyOutsideNamespace->evaluate($cd[0], $violations, 'we want to add this rule for our software');

self::assertCount(1, $violations);
Expand Down Expand Up @@ -707,7 +715,7 @@ class ApplicationLevelDto

$violations = new Violations();

$notHaveDependencyOutsideNamespace = new DependsOnlyOnTheseNamespaces('MyProject\AppBundle\Application');
$notHaveDependencyOutsideNamespace = new DependsOnlyOnTheseNamespaces(['MyProject\AppBundle\Application']);
$notHaveDependencyOutsideNamespace->evaluate($cd[0], $violations, 'we want to add this rule for our software');

self::assertCount(1, $violations);
Expand Down Expand Up @@ -801,7 +809,7 @@ class ApplicationLevelDto

$violations = new Violations();

$notHaveDependencyOutsideNamespace = new DependsOnlyOnTheseNamespaces('MyProject\AppBundle\Application');
$notHaveDependencyOutsideNamespace = new DependsOnlyOnTheseNamespaces(['MyProject\AppBundle\Application']);
$notHaveDependencyOutsideNamespace->evaluate($cd[0], $violations, 'we want to add this rule for our software');

self::assertCount(1, $violations);
Expand Down Expand Up @@ -831,7 +839,7 @@ class ApplicationLevelDto

$violations = new Violations();

$notHaveDependencyOutsideNamespace = new DependsOnlyOnTheseNamespaces('MyProject\AppBundle\Application');
$notHaveDependencyOutsideNamespace = new DependsOnlyOnTheseNamespaces(['MyProject\AppBundle\Application']);
$notHaveDependencyOutsideNamespace->evaluate($cd[0], $violations, 'we want to add this rule for our software');

self::assertCount(1, $violations);
Expand Down Expand Up @@ -894,7 +902,7 @@ class ApplicationLevelDto

$violations = new Violations();

$dependsOnTheseNamespaces = new DependsOnlyOnTheseNamespaces('MyProject\AppBundle\Application');
$dependsOnTheseNamespaces = new DependsOnlyOnTheseNamespaces(['MyProject\AppBundle\Application']);
$dependsOnTheseNamespaces->evaluate($cd[0], $violations, 'we want to add this rule for our software');

self::assertCount(1, $violations);
Expand All @@ -920,7 +928,7 @@ public function getBookList(): QueryBuilder;

$violations = new Violations();

$dependsOnTheseNamespaces = new DependsOnlyOnTheseNamespaces('MyProject\AppBundle\Application');
$dependsOnTheseNamespaces = new DependsOnlyOnTheseNamespaces(['MyProject\AppBundle\Application']);
$dependsOnTheseNamespaces->evaluate($cd[0], $violations, 'we want to add this rule for our software');

self::assertCount(1, $violations);
Expand Down Expand Up @@ -991,7 +999,7 @@ public function getRequest(): Request //the violations is reported here

$violations = new Violations();

$dependsOnTheseNamespaces = new DependsOnlyOnTheseNamespaces('Foo', 'Symfony', 'Doctrine');
$dependsOnTheseNamespaces = new DependsOnlyOnTheseNamespaces(['Foo', 'Symfony', 'Doctrine']);
$dependsOnTheseNamespaces->evaluate($cd[0], $violations, 'we want to add this rule for our software');

self::assertCount(0, $violations);
Expand Down Expand Up @@ -1020,7 +1028,7 @@ class ApplicationLevelDto

$violations = new Violations();

$dependsOnlyOnTheseNamespaces = new DependsOnlyOnTheseNamespaces('MyProject\AppBundle\Application');
$dependsOnlyOnTheseNamespaces = new DependsOnlyOnTheseNamespaces(['MyProject\AppBundle\Application']);
$dependsOnlyOnTheseNamespaces->evaluate($cd[0], $violations, 'we want to add this rule for our software');

self::assertCount(0, $violations);
Expand Down Expand Up @@ -1079,7 +1087,7 @@ class MyClass

$violations = new Violations();

$dependsOnTheseNamespaces = new DependsOnlyOnTheseNamespaces('Domain');
$dependsOnTheseNamespaces = new DependsOnlyOnTheseNamespaces(['Domain']);
$dependsOnTheseNamespaces->evaluate($cd[0], $violations, 'we want to add this rule for our software');

self::assertCount(1, $violations);
Expand Down Expand Up @@ -1109,7 +1117,7 @@ class MyClass

$violations = new Violations();

$dependsOnTheseNamespaces = new DependsOnlyOnTheseNamespaces('Domain');
$dependsOnTheseNamespaces = new DependsOnlyOnTheseNamespaces(['Domain']);
$dependsOnTheseNamespaces->evaluate($cd[0], $violations, 'we want to add this rule for our software');

self::assertCount(1, $violations);
Expand Down Expand Up @@ -1139,7 +1147,7 @@ class MyClass

$violations = new Violations();

$dependsOnTheseNamespaces = new DependsOnlyOnTheseNamespaces('Domain');
$dependsOnTheseNamespaces = new DependsOnlyOnTheseNamespaces(['Domain']);
$dependsOnTheseNamespaces->evaluate($cd[0], $violations, 'we want to add this rule for our software');

self::assertCount(1, $violations);
Expand Down Expand Up @@ -1171,7 +1179,7 @@ public function __construct(array $dtoList)

$violations = new Violations();

$dependsOnTheseNamespaces = new DependsOnlyOnTheseNamespaces('Domain');
$dependsOnTheseNamespaces = new DependsOnlyOnTheseNamespaces(['Domain']);
$dependsOnTheseNamespaces->evaluate($cd[0], $violations, 'we want to add this rule for our software');

self::assertCount(1, $violations);
Expand Down Expand Up @@ -1203,7 +1211,7 @@ public function __construct(array $dtoList)

$violations = new Violations();

$dependsOnTheseNamespaces = new DependsOnlyOnTheseNamespaces('Domain');
$dependsOnTheseNamespaces = new DependsOnlyOnTheseNamespaces(['Domain']);
$dependsOnTheseNamespaces->evaluate($cd[0], $violations, 'we want to add this rule for our software');

self::assertCount(1, $violations);
Expand Down Expand Up @@ -1235,7 +1243,7 @@ public function __construct(array $dtoList)

$violations = new Violations();

$dependsOnTheseNamespaces = new DependsOnlyOnTheseNamespaces('Domain');
$dependsOnTheseNamespaces = new DependsOnlyOnTheseNamespaces(['Domain']);
$dependsOnTheseNamespaces->evaluate($cd[0], $violations, 'we want to add this rule for our software');

self::assertCount(1, $violations);
Expand Down Expand Up @@ -1270,7 +1278,7 @@ public function __construct(string $var1, array $dtoList, $var2, array $voList)

$violations = new Violations();

$dependsOnTheseNamespaces = new DependsOnlyOnTheseNamespaces('Domain');
$dependsOnTheseNamespaces = new DependsOnlyOnTheseNamespaces(['Domain']);
$dependsOnTheseNamespaces->evaluate($cd[0], $violations, 'we want to add this rule for our software');

self::assertCount(1, $violations);
Expand Down Expand Up @@ -1303,7 +1311,7 @@ public function getList(): array

$violations = new Violations();

$dependsOnTheseNamespaces = new DependsOnlyOnTheseNamespaces('Domain');
$dependsOnTheseNamespaces = new DependsOnlyOnTheseNamespaces(['Domain']);
$dependsOnTheseNamespaces->evaluate($cd[0], $violations, 'we want to add this rule for our software');

self::assertCount(1, $violations);
Expand Down Expand Up @@ -1336,7 +1344,7 @@ public function getList(): array

$violations = new Violations();

$dependsOnTheseNamespaces = new DependsOnlyOnTheseNamespaces('Domain');
$dependsOnTheseNamespaces = new DependsOnlyOnTheseNamespaces(['Domain']);
$dependsOnTheseNamespaces->evaluate($cd[0], $violations, 'we want to add this rule for our software');

self::assertCount(1, $violations);
Expand Down Expand Up @@ -1369,7 +1377,7 @@ public function getList(): array

$violations = new Violations();

$dependsOnTheseNamespaces = new DependsOnlyOnTheseNamespaces('Domain');
$dependsOnTheseNamespaces = new DependsOnlyOnTheseNamespaces(['Domain']);
$dependsOnTheseNamespaces->evaluate($cd[0], $violations, 'we want to add this rule for our software');

self::assertCount(1, $violations);
Expand Down Expand Up @@ -1398,7 +1406,7 @@ public function getBookList(): QueryBuilder

$violations = new Violations();

$dependsOnTheseNamespaces = new DependsOnlyOnTheseNamespaces('MyProject\AppBundle\Application');
$dependsOnTheseNamespaces = new DependsOnlyOnTheseNamespaces(['MyProject\AppBundle\Application']);
$dependsOnTheseNamespaces->evaluate($cd[0], $violations, 'we want to add this rule for our software');

self::assertCount(1, $violations);
Expand Down
6 changes: 3 additions & 3 deletions tests/Unit/Architecture/ArchitectureTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ public function test_layered_architecture(): void
$expectedRules = [
Rule::allClasses()
->that(new ResideInOneOfTheseNamespaces('App\*\Domain\*'))
->should(new NotDependsOnTheseNamespaces('App\*\Application\*', 'App\*\Infrastructure\*'))
->should(new NotDependsOnTheseNamespaces(['App\*\Application\*', 'App\*\Infrastructure\*']))
->because('of component architecture'),
Rule::allClasses()
->that(new ResideInOneOfTheseNamespaces('App\*\Application\*'))
->should(new NotDependsOnTheseNamespaces('App\*\Infrastructure\*'))
->should(new NotDependsOnTheseNamespaces(['App\*\Infrastructure\*']))
->because('of component architecture'),
];

Expand All @@ -50,7 +50,7 @@ public function test_layered_architecture_with_depends_only_on_components(): voi
$expectedRules = [
Rule::allClasses()
->that(new ResideInOneOfTheseNamespaces('App\*\Domain\*'))
->should(new DependsOnlyOnTheseNamespaces('App\*\Domain\*'))
->should(new DependsOnlyOnTheseNamespaces(['App\*\Domain\*']))
->because('of component architecture'),
];

Expand Down
Loading