diff --git a/src/Enum/RuleIdentifier.php b/src/Enum/RuleIdentifier.php index 9f84e80e..380fa315 100644 --- a/src/Enum/RuleIdentifier.php +++ b/src/Enum/RuleIdentifier.php @@ -160,4 +160,9 @@ final class RuleIdentifier * @var string */ public const SYMFONY_REQUIRE_INVOKABLE_CONTROLLER = 'symfony.requireInvokableController'; + + /** + * @var string + */ + public const NO_VALUE_OBJECT_IN_SERVICE_CONSTRUCTOR = 'symplify.noValueObjectInServiceConstructor'; } diff --git a/src/Rules/NoValueObjectInServiceConstructorRule.php b/src/Rules/NoValueObjectInServiceConstructorRule.php new file mode 100644 index 00000000..d97b3f3e --- /dev/null +++ b/src/Rules/NoValueObjectInServiceConstructorRule.php @@ -0,0 +1,72 @@ + + */ +final class NoValueObjectInServiceConstructorRule implements Rule +{ + public function getNodeType(): string + { + return ClassMethod::class; + } + + /** + * @param ClassMethod $node + */ + public function processNode(Node $node, Scope $scope): array + { + if ($node->name->toString() !== '__construct') { + return []; + } + + if (! $scope->isInClass()) { + return []; + } + + $classReflection = $scope->getClassReflection(); + + // value objects can accept value objects + if ($this->isValueObject($classReflection->getName())) { + return []; + } + + $ruleErrors = []; + + foreach ($node->params as $param) { + if (! $param->type instanceof Name) { + continue; + } + + $paramType = $param->type->toString(); + if (! $this->isValueObject($paramType)) { + continue; + } + + $ruleErrors[] = RuleErrorBuilder::message(sprintf( + 'Value object "%s" cannot be passed to constructor of a service. Pass it as a method argument instead', + $paramType + )) + ->identifier(RuleIdentifier::NO_VALUE_OBJECT_IN_SERVICE_CONSTRUCTOR) + ->build(); + } + + return $ruleErrors; + } + + private function isValueObject(string $className): bool + { + return preg_match('#(ValueObject|DataObject|Models)#', $className) === 1; + } +}