diff --git a/README.md b/README.md index dfbfa942..bc9ec14c 100644 --- a/README.md +++ b/README.md @@ -286,11 +286,13 @@ return static function (Config $config): void { ->component('Service')->definedBy('App\Service\*') ->component('Repository')->definedBy('App\Repository\*') ->component('Entity')->definedBy('App\Entity\*') + ->component('Domain')->definedBy('App\Domain\*') ->where('Controller')->mayDependOnComponents('Service', 'Entity') ->where('Service')->mayDependOnComponents('Repository', 'Entity') ->where('Repository')->mayDependOnComponents('Entity') ->where('Entity')->shouldNotDependOnAnyComponent() + ->where('Domain')->shouldOnlyDependOnComponents('Domain') ->rules(); diff --git a/src/RuleBuilders/Architecture/Architecture.php b/src/RuleBuilders/Architecture/Architecture.php index 30d7ce19..a1b38832 100644 --- a/src/RuleBuilders/Architecture/Architecture.php +++ b/src/RuleBuilders/Architecture/Architecture.php @@ -3,11 +3,12 @@ namespace Arkitect\RuleBuilders\Architecture; +use Arkitect\Expression\ForClasses\DependsOnlyOnTheseNamespaces; use Arkitect\Expression\ForClasses\NotDependsOnTheseNamespaces; use Arkitect\Expression\ForClasses\ResideInOneOfTheseNamespaces; use Arkitect\Rules\Rule; -class Architecture implements Component, DefinedBy, Where, MayDependOnComponents, MayDependOnAnyComponent, ShouldNotDependOnAnyComponent, Rules +class Architecture implements Component, DefinedBy, Where, MayDependOnComponents, MayDependOnAnyComponent, ShouldNotDependOnAnyComponent, ShouldOnlyDependOnComponents, Rules { /** @var string */ private $componentName; @@ -15,12 +16,15 @@ class Architecture implements Component, DefinedBy, Where, MayDependOnComponents private $componentSelectors; /** @var array */ private $allowedDependencies; + /** @var array */ + private $componentDependsOnlyOnTheseNamespaces; private function __construct() { $this->componentName = ''; $this->componentSelectors = []; $this->allowedDependencies = []; + $this->componentDependsOnlyOnTheseNamespaces = []; } public static function withComponents(): Component @@ -38,7 +42,6 @@ public function component(string $name): DefinedBy public function definedBy(string $selector) { $this->componentSelectors[$this->componentName] = $selector; - $this->allowedDependencies[$this->componentName] = []; return $this; } @@ -57,6 +60,13 @@ public function shouldNotDependOnAnyComponent() return $this; } + public function shouldOnlyDependOnComponents(string ...$componentNames) + { + $this->componentDependsOnlyOnTheseNamespaces[$this->componentName] = $componentNames; + + return $this; + } + public function mayDependOnComponents(string ...$componentNames) { $this->allowedDependencies[$this->componentName] = $componentNames; @@ -76,19 +86,32 @@ public function rules(): iterable $layerNames = array_keys($this->componentSelectors); foreach ($this->componentSelectors as $name => $selector) { - $forbiddenComponents = array_diff($layerNames, [$name], $this->allowedDependencies[$name]); + if (isset($this->allowedDependencies[$name])) { + $forbiddenComponents = array_diff($layerNames, [$name], $this->allowedDependencies[$name]); + + if (!empty($forbiddenComponents)) { + $forbiddenSelectors = array_map(function (string $componentName): string { + return $this->componentSelectors[$componentName]; + }, $forbiddenComponents); + + yield Rule::allClasses() + ->that(new ResideInOneOfTheseNamespaces($selector)) + ->should(new NotDependsOnTheseNamespaces(...$forbiddenSelectors)) + ->because('of component architecture'); + } + } - if (empty($forbiddenComponents)) { + if (!isset($this->componentDependsOnlyOnTheseNamespaces[$name])) { continue; } - $forbiddenSelectors = array_map(function (string $componentName): string { + $allowedDependencies = array_map(function (string $componentName): string { return $this->componentSelectors[$componentName]; - }, $forbiddenComponents); + }, $this->componentDependsOnlyOnTheseNamespaces[$name]); yield Rule::allClasses() ->that(new ResideInOneOfTheseNamespaces($selector)) - ->should(new NotDependsOnTheseNamespaces(...$forbiddenSelectors)) + ->should(new DependsOnlyOnTheseNamespaces(...$allowedDependencies)) ->because('of component architecture'); } } diff --git a/src/RuleBuilders/Architecture/ShouldOnlyDependOnComponents.php b/src/RuleBuilders/Architecture/ShouldOnlyDependOnComponents.php new file mode 100644 index 00000000..021a32d0 --- /dev/null +++ b/src/RuleBuilders/Architecture/ShouldOnlyDependOnComponents.php @@ -0,0 +1,10 @@ +component('Domain')->definedBy('App\*\Domain\*') + ->where('Domain')->shouldOnlyDependOnComponents('Domain') + + ->rules(); + + $expectedRules = [ + Rule::allClasses() + ->that(new ResideInOneOfTheseNamespaces('App\*\Domain\*')) + ->should(new DependsOnlyOnTheseNamespaces('App\*\Domain\*')) + ->because('of component architecture'), + ]; + + self::assertEquals($expectedRules, iterator_to_array($rules)); + } }