diff --git a/CHANGELOG.md b/CHANGELOG.md index 13b23546..4ed246a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Metadata - Improved accuracy of metadata-related error messages - `ReflectorMetaSource` is marked as `@internal` + - Check that `Callback` returns correct `Args` class type - `Rule` - `resolveArgs()` accepts `ArgsFieldContext` instead of `ArgsContext` - all rules initialize `Type` lazily (performance optimization) diff --git a/src/Meta/MetaResolver.php b/src/Meta/MetaResolver.php index 2147a952..8d1bf97f 100644 --- a/src/Meta/MetaResolver.php +++ b/src/Meta/MetaResolver.php @@ -330,12 +330,20 @@ private function resolveCallbackMeta( ): CallbackRuntimeMeta { $type = $meta->getType(); + $args = $type::resolveArgs($meta->getArgs(), $context, $reflector); - return new CallbackRuntimeMeta( - $type, - $type::resolveArgs($meta->getArgs(), $context, $reflector), - $declaringClass, - ); + $argsType = $type::getArgsType(); + if (!is_a($args, $argsType)) { + $realArgsType = get_class($args); + + throw InvalidArgument::create() + ->withMessage( + "'{$type}::resolveArgs()' should return '$argsType' (as defined in 'getArgsType()' method)" + . ", but returns '$realArgsType'.", + ); + } + + return new CallbackRuntimeMeta($type, $args, $declaringClass); } /** @@ -405,13 +413,14 @@ public function resolveRuleMeta(RuleCompileMeta $meta, ArgsFieldContext $context $rule = $this->ruleManager->getRule($type); $args = $rule->resolveArgs($meta->getArgs(), $context); - if (!is_a($args, $rule->getArgsType())) { + $argsType = $rule->getArgsType(); + if (!is_a($args, $argsType)) { $ruleClass = get_class($rule); $realArgsType = get_class($args); throw InvalidArgument::create() ->withMessage( - "'{$ruleClass}->resolveArgs()' should return '{$rule->getArgsType()}' (as defined in 'getArgsType()' method)" + "'{$ruleClass}->resolveArgs()' should return '$argsType' (as defined in 'getArgsType()' method)" . ", but returns '$realArgsType'.", ); } diff --git a/tests/Doubles/Callbacks/WrongArgsTypeCallback.php b/tests/Doubles/Callbacks/WrongArgsTypeCallback.php new file mode 100644 index 00000000..5c562b26 --- /dev/null +++ b/tests/Doubles/Callbacks/WrongArgsTypeCallback.php @@ -0,0 +1,44 @@ + + */ +final class WrongArgsTypeCallback implements Callback +{ + + public static function resolveArgs(array $args, ArgsContext $context, Reflector $reflector): Args + { + return new NullArgs(false); + } + + public static function getArgsType(): string + { + /** @phpstan-ignore-next-line */ + return EmptyArgs::class; + } + + public static function invoke( + $data, + Args $args, + ObjectHolder $holder, + BaseFieldContext $context, + ReflectionClass $declaringClass + ): void + { + throw NotImplemented::create(); + } + +} diff --git a/tests/Doubles/Callbacks/WrongArgsTypeCallbackValue.php b/tests/Doubles/Callbacks/WrongArgsTypeCallbackValue.php new file mode 100644 index 00000000..17a674bc --- /dev/null +++ b/tests/Doubles/Callbacks/WrongArgsTypeCallbackValue.php @@ -0,0 +1,28 @@ + + * @implements Rule */ final class WrongArgsTypeRule implements Rule { public function resolveArgs(array $args, ArgsContext $context): Args { - return new EmptyArgs(); + return new NullArgs(false); } public function getArgsType(): string { /** @phpstan-ignore-next-line */ - return 'nonsense'; + return EmptyArgs::class; } public function processValue($value, Args $args, FieldContext $context) diff --git a/tests/Doubles/Rules/WrongArgsTypeValue.php b/tests/Doubles/Rules/WrongArgsTypeRuleValue.php similarity index 82% rename from tests/Doubles/Rules/WrongArgsTypeValue.php rename to tests/Doubles/Rules/WrongArgsTypeRuleValue.php index 2d1318a5..8286bfb0 100644 --- a/tests/Doubles/Rules/WrongArgsTypeValue.php +++ b/tests/Doubles/Rules/WrongArgsTypeRuleValue.php @@ -9,10 +9,10 @@ /** * @Annotation * @NamedArgumentConstructor() - * @Target({"PROPERTY", "ANNOTATION"}) + * @Target({"PROPERTY"}) */ #[Attribute(Attribute::TARGET_PROPERTY)] -final class WrongArgsTypeValue implements RuleDefinition +final class WrongArgsTypeRuleValue implements RuleDefinition { public function getType(): string diff --git a/tests/Unit/Meta/MetaResolverTest.php b/tests/Unit/Meta/MetaResolverTest.php index 618d47b3..5a0e7bb7 100644 --- a/tests/Unit/Meta/MetaResolverTest.php +++ b/tests/Unit/Meta/MetaResolverTest.php @@ -17,7 +17,8 @@ use Tests\Orisai\ObjectMapper\Doubles\Invalid\FieldTraitMetaInvalidScopeRootVO; use Tests\Orisai\ObjectMapper\Doubles\Invalid\MultipleIdenticalFieldNamesVO; use Tests\Orisai\ObjectMapper\Doubles\Invalid\StaticMappedPropertyVO; -use Tests\Orisai\ObjectMapper\Doubles\Invalid\WrongArgsTypeVO; +use Tests\Orisai\ObjectMapper\Doubles\Invalid\WrongCallbackArgsTypeVO; +use Tests\Orisai\ObjectMapper\Doubles\Invalid\WrongRuleArgsTypeVO; use Tests\Orisai\ObjectMapper\Doubles\Rules\WrongArgsTypeRule; use Tests\Orisai\ObjectMapper\Toolkit\ProcessingTestCase; @@ -194,17 +195,32 @@ public function testFieldNamesFromTrait(): void $this->metaLoader->load(FieldNamesFromTraitVO::class); } - public function testNotMatchingArgsType(): void + public function testWrongRuleArgsType(): void { $this->ruleManager->addRule(new WrongArgsTypeRule()); $this->expectException(InvalidArgument::class); $this->expectExceptionMessage( - "'Tests\Orisai\ObjectMapper\Doubles\Rules\WrongArgsTypeRule->resolveArgs()' should return 'nonsense'" - . " (as defined in 'getArgsType()' method), but returns 'Orisai\ObjectMapper\Args\EmptyArgs'.", + "'Tests\Orisai\ObjectMapper\Doubles\Rules\WrongArgsTypeRule->resolveArgs()'" + . " should return 'Orisai\ObjectMapper\Args\EmptyArgs'" + . " (as defined in 'getArgsType()' method)," + . " but returns 'Orisai\ObjectMapper\Rules\NullArgs'.", ); - $this->metaLoader->load(WrongArgsTypeVO::class); + $this->metaLoader->load(WrongRuleArgsTypeVO::class); + } + + public function testWrongCallbackArgsType(): void + { + $this->expectException(InvalidArgument::class); + $this->expectExceptionMessage( + "'Tests\Orisai\ObjectMapper\Doubles\Callbacks\WrongArgsTypeCallback::resolveArgs()'" + . " should return 'Orisai\ObjectMapper\Args\EmptyArgs'" + . " (as defined in 'getArgsType()' method)," + . " but returns 'Orisai\ObjectMapper\Rules\NullArgs'.", + ); + + $this->metaLoader->load(WrongCallbackArgsTypeVO::class); } }