Десериализовать DateTime в Symfony

Попытка использовать компонент сериализатора в Symfony 3.3. Я борюсь с сущностями, имеющими членов DateTime.

Инициализация моего сериализатора config.yml:

serializer:
    enable_annotations: true

Добавил это в service.yml:

datetime_method_normalizer:
    class: Symfony\Component\Serializer\Normalizer\DateTimeNormalizer
    public: false
    tags: [serializer.normalizer]

Десериализованный код выглядит так:

$yml = [...]   // It was created by serializer->serialize()
$serializer = $this->get('serializer');
$myObject = $serializer->deserialize($yml, MyObject::class, "yaml");

Получается ошибка: Expected argument of type "DateTime", "string" given в vendor/symfony/symfony/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php (строка 204)

Я думаю, что DateTimeNormalizer::denormalize никогда не вызывается. Есть идеи, как вернуть его к жизни?

Информация: вызывается DateTimeNormalizer::__constructor().


person user3429660    schedule 26.06.2017    source источник


Ответы (3)


Поскольку DateTime является вложенным объектом, вы должны использовать компонент PropertyInfo, как описано здесь — https://symfony.com/doc/current/components/serializer.html#recursive-denormalization-and-type-safety

Извлечение информации о свойствах выполняется классами экстракторов.

https://symfony.com/doc/current/components/property_info.html#extractors

Существует 4 типа экстракторов:

  • ОтражениеExtractor
  • PhpDocExtractor
  • сериализатор экстрактор
  • DoctrineExtractor

Например, используя ReflectionExtractor, вам нужно указать подсказку типа либо для параметров, либо для возвращаемого типа. Он также ищет параметры конструктора (требуется явное включение)

class Item {

   protected $date;

   public function setDate(\DateTime $date) {...}
   public function getDate() : \DateTime {...}
}

Информация о собственности регистрируется автоматически, когда установлена ​​опция:

# config/packages/framework.yaml 
framework:
  property_info: ~

После этого вам нужно переопределить сервис serializer, чтобы использовать его, или определить собственный. И последняя часть — добавьте DateTimeNormalizer, чтобы DateTime мог обрабатываться сериализатором.

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' ]

Вот и все.

person Danila Pakulin    schedule 30.10.2018
comment
Это правильное решение. Однако от вас не требуется переопределять или иным образом возиться с самой Serializer службой. Компонент PropertyInfo после регистрации в Symfony Framework должен автоматически внедрить себя в ObjectNormalizer, как это было в моем случае. - person SteveB; 08.02.2019
comment
Просто установка компонента symfony/property-info сработала для меня в приложении symfony 4.4. - person rubenj; 03.03.2020

Этот вопрос недавно сломал мне мозг, и у меня есть две сущности со свойством dateTime, решение - это пользовательский денормализатор, например:

<?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);
}

И объявить службу следующим образом:

# DateTime Hook Normalizer
Mybundle.normalizer.dateTimeHook:
    class: 'MybundleBundle\Serializer\Normalizer\DateTimeHookNormalizer'
    public: false
    tags: [serializer.normalizer]

Это нормально для меня, эта работа!

person pierre raby    schedule 20.09.2017
comment
Я тоже использую этот обходной путь, но странно, что он мне не нужен в некоторых проектах. - person a11r; 15.10.2018

Кажется, единственный официальный способ объявить callback:

$callback = function ($dateTime) {
return $dateTime instanceof \DateTime
    ? $dateTime->format(\DateTime::ISO8601)
    : '';
};

$normalizer->setCallbacks(array('createdAt' => $callback));
$serializer = new Serializer(array($normalizer), array($encoder));

https://symfony.com/doc/current/components/serializer.html#using-callbacks-to-serialize-properties-with-object-instances

person Valentas    schedule 26.06.2017
comment
Спасибо. Использование обратных вызовов будет неприятным, потому что имена свойств DateTime будут меняться по мере развития программного обеспечения. Если он не переводится из строки в DateTime, какова цель DateTimeNormalizer? - person user3429660; 26.06.2017
comment
Я думаю, что этот ответ неверен. Метод AbstractNormalizer::setCallbacks только применяет нормализаторы, а не денормализаторы. Нормализаторы преобразуют типизированные значения в значения массива, готовые для кодирования в сериализованные строки. Вопрос касается другого направления: денормализация значений массива обратно в типизированные значения как часть процесса десериализации из кодировки в объект. - person ironchicken; 01.09.2017