Skip to content

Commit e7a9b6a

Browse files
Allow to exclude some namespaces in NotDependsOnTheseNamespaces and DependsOnlyOnTheseNamespaces (#501)
1 parent 575f131 commit e7a9b6a

File tree

11 files changed

+111
-54
lines changed

11 files changed

+111
-54
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ Currently, you can check if a class:
166166
```php
167167
$rules[] = Rule::allClasses()
168168
->that(new ResideInOneOfTheseNamespaces('App\Domain'))
169-
->should(new DependsOnlyOnTheseNamespaces('App\Domain', 'Ramsey\Uuid'))
169+
->should(new DependsOnlyOnTheseNamespaces(['App\Domain', 'Ramsey\Uuid'], ['App\Excluded']))
170170
->because('we want to protect our domain from external dependencies except for Ramsey\Uuid');
171171
```
172172

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

src/Expression/ForClasses/DependsOnlyOnTheseNamespaces.php

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,13 @@ class DependsOnlyOnTheseNamespaces implements Expression
1717
/** @var array<string> */
1818
private array $namespaces;
1919

20-
public function __construct(string ...$namespace)
20+
/** @var array<string> */
21+
private array $exclude;
22+
23+
public function __construct(array $namespaces = [], array $exclude = [])
2124
{
22-
$this->namespaces = $namespace;
25+
$this->namespaces = $namespaces;
26+
$this->exclude = $exclude;
2327
}
2428

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

3640
/** @var ClassDependency $dependency */
3741
foreach ($dependencies as $dependency) {
38-
if (
39-
'' === $dependency->getFQCN()->namespace()
40-
|| $theClass->namespaceMatches($dependency->getFQCN()->namespace())
41-
) {
42-
continue;
42+
if ('' === $dependency->getFQCN()->namespace()) {
43+
continue; // skip root namespace
44+
}
45+
46+
if ($theClass->namespaceMatches($dependency->getFQCN()->namespace())) {
47+
continue; // skip classes in the same namespace
48+
}
49+
50+
if ($dependency->matchesOneOf(...$this->exclude)) {
51+
continue; // skip excluded namespaces
4352
}
4453

4554
if (!$dependency->matchesOneOf(...$this->namespaces)) {

src/Expression/ForClasses/NotDependsOnTheseNamespaces.php

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,13 @@ class NotDependsOnTheseNamespaces implements Expression
1717
/** @var array<string> */
1818
private array $namespaces;
1919

20-
public function __construct(string ...$namespace)
20+
/** @var array<string> */
21+
private array $exclude;
22+
23+
public function __construct(array $namespaces, array $exclude = [])
2124
{
22-
$this->namespaces = $namespace;
25+
$this->namespaces = $namespaces;
26+
$this->exclude = $exclude;
2327
}
2428

2529
public function describe(ClassDescription $theClass, string $because): Description
@@ -36,7 +40,11 @@ public function evaluate(ClassDescription $theClass, Violations $violations, str
3640
/** @var ClassDependency $dependency */
3741
foreach ($dependencies as $dependency) {
3842
if ('' === $dependency->getFQCN()->namespace()) {
39-
continue;
43+
continue; // skip root namespace
44+
}
45+
46+
if ($dependency->matchesOneOf(...$this->exclude)) {
47+
continue; // skip excluded namespaces
4048
}
4149

4250
if ($dependency->matchesOneOf(...$this->namespaces)) {

src/RuleBuilders/Architecture/Architecture.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,13 @@ public function rules(string $because = 'of component architecture'): iterable
9090
$forbiddenComponents = array_diff($layerNames, [$name], $this->allowedDependencies[$name]);
9191

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

9797
yield Rule::allClasses()
9898
->that(new ResideInOneOfTheseNamespaces($selector))
99-
->should(new NotDependsOnTheseNamespaces(...$forbiddenSelectors))
99+
->should(new NotDependsOnTheseNamespaces($forbiddenSelectors))
100100
->because($because);
101101
}
102102
}
@@ -105,13 +105,13 @@ public function rules(string $because = 'of component architecture'): iterable
105105
continue;
106106
}
107107

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

112112
yield Rule::allClasses()
113113
->that(new ResideInOneOfTheseNamespaces($selector))
114-
->should(new DependsOnlyOnTheseNamespaces(...$allowedDependencies))
114+
->should(new DependsOnlyOnTheseNamespaces($allowedDependencies))
115115
->because($because);
116116
}
117117
}

tests/E2E/_fixtures/configDependenciesLeak.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

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

1818
$config

tests/E2E/_fixtures/configIgnoreBaselineLineNumbers.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
$rules = [
1616
Rule::allClasses()
1717
->that(new ResideInOneOfTheseNamespaces('App\Application'))
18-
->should(new DependsOnlyOnTheseNamespaces('App\Application'))
18+
->should(new DependsOnlyOnTheseNamespaces(['App\Application']))
1919
->because('That is how I want it'),
2020
];
2121

tests/Integration/CheckAttributeDependencyTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public function test_assertion_should_fail_on_invalid_dependency(): void
2121

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

2727
$runner->run($dir, $rule);

tests/Unit/Analyzer/FileVisitorTest.php

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,14 @@
2323

2424
class FileVisitorTest extends TestCase
2525
{
26+
public function test_should_parse_non_php_file(): void
27+
{
28+
$fp = FileParserFactory::createFileParser(TargetPhpVersion::create('7.4'));
29+
$fp->parse('', 'path/to/class.php');
30+
31+
self::assertEmpty($fp->getClassDescriptions());
32+
}
33+
2634
public function test_should_parse_empty_file(): void
2735
{
2836
$code = <<< 'EOF'
@@ -59,7 +67,7 @@ public function __construct(Request $request)
5967

6068
$violations = new Violations();
6169

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

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

232240
$violations = new Violations();
233241

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

237245
self::assertCount(0, $violations);
@@ -300,7 +308,7 @@ public function __construct()
300308

301309
$violations = new Violations();
302310

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

306314
self::assertCount(0, $violations);
@@ -679,7 +687,7 @@ class ApplicationLevelDto
679687

680688
$violations = new Violations();
681689

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

685693
self::assertCount(1, $violations);
@@ -707,7 +715,7 @@ class ApplicationLevelDto
707715

708716
$violations = new Violations();
709717

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

713721
self::assertCount(1, $violations);
@@ -801,7 +809,7 @@ class ApplicationLevelDto
801809

802810
$violations = new Violations();
803811

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

807815
self::assertCount(1, $violations);
@@ -831,7 +839,7 @@ class ApplicationLevelDto
831839

832840
$violations = new Violations();
833841

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

837845
self::assertCount(1, $violations);
@@ -894,7 +902,7 @@ class ApplicationLevelDto
894902

895903
$violations = new Violations();
896904

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

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

921929
$violations = new Violations();
922930

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

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

9921000
$violations = new Violations();
9931001

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

9971005
self::assertCount(0, $violations);
@@ -1020,7 +1028,7 @@ class ApplicationLevelDto
10201028

10211029
$violations = new Violations();
10221030

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

10261034
self::assertCount(0, $violations);
@@ -1079,7 +1087,7 @@ class MyClass
10791087

10801088
$violations = new Violations();
10811089

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

10851093
self::assertCount(1, $violations);
@@ -1109,7 +1117,7 @@ class MyClass
11091117

11101118
$violations = new Violations();
11111119

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

11151123
self::assertCount(1, $violations);
@@ -1139,7 +1147,7 @@ class MyClass
11391147

11401148
$violations = new Violations();
11411149

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

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

11721180
$violations = new Violations();
11731181

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

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

12041212
$violations = new Violations();
12051213

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

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

12361244
$violations = new Violations();
12371245

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

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

12711279
$violations = new Violations();
12721280

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

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

13041312
$violations = new Violations();
13051313

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

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

13371345
$violations = new Violations();
13381346

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

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

13701378
$violations = new Violations();
13711379

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

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

13991407
$violations = new Violations();
14001408

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

14041412
self::assertCount(1, $violations);

tests/Unit/Architecture/ArchitectureTest.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@ public function test_layered_architecture(): void
2828
$expectedRules = [
2929
Rule::allClasses()
3030
->that(new ResideInOneOfTheseNamespaces('App\*\Domain\*'))
31-
->should(new NotDependsOnTheseNamespaces('App\*\Application\*', 'App\*\Infrastructure\*'))
31+
->should(new NotDependsOnTheseNamespaces(['App\*\Application\*', 'App\*\Infrastructure\*']))
3232
->because('of component architecture'),
3333
Rule::allClasses()
3434
->that(new ResideInOneOfTheseNamespaces('App\*\Application\*'))
35-
->should(new NotDependsOnTheseNamespaces('App\*\Infrastructure\*'))
35+
->should(new NotDependsOnTheseNamespaces(['App\*\Infrastructure\*']))
3636
->because('of component architecture'),
3737
];
3838

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

0 commit comments

Comments
 (0)