Trying to use the serializer component in Symfony 3.3. I struggle with entities having 'DateTime' members.
My config.yml serializer init:
serializer:
    enable_annotations: true
Added this in service.yml:
datetime_method_normalizer:
    class: Symfony\Component\Serializer\Normalizer\DateTimeNormalizer
    public: false
    tags: [serializer.normalizer]
The deserialized code looks like this:
$yml = [...]   // It was created by serializer->serialize()
$serializer = $this->get('serializer');
$myObject = $serializer->deserialize($yml, MyObject::class, "yaml");
The error is get is: Expected argument of type "DateTime", "string" given in in vendor/symfony/symfony/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php (line 204)
I think the DateTimeNormalizer::denormalize never gets called. Any idea how to bring it back to life?
Info: DateTimeNormalizer::__constructor() is called.
As DateTime is a nested object, you should use PropertyInfo Component as described here — https://symfony.com/doc/current/components/serializer.html#recursive-denormalization-and-type-safety
The extraction of property information is performed by extractor classes.
https://symfony.com/doc/current/components/property_info.html#extractors
There are 4 types of extractors:
For example, using ReflectionExtractor you need to specify type hint for either params or return type. It also looks for constructor params (requires to be enabled explicitly)
class Item {
   protected $date;
   public function setDate(\DateTime $date) {...}
   public function getDate() : \DateTime {...}
}
Property Info is registered automatically when option set:
# config/packages/framework.yaml 
framework:
  property_info: ~
After that you need to override serializer service to use it, or define a custom one. And the last part — add DateTimeNormalizer, so DateTime can be processed by serializer.
app.normalizer.item_normalizer:
    class: Symfony\Component\Serializer\Normalizer\ObjectNormalizer
    arguments:
      - null
      - null
      - null
      - '@property_info.reflection_extractor'
    tags: [ 'serializer.normalizer' ]
app.serializer.item:
    class: Symfony\Component\Serializer\Serializer
    public: true
    arguments:
      - [
          '@serializer.normalizer.datetime',
          '@app.normalizer.item_normalizer',
        ]
      - [ '@serializer.encoder.json' ]
That's it.
This question break my brain recently, and I've two entities with dateTime property, the solution is custom denormalizer like this:
<?php
namespace MyBundle\Serializer\Normalizer;
use MyBundle\Entity\MyEntity1;
use MyBundle\Entity\MyEntity2;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
/**
 * DateTime hook normalizer
 */
class DateTimeHookNormalizer implements DenormalizerInterface
{
    /**
     * {@inheritdoc}
     */
    public function denormalize($data, $class, $format = null, array $context = array())
    {
        if (isset($data['MyDateTime1']) && is_string($data['MyDateTime1']))
        {
            $data['MyDateTime1'] = new \DateTime($data['MyDateTime1']);
        }
        if (isset($data['MyDateTime2']) && is_string($data['MyDateTime2']))
        {
            $data['MyDateTime2'] = new \DateTime($data['MyDateTime2']);
        }
        And more ...
        $normalizer = new ObjectNormalizer();//default normalizer
        return $normalizer->denormalize($data, $class, $format, $context);
    }
}
/**
 * {@inheritdoc}
 */
public function supportsDenormalization($data, $type, $format = null)
{
    return is_array($data) && ($type === MyEntity1::class || $type === MyEntity2::class);
}
And declare service like this :
# DateTime Hook Normalizer
Mybundle.normalizer.dateTimeHook:
    class: 'MybundleBundle\Serializer\Normalizer\DateTimeHookNormalizer'
    public: false
    tags: [serializer.normalizer]
It's ok for me, that work !
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With