Skip to content
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
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\Excluded']))
->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