Closed Nightbr closed 3 years ago
https://github.com/api-platform/core/pull/2757 might help you fix this I'm also okay for a fix in the validation but it may be wrong to not validate on these simple criteria :|.
Hey, thanks for the quick answer!
Sure if we can control the listener it will handle our use case perfectly and be less intrusive in the Validation process ;)
I think we can say: No validation if No attribute & No data. But I'm not sure if there is some drawback to do this.
If your input DTO has no attributes, you must send an empty JSON object, not an empty body.
I tried with an empty DTO and send an empty JSON object {} but still an error: ResendValidationMailCommand.php (UserCommandInterface is just an empty interface)
<?php
declare(strict_types=1);
namespace App\Domain\User\Command;
/**
* Class ResendValidationMailCommand.
*/
class ResendValidationMailCommand implements UserCommandInterface
{
}
Response:
{
"@context": "\/api\/contexts\/Error",
"@type": "hydra:Error",
"hydra:title": "An error `occurred",`
"hydra:description": "There is no PropertyInfo extractor supporting the class \"App\\Domain\\User\\Command\\ResendValidationMailCommand\".",
"trace": [
{
"namespace": "",```
"short_class": "",
"class": "",
"type": "",
"function": "",
"file": "\/var\/www\/vendor\/api-platform\/core\/src\/Bridge\/Symfony\/PropertyInfo\/Metadata\/Property\/PropertyInfoPropertyNameCollectionFactory.php",
"line": 46,
"args": []
},
{
"namespace": "ApiPlatform\\Core\\Bridge\\Symfony\\PropertyInfo\\Metadata\\Property",
"short_class": "PropertyInfoPropertyNameCollectionFactory",
"class": "ApiPlatform\\Core\\Bridge\\Symfony\\PropertyInfo\\Metadata\\Property\\PropertyInfoPropertyNameCollectionFactory",
"type": "->",
"function": "create",
"file": "\/var\/www\/vendor\/api-platform\/core\/src\/Metadata\/Property\/Factory\/InheritedPropertyNameCollectionFactory.php",
"line": 44,
"args": [
[
"string",
"App\\Domain\\User\\Command\\ResendValidationMailCommand"
],
[
"array",
{
"collection_operation_name": [
"string",
"postResendValidationMail"
]
}
]
]
},
{
"namespace": "ApiPlatform\\Core\\Metadata\\Property\\Factory",
"short_class": "InheritedPropertyNameCollectionFactory",
"class": "ApiPlatform\\Core\\Metadata\\Property\\Factory\\InheritedPropertyNameCollectionFactory",
"type": "->",
"function": "create",
"file": "\/var\/www\/vendor\/api-platform\/core\/src\/Metadata\/Property\/Factory\/ExtractorPropertyNameCollectionFactory.php",
"line": 50,
"args": [
[
"string",
"App\\Domain\\User\\Command\\ResendValidationMailCommand"
],
[
"array",
{
"collection_operation_name": [
"string",
"postResendValidationMail"
]
}
]
]
},
{
"namespace": "ApiPlatform\\Core\\Metadata\\Property\\Factory",
"short_class": "ExtractorPropertyNameCollectionFactory",
"class": "ApiPlatform\\Core\\Metadata\\Property\\Factory\\ExtractorPropertyNameCollectionFactory",
"type": "->",
"function": "create",
"file": "\/var\/www\/vendor\/api-platform\/core\/src\/Metadata\/Property\/Factory\/ExtractorPropertyNameCollectionFactory.php",
"line": 50,
"args": [
[
"string",
"App\\Domain\\User\\Command\\ResendValidationMailCommand"
],
[
"array",
{
"collection_operation_name": [
"string",
"postResendValidationMail"
]
}
]
]
},
{
"namespace": "ApiPlatform\\Core\\Metadata\\Property\\Factory",
"short_class": "ExtractorPropertyNameCollectionFactory",
"class": "ApiPlatform\\Core\\Metadata\\Property\\Factory\\ExtractorPropertyNameCollectionFactory",
"type": "->",
"function": "create",
"file": "\/var\/www\/vendor\/api-platform\/core\/src\/Metadata\/Property\/Factory\/CachedPropertyNameCollectionFactory.php",
"line": 47,
"args": [
[
"string",
"App\\Domain\\User\\Command\\ResendValidationMailCommand"
],
[
"array",
{
"collection_operation_name": [
"string",
"postResendValidationMail"
]
}
]
]
},
{
"namespace": "ApiPlatform\\Core\\Metadata\\Property\\Factory",
"short_class": "CachedPropertyNameCollectionFactory",
"class": "ApiPlatform\\Core\\Metadata\\Property\\Factory\\CachedPropertyNameCollectionFactory",
"type": "->",
"function": "ApiPlatform\\Core\\Metadata\\Property\\Factory\\{closure}",
"file": "\/var\/www\/vendor\/api-platform\/core\/src\/Cache\/CachedTrait.php",
"line": 44,
"args": []
},
{
"namespace": "ApiPlatform\\Core\\Metadata\\Property\\Factory",
"short_class": "CachedPropertyNameCollectionFactory",
"class": "ApiPlatform\\Core\\Metadata\\Property\\Factory\\CachedPropertyNameCollectionFactory",
"type": "->",
"function": "getCached",
"file": "\/var\/www\/vendor\/api-platform\/core\/src\/Metadata\/Property\/Factory\/CachedPropertyNameCollectionFactory.php",
"line": 48,
"args": [
[
"string",
"property_name_collection_24a6f81e1e9443f5e1f3be6986ae3e8d"
],
[
"object",
"Closure"
]
]
},
{
"namespace": "ApiPlatform\\Core\\Metadata\\Property\\Factory",
"short_class": "CachedPropertyNameCollectionFactory",
"class": "ApiPlatform\\Core\\Metadata\\Property\\Factory\\CachedPropertyNameCollectionFactory",
"type": "->",
"function": "create",
"file": "\/var\/www\/vendor\/api-platform\/core\/src\/Serializer\/AbstractItemNormalizer.php",
"line": 291,
"args": [
[
"string",
"App\\Domain\\User\\Command\\ResendValidationMailCommand"
],
[
"array",
{
"collection_operation_name": [
"string",
"postResendValidationMail"
]
}
]
]
},
{
"namespace": "ApiPlatform\\Core\\Serializer",
"short_class": "AbstractItemNormalizer",
"class": "ApiPlatform\\Core\\Serializer\\AbstractItemNormalizer",
"type": "->",
"function": "getAllowedAttributes",
"file": "\/var\/www\/vendor\/symfony\/serializer\/Normalizer\/AbstractObjectNormalizer.php",
"line": 278,
"args": [
[
"string",
"App\\Domain\\User\\Command\\ResendValidationMailCommand"
],
[
"array",
{
"resource_class": [
"string",
"App\\Domain\\User\\Command\\ResendValidationMailCommand"
],
"operation_type": [
"string",
"collection"
],
"collection_operation_name": [
"string",
"postResendValidationMail"
],
"api_allow_update": [
"boolean",
false
],
"input": [
"array",
{
"class": [
"string",
"App\\Domain\\User\\Command\\ResendValidationMailCommand"
],
"name": [
"string",
"ResendValidationMailCommand"
]
}
],
"output": [
"array",
{
"class": [
"null",
null
]
}
],
"request_uri": [
"string",
"\/api\/users\/resend_validation_mail"
],
"uri": [
"string",
"http:\/\/api.renters.test\/api\/users\/resend_validation_mail"
],
"api_denormalize": [
"boolean",
true
],
"cache_key": [
"string",
"be175295861aa5aa1e028b51fff958fe"
]
}
],
[
"boolean",
true
]
]
},
{
"namespace": "Symfony\\Component\\Serializer\\Normalizer",
"short_class": "AbstractObjectNormalizer",
"class": "Symfony\\Component\\Serializer\\Normalizer\\AbstractObjectNormalizer",
"type": "->",
"function": "denormalize",
"file": "\/var\/www\/vendor\/api-platform\/core\/src\/Serializer\/AbstractItemNormalizer.php",
"line": 179,
"args": [
[
"array",
[]
],
[
"string",
"App\\Domain\\User\\Command\\ResendValidationMailCommand"
],
[
"string",
"json"
],
[
"array",
{
"resource_class": [
"string",
"App\\Domain\\User\\Command\\ResendValidationMailCommand"
],
"operation_type": [
"string",
"collection"
],
"collection_operation_name": [
"string",
"postResendValidationMail"
],
"api_allow_update": [
"boolean",
false
],
"input": [
"array",
{
"class": [
"string",
"App\\Domain\\User\\Command\\ResendValidationMailCommand"
],
"name": [
"string",
"ResendValidationMailCommand"
]
}
],
"output": [
"array",
{
"class": [
"null",
null
]
}
],
"request_uri": [
"string",
"\/api\/users\/resend_validation_mail"
],
"uri": [
"string",
"http:\/\/api.renters.test\/api\/users\/resend_validation_mail"
],
"api_denormalize": [
"boolean",
true
],
"cache_key": [
"string",
"be175295861aa5aa1e028b51fff958fe"
]
}
]
]
},
{
"namespace": "ApiPlatform\\Core\\Serializer",
"short_class": "AbstractItemNormalizer",
"class": "ApiPlatform\\Core\\Serializer\\AbstractItemNormalizer",
"type": "->",
"function": "denormalize",
"file": "\/var\/www\/vendor\/api-platform\/core\/src\/Serializer\/ItemNormalizer.php",
"line": 71,
"args": [
[
"array",
[]
],
[
"string",
"App\\Infrastructure\\Doctrine\\Projection\\User"
],
[
"string",
"json"
],
[
"array",
{
"operation_type": [
"string",
"collection"
],
"collection_operation_name": [
"string",
"postResendValidationMail"
],
"api_allow_update": [
"boolean",
false
],
"resource_class": [
"string",
"App\\Infrastructure\\Doctrine\\Projection\\User"
],
"input": [
"array",
{
"class": [
"string",
"App\\Domain\\User\\Command\\ResendValidationMailCommand"
],
"name": [
"string",
"ResendValidationMailCommand"
]
}
],
"output": [
"array",
{
"class": [
"null",
null
]
}
],
"request_uri": [
"string",
"\/api\/users\/resend_validation_mail"
],
"uri": [
"string",
"http:\/\/api.renters.test\/api\/users\/resend_validation_mail"
],
"api_denormalize": [
"boolean",
true
]
}
]
]
},
{
"namespace": "ApiPlatform\\Core\\Serializer",
"short_class": "ItemNormalizer",
"class": "ApiPlatform\\Core\\Serializer\\ItemNormalizer",
"type": "->",
"function": "denormalize",
"file": "\/var\/www\/vendor\/symfony\/serializer\/Serializer.php",
"line": 191,
"args": [
[
"array",
[]
],
[
"string",
"App\\Infrastructure\\Doctrine\\Projection\\User"
],
[
"string",
"json"
],
[
"array",
{
"operation_type": [
"string",
"collection"
],
"collection_operation_name": [
"string",
"postResendValidationMail"
],
"api_allow_update": [
"boolean",
false
],
"resource_class": [
"string",
"App\\Infrastructure\\Doctrine\\Projection\\User"
],
"input": [
"array",
{
"class": [
"string",
"App\\Domain\\User\\Command\\ResendValidationMailCommand"
],
"name": [
"string",
"ResendValidationMailCommand"
]
}
],
"output": [
"array",
{
"class": [
"null",
null
]
}
],
"request_uri": [
"string",
"\/api\/users\/resend_validation_mail"
],
"uri": [
"string",
"http:\/\/api.renters.test\/api\/users\/resend_validation_mail"
]
}
]
]
},
{
"namespace": "Symfony\\Component\\Serializer",
"short_class": "Serializer",
"class": "Symfony\\Component\\Serializer\\Serializer",
"type": "->",
"function": "denormalize",
"file": "\/var\/www\/vendor\/symfony\/serializer\/Serializer.php",
"line": 142,
"args": [
[
"array",
[]
],
[
"string",
"App\\Infrastructure\\Doctrine\\Projection\\User"
],
[
"string",
"json"
],
[
"array",
{
"operation_type": [
"string",
"collection"
],
"collection_operation_name": [
"string",
"postResendValidationMail"
],
"api_allow_update": [
"boolean",
false
],
"resource_class": [
"string",
"App\\Infrastructure\\Doctrine\\Projection\\User"
],
"input": [
"array",
{
"class": [
"string",
"App\\Domain\\User\\Command\\ResendValidationMailCommand"
],
"name": [
"string",
"ResendValidationMailCommand"
]
}
],
"output": [
"array",
{
"class": [
"null",
null
]
}
],
"request_uri": [
"string",
"\/api\/users\/resend_validation_mail"
],
"uri": [
"string",
"http:\/\/api.renters.test\/api\/users\/resend_validation_mail"
]
}
]
]
},
{
"namespace": "Symfony\\Component\\Serializer",
"short_class": "Serializer",
"class": "Symfony\\Component\\Serializer\\Serializer",
"type": "->",
"function": "deserialize",
"file": "\/var\/www\/vendor\/api-platform\/core\/src\/EventListener\/DeserializeListener.php",
"line": 100,
"args": [
[
"array",
[]
],
[
"string",
"App\\Infrastructure\\Doctrine\\Projection\\User"
],
[
"string",
"json"
],
[
"array",
{
"operation_type": [
"string",
"collection"
],
"collection_operation_name": [
"string",
"postResendValidationMail"
],
"api_allow_update": [
"boolean",
false
],
"resource_class": [
"string",
"App\\Infrastructure\\Doctrine\\Projection\\User"
],
"input": [
"array",
{
"class": [
"string",
"App\\Domain\\User\\Command\\ResendValidationMailCommand"
],
"name": [
"string",
"ResendValidationMailCommand"
]
}
],
"output": [
"array",
{
"class": [
"null",
null
]
}
],
"request_uri": [
"string",
"\/api\/users\/resend_validation_mail"
],
"uri": [
"string",
"http:\/\/api.renters.test\/api\/users\/resend_validation_mail"
]
}
]
]
},
{
"namespace": "ApiPlatform\\Core\\EventListener",
"short_class": "DeserializeListener",
"class": "ApiPlatform\\Core\\EventListener\\DeserializeListener",
"type": "->",
"function": "onKernelRequest",
"file": "\/var\/www\/vendor\/symfony\/event-dispatcher\/Debug\/WrappedListener.php",
"line": 115,
"args": [
[
"object",
"Symfony\\Component\\HttpKernel\\Event\\GetResponseEvent"
],
[
"string",
"kernel.request"
],
[
"object",
"Symfony\\Component\\HttpKernel\\Debug\\TraceableEventDispatcher"
]
]
},
{
"namespace": "Symfony\\Component\\EventDispatcher\\Debug",
"short_class": "WrappedListener",
"class": "Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener",
"type": "->",
"function": "__invoke",
"file": "\/var\/www\/vendor\/symfony\/event-dispatcher\/EventDispatcher.php",
"line": 212,
"args": [
[
"object",
"Symfony\\Component\\HttpKernel\\Event\\GetResponseEvent"
],
[
"string",
"kernel.request"
],
[
"object",
"Symfony\\Component\\HttpKernel\\Debug\\TraceableEventDispatcher"
]
]
},
{
"namespace": "Symfony\\Component\\EventDispatcher",
"short_class": "EventDispatcher",
"class": "Symfony\\Component\\EventDispatcher\\EventDispatcher",
"type": "->",
"function": "doDispatch",
"file": "\/var\/www\/vendor\/symfony\/event-dispatcher\/EventDispatcher.php",
"line": 44,
"args": [
[
"array",
[
[
"object",
"Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener"
],
[
"object",
"Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener"
],
[
"object",
"Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener"
],
[
"object",
"Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener"
],
[
"object",
"Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener"
],
[
"object",
"Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener"
],
[
"object",
"Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener"
],
[
"object",
"Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener"
],
[
"object",
"Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener"
],
[
"object",
"Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener"
],
[
"object",
"Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener"
],
[
"object",
"Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener"
],
[
"object",
"Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener"
],
[
"object",
"Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener"
],
[
"object",
"Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener"
],
[
"object",
"Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener"
],
[
"object",
"Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener"
]
]
],
[
"string",
"kernel.request"
],
[
"object",
"Symfony\\Component\\HttpKernel\\Event\\GetResponseEvent"
]
]
},
{
"namespace": "Symfony\\Component\\EventDispatcher",
"short_class": "EventDispatcher",
"class": "Symfony\\Component\\EventDispatcher\\EventDispatcher",
"type": "->",
"function": "dispatch",
"file": "\/var\/www\/vendor\/symfony\/event-dispatcher\/Debug\/TraceableEventDispatcher.php",
"line": 145,
"args": [
[
"string",
"kernel.request"
],
[
"object",
"Symfony\\Component\\HttpKernel\\Event\\GetResponseEvent"
]
]
},
{
"namespace": "Symfony\\Component\\EventDispatcher\\Debug",
"short_class": "TraceableEventDispatcher",
"class": "Symfony\\Component\\EventDispatcher\\Debug\\TraceableEventDispatcher",
"type": "->",
"function": "dispatch",
"file": "\/var\/www\/vendor\/symfony\/http-kernel\/HttpKernel.php",
"line": 126,
"args": [
[
"string",
"kernel.request"
],
[
"object",
"Symfony\\Component\\HttpKernel\\Event\\GetResponseEvent"
]
]
},
{
"namespace": "Symfony\\Component\\HttpKernel",
"short_class": "HttpKernel",
"class": "Symfony\\Component\\HttpKernel\\HttpKernel",
"type": "->",
"function": "handleRaw",
"file": "\/var\/www\/vendor\/symfony\/http-kernel\/HttpKernel.php",
"line": 67,
"args": [
[
"object",
"Symfony\\Component\\HttpFoundation\\Request"
],
[
"integer",
1
]
]
},
{
"namespace": "Symfony\\Component\\HttpKernel",
"short_class": "HttpKernel",
"class": "Symfony\\Component\\HttpKernel\\HttpKernel",
"type": "->",
"function": "handle",
"file": "\/var\/www\/vendor\/symfony\/http-kernel\/Kernel.php",
"line": 198,
"args": [
[
"object",
"Symfony\\Component\\HttpFoundation\\Request"
],
[
"integer",
1
],
[
"boolean",
true
]
]
},
{
"namespace": "Symfony\\Component\\HttpKernel",
"short_class": "Kernel",
"class": "Symfony\\Component\\HttpKernel\\Kernel",
"type": "->",
"function": "handle",
"file": "\/var\/www\/public\/index.php",
"line": 25,
"args": [
[
"object",
"Symfony\\Component\\HttpFoundation\\Request"
]
]
}
]
}
I think this issue exists indeed because your class is empty.
Ok, so I think it could be a nice feature to be able to handle Empty DTO (Command) for some use case such as ResendUserPassword, POST workflow change state, ...
I think we could discuss on this implementation. Is it link to the possibility to toggle listeners or is it built in the different part of API platform (add condition on validator, propertyInfo, ...)?
IMO if you really want this behavior you should implement your own listener.
it's not a feature we want to handle in API Platform itself? I can make a PR if it's the subject here.
I think that we should fix this in the https://github.com/api-platform/core/blob/2f941fc3c0dc9dd257da3b34a6bb3f5c21c4230c/src/Bridge/Symfony/PropertyInfo/Metadata/Property/PropertyInfoPropertyNameCollectionFactory.php#L46 instead. We should return new PropertyNameCollection([])
instead but I'm afraid it'd break some stuff :p.
No, it should be fixed in PropertyInfo. null
should not be returned from getProperties
when the class simply has no properties.
This is wrong: https://github.com/symfony/symfony/blob/v4.2.8/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php#L98
But perhaps it's done that way because PropertyInfo does not have a way to merge results from multiple extractors.
:+1: indeed I also thought about this, we should try to fix this upstream then :/
For me this workaround works:
"defaults"={"_api_receive"=false},
class NotificationTokensDelete
{
public function __invoke($data = null)
{
return $data;
}
}
It would by nice if PlaceholderAction allows null as value for $data - this problem raise everytime when you disable read listener and has empty body. For example, I have delete action for notification token for logged user, so there is no need to send id for entity. Read, validation listeners are disabled and everything I need is custom data persister - there is also POST endpoint for set tokens.
Greetings! We appreciate your concern but weren't able to reproduce this issue or it is more of a question. As described in the API Platform contributing guide, we use GitHub issues for bugs and feature requests only.
For support question ("How To", usage advice, or troubleshooting your own code), you have several options:
Feel free reach one of the support channels above. In the meantime we're closing this issue.
Hey folks,
I'm running into an issue where I want to use API entrypoint with
messenger="input"
and my input class (Command) is a DTO and it has no attributes.A simple example of a User entity who needs to resend its validation email:
Entity/User.php
And the input class (DTO) here we called them Command:
ResendValidationMailCommand.php
This will always throw an error because the body is empty when we call the POST entrypoint:
Analysis
It seems the Symfony Validator is always called even if there is no attribute to be validated. It think it could be great to handle this case in https://github.com/api-platform/core/blob/master/src/Bridge/Symfony/Validator/EventListener/ValidateListener.php#L85 When the $validationGroup & $data are null/empty, don't call validate.
I think it's more in https://github.com/api-platform/core/blob/master/src/Validator/EventListener/ValidateListener.php#L60 we need to handle empty DTO input.
Further investigation
I tried to put
"defaults"={"_api_receive"=false},
on the operation but it can't handle $data null for POST:Workaround
The only workaround we found is to add a dummy attribute in the DTO but it's not ideal:
ideas or other workaround?
Any idea on this? Or workaround because we have several entrypoint we want to manage with CQRS pattern which haven't data to transport (just the info that Command is called).
Thanks in advance ;)