Description
Description
When dealing with DTOs & serializer and ObjectNormalizer
, a missing feature to me is the ability to normalize values before actually setting the property in the DTOs. DTOs are often written with public properties only and writing custom setters (e.g: for trimming a string or change empty strings from the input to null
) for it or a custom denormalizer is a bit overhead for the developer. The idea would be to be able to pass a normalizer
callable in metadata that will be called by the ObjectNormalizer
.
Another approach would be to rely on a PropertyAccessor
annotation. #22190 inits the metadata support on this component, so it can fit as well.
Example
final class AddNotePayload
{
/**
* @var string
*
* @Assert\NotBlank()
* @Serializer\Denormalizer("trim")
*/
public $content;
/**
* @var \DateTime|null
*
* @Assert\Type(\DateTime::class)
*/
public $createdAt;
}
or
final class AddNotePayload
{
/**
* @var string
*
* @Assert\NotBlank()
* @PropertyAccessor(setterNormalizer="trim")
*/
public $content;
/**
* @var \DateTime|null
*
* @Assert\Type(\DateTime::class)
*/
public $createdAt;
}
Potential issues
When using AbstractObjectNormalizer::DISABLE_TYPE_ENFORCEMENT => true
, we might get an unexpected type, thus the normalizer callable may fail due to incompatible type.
A solution might be to not execute the normalizer unless type is actually matching but then it probably has to live in the Serializer and not in the PropertyAccessor component.
Or the PropertyAccessor could catch any exception thrown by the normalizer callable and still set the value (could be configurable with a force
option or whatever).
We might also provide a set of common normalizers that'll only act upon expected types and let the exception raise up if another user defined normalizer does not account properly for type/data (so native trim
function usage would be discouraged in favor of a more clever PropertyAccessor\normalizers\trim()
function).
In any case, proper validation must happen after and reject the non-matching value. So what we really want here is only trying to normalize a valid value (according to the type).