diff --git a/README.md b/README.md index c9659c4e..63936e12 100644 --- a/README.md +++ b/README.md @@ -152,6 +152,23 @@ For example: `phparkitect debug:expression ResideInOneOfTheseNamespaces App` Currently, you can check if a class: +### Is referenced in a given map + +This is useful, for example, to ensure that DTOs like commands and +events are always set in a map, so that we are sure a serializer knows how +to serialize/deserialize them. + +```php +$map = [ + 'a' => 'App\Core\Component\MyComponent\Command\MyCommand', + 'b' => 'App\Core\Component\MyComponent\Event\MyEvent', +]; +$rules = Rule::allClasses() + ->that(new ResideInOneOfTheseNamespaces('App\Core\Component\**\Command', 'App\Core\Component\**\Event')) + ->should(new IsMapped($map)) + ->because('we want to ensure our serializer can serialize/deserialize all commands and events'); +``` + ### Depends on a namespace ```php diff --git a/src/Expression/ForClasses/IsMapped.php b/src/Expression/ForClasses/IsMapped.php new file mode 100644 index 00000000..70cd0f77 --- /dev/null +++ b/src/Expression/ForClasses/IsMapped.php @@ -0,0 +1,44 @@ +list = array_flip($list); + } + + public function describe(ClassDescription $theClass, string $because): Description + { + return new Description(self::POSITIVE_DESCRIPTION, $because); + } + + public function evaluate(ClassDescription $theClass, Violations $violations, string $because): void + { + if (isset($this->list[$theClass->getFQCN()])) { + return; + } + + $violation = Violation::create( + $theClass->getFQCN(), + ViolationMessage::selfExplanatory($this->describe($theClass, $because)) + ); + + $violations->add($violation); + } +} diff --git a/tests/Unit/Expressions/ForClasses/IsMappedTest.php b/tests/Unit/Expressions/ForClasses/IsMappedTest.php new file mode 100644 index 00000000..3479dc28 --- /dev/null +++ b/tests/Unit/Expressions/ForClasses/IsMappedTest.php @@ -0,0 +1,46 @@ + $listedFqcn, + 'b' => 'App\SharedKernel\Component\MyOtherClass', + ]; + $expression = new IsMapped($list); + $classDescription = (new ClassDescriptionBuilder())->setClassName($listedFqcn)->build(); + + $violations = new Violations(); + $expression->evaluate($classDescription, $violations, ''); + + self::assertEquals(0, $violations->count()); + } + + public function test_it_should_add_violation_if_fqcn_is_not_in_list(): void + { + $nonListedFqcn = 'App\SharedKernel\Component\MyClass'; + $list = [ + 'a' => 'App\SharedKernel\Component\SomeClass', + 'b' => 'App\SharedKernel\Component\MyOtherClass', + ]; + $expression = new IsMapped($list); + $classDescription = (new ClassDescriptionBuilder())->setClassName($nonListedFqcn)->build(); + + $violations = new Violations(); + $expression->evaluate($classDescription, $violations, ''); + + self::assertEquals(1, $violations->count()); + self::assertEquals(IsMapped::POSITIVE_DESCRIPTION, $expression->describe($classDescription, '')->toString()); + } +}