diff --git a/src/Compiler/Exception/CannotCreateMapperCompilerException.php b/src/Compiler/Exception/CannotCreateMapperCompilerException.php index 5fffc9b..2cf08f5 100644 --- a/src/Compiler/Exception/CannotCreateMapperCompilerException.php +++ b/src/Compiler/Exception/CannotCreateMapperCompilerException.php @@ -4,6 +4,7 @@ use LogicException; use PHPStan\PhpDocParser\Ast\Type\TypeNode; +use ReflectionParameter; use ShipMonk\InputMapper\Compiler\Mapper\MapperCompiler; use ShipMonk\InputMapper\Compiler\Validator\ValidatorCompiler; use Throwable; @@ -17,6 +18,25 @@ public static function fromType(TypeNode $type, ?string $reason = null, ?Throwab return new self("Cannot create mapper for type {$type}{$reason}", 0, $previous); } + public static function withIncompatibleMapperForMethodParameter( + MapperCompiler $mapperCompiler, + ReflectionParameter $parameter, + TypeNode $parameterType, + ?Throwable $previous = null + ): self + { + $mapperCompilerClass = $mapperCompiler::class; + $mapperOutputType = $mapperCompiler->getOutputType(); + + $parameterName = $parameter->getName(); + $className = $parameter->getDeclaringClass()?->getName(); + $methodName = $parameter->getDeclaringFunction()->getName(); + $methodFullName = $className !== null ? "{$className}::{$methodName}" : $methodName; + + $reason = "mapper output type '{$mapperOutputType}' is not compatible with parameter type '{$parameterType}'"; + return new self("Cannot use mapper {$mapperCompilerClass} for parameter \${$parameterName} of method {$methodFullName}, because {$reason}", 0, $previous); + } + public static function withIncompatibleValidator( ValidatorCompiler $validatorCompiler, MapperCompiler $mapperCompiler, diff --git a/src/Compiler/MapperFactory/DefaultMapperCompilerFactory.php b/src/Compiler/MapperFactory/DefaultMapperCompilerFactory.php index 748e983..5452847 100644 --- a/src/Compiler/MapperFactory/DefaultMapperCompilerFactory.php +++ b/src/Compiler/MapperFactory/DefaultMapperCompilerFactory.php @@ -317,6 +317,10 @@ protected function createParameterMapperCompiler( default => new ChainMapperCompiler($mappers), }; + if (!PhpDocTypeUtils::isSubTypeOf($mapper->getOutputType(), $type)) { + throw CannotCreateMapperCompilerException::withIncompatibleMapperForMethodParameter($mapper, $parameterReflection, $type); + } + foreach ($validators as $validator) { $mapper = $this->addValidator($mapper, $validator); } diff --git a/tests/Compiler/MapperFactory/Data/InputWithIncompatibleMapperCompiler.php b/tests/Compiler/MapperFactory/Data/InputWithIncompatibleMapperCompiler.php new file mode 100644 index 0000000..0f49b26 --- /dev/null +++ b/tests/Compiler/MapperFactory/Data/InputWithIncompatibleMapperCompiler.php @@ -0,0 +1,17 @@ + [ + InputWithIncompatibleMapperCompiler::class, + [], + 'Cannot use mapper ShipMonk\InputMapper\Compiler\Mapper\Scalar\MapString for parameter $id of method ShipMonkTests\InputMapper\Compiler\MapperFactory\Data\InputWithIncompatibleMapperCompiler::__construct, because mapper output type \'string\' is not compatible with parameter type \'int\'', + ]; + yield 'DateTime' => [ DateTime::class, [DefaultMapperCompilerFactory::DELEGATE_OBJECT_MAPPING => false],