Gedmo Timestampable всегда обновляет ссылку при десериализации с помощью JMS Serializer

У меня есть отношение один к одному в моем проекте Symfony2, где Question имеет ссылку на Video - оба имеют созданные и обновленные Gedmo\Timestampable< /em> поведение, которое в основном работает так, как ожидалось. Но как-то слишком:

При десериализации Question с прикрепленным Video (только в качестве идентификатора, чтобы избежать других изменений в метаданных видео) документ Video всегда получает обновление как на created , так и на поле updated. Это не кажется правильным. Я могу понять, почему обновленное поле получает новую дату - даже если на самом деле ничего не изменилось в самом объекте, но почему создано ?

Это мой код (упрощенный):

Вопрос класса:

<?php

/**
 * Class Question
 *
 * @Serializer\AccessorOrder("alphabetical")
 * @MongoDB\Document(
 *   collection="Quiz",
 *   repositoryClass="MyNamespace\Bundle\QuizBundle\Repository\QuestionRepository",
 * )
 * @package MyNamespace\Bundle\QuizBundle\Document
 */
class Question
{
    /**
     * @var \MongoId
     * @MongoDB\Id(strategy="auto")
     * @Serializer\Type("string")
     * @Serializer\Groups({
     *   "quiz_admin_list",
     *   "quiz_admin_detail"
     * })
     */
    protected $id;

    /**
     * @var \DateTime
     *
     * @Assert\Date(
     *   message = "quiz:constraints.model.question.created.invalid"
     * )
     * @Serializer\Type("DateTime<'U'>")
     * @Serializer\Accessor(getter="getCreated", setter="setCreatedEmpty")
     * @Serializer\Groups({
     *   "quiz_admin_list",
     *   "quiz_admin_detail"
     * })
     * @Gedmo\Timestampable(on="create")
     * @MongoDB\Date
     */
    protected $created;


    /**
     * @var \DateTime
     *
     * @Assert\Date(
     *   message = "quiz:constraints.model.question.updated.invalid"
     * )
     * @Serializer\Type("DateTime<'U'>")
     * @Serializer\Accessor(getter="getUpdated", setter="setUpdatedEmpty")
     * @Serializer\Groups({
     *   "quiz_admin_list",
     *   "quiz_admin_detail"
     * })
     * @Gedmo\Timestampable(on="update")
     * @MongoDB\Date
     */
    protected $updated;

    /**
     * @var Video
     *
     * @Serializer\Type("MyNamespace\Bundle\CoreMediaAdminBundle\Document\Video")
     * @Serializer\Groups({
     *   "quiz_admin_list",
     *   "quiz_admin_detail"
     * })
     * @MongoDB\ReferenceOne(
     *   targetDocument="MyNamespace\Bundle\CoreMediaAdminBundle\Document\Video",
     *   cascade={"all"}
     * )
     */
    protected $answerVideo;

}

Видео класса:

<?php

/**
 * Class Video
 * @Serializer\AccessorOrder("alphabetical")
 * @MongoDB\Document(
 *   collection="CoreMediaAdminVideo",
 *   repositoryClass="MyNamespace\Bundle\CoreMediaAdminBundle\Repository\VideoRepository",
 * )
 * @Vich\Uploadable
 * @package MyNamespace\Bundle\CoreMediaAdminBundle\Document
 */
class Video 
{

    /**
     * @MongoDB\Id(strategy="auto")
     * @Serializer\Type("string")
     * @Serializer\Groups({
     *   "core_media_list",
     *   "core_media_search",
     *   "core_media_video_list",
     *   "core_media_video_detail"
     * })
     */
    protected $id;

    /**
     * @Vich\UploadableField(
     *   mapping = "core_media_admin_video",
     *   fileNameProperty = "fileName"
     * )
     * @Serializer\Exclude
     * @var File $file
     */
    protected $file;

    /**
     * @MongoDB\Field(type="string")
     * @Serializer\Type("string")
     * @Serializer\Groups({
     *   "core_media_list",
     *   "core_media_search",
     *   "core_media_video_list",
     *   "core_media_video_detail"
     * })
     */
    protected $mimeType;

    /**
     * @var String
     *
     * @Assert\NotBlank(
     *   message = "core.media.admin:constraints.model.base.title.not_blank"
     * )
     * @Serializer\Type("string")
     * @Serializer\Groups({
     *   "core_media_list",
     *   "core_media_search",
     *   "core_media_video_list",
     *   "core_media_video_detail"
     * })
     * @MongoDB\Field(type="string")
     */
    protected $title;

    /**
     * @var \DateTime
     *
     * @Assert\Date(
     *   message = "core.media.admin:constraints.model.base.date.invalid"
     * )
     * @Serializer\Type("DateTime<'U'>")
     * @Serializer\Accessor(getter="getCreated", setter="setCreatedEmpty")
     * @Serializer\Groups({
     *   "core_media_list",
     *   "core_media_search",
     *   "core_media_video_list",
     *   "core_media_video_detail"
     * })
     * @Gedmo\Timestampable(on="create")
     * @MongoDB\Date
     */
    protected $created;

    /**
     * @var \DateTime
     *
     * @Assert\Date(
     *   message = "core.media.admin:constraints.model.base.date.invalid"
     * )
     * @Serializer\Type("DateTime<'U'>")
     * _Serializer\Accessor(getter="getUpdated", setter="setUpdatedEmpty")
     * @Serializer\Groups({
     *   "core_media_list",
     *   "core_media_search",
     *   "core_media_video_list",
     *   "core_media_video_detail"
     * })
     * @Gedmo\Timestampable(on="update")
     * @MongoDB\Date
     */
    protected $updated;

    /**
     * @var \DateTime
     *
     * @Assert\Date(
     *   message = "core.media.admin:constraints.model.base.date.invalid"
     * )
     * @Serializer\Type("DateTime<'U'>")
     * @Serializer\Groups({
     *   "core_media_list",
     *   "core_media_search",
     *   "core_media_video_list",
     *   "core_media_video_detail"
     * })
     * @Gedmo\Timestampable(on="update", field={"title", "tags", "comment", "dataOrigin", "description", "videoMetaData", "mimeType", "fileName", "file" })
     * @MongoDB\Date
     */
    protected $updatedContent;

}

Интересно то, что в Video объектах во время десериализации не вносятся никакие изменения - есть только запрос на обновление для установки полей created и updated видео. Я также протестировал поле для параметра Timestampable для принудительного обновления только тогда, когда одно из этих полей получает обновление, но это, похоже, полностью игнорируется.

Вот также десериализованный JSON и соответствующие запросы MongoDB:

{
  "id": "547f31e650e56f2c26000063",
  "question_id": 12,
  "question_text": "Wer einen Gemüsegarten hat, sollte wissen, dass Schnecken…?",
  "answer_text": "test",
  "answer_video": {
    "id": "547f31d850e56f2c26000031"
  },
  "tags": [
    "Schnecken",
    "Basilikum",
    "Thymian",
    "Garten"
  ]
}

Запросы:

db.QuizQuestion.find({
  "_id": ObjectId("547f31e650e56f2c26000063")
}).limit(1).limit();

db.CoreMediaAdminVideo.update({
  "_id": ObjectId("547f31d850e56f2c26000031")
},
{
  "$set": {
    "created": newISODate("2014-12-03T21:30:02+01:00"),
    "updated": newISODate("2014-12-03T21:30:02+01:00"),
    "updatedContent": newISODate("2014-12-03T21:30:02+01:00")
  }
});

db.ARDBuffetQuizQuestion.update({
  "_id": ObjectId("547f31e650e56f2c26000063")
},
{
  "$set": {
    "created": newISODate("2014-12-03T21:30:02+01:00"),
    "updated": newISODate("2014-12-03T21:30:02+01:00"),
    "questionText": "Wer einen Gemüsegarten hat, sollte wissen, dass Schnecken…?",
    "answerText": "test",
    "answerVideo": {
      "$ref": "CoreMediaAdminVideo",
      "$id": ObjectId("547f31d850e56f2c26000031"),
      "$db": "my-database"
    }
  }
});

db.ARDBuffetQuizQuestion.update({
  "_id": ObjectId("547f31e650e56f2c26000063")
},
{
  "$set": {
    "tags": [
      {
        "value": "Schnecken",
        "normalized": "schnecken"
      },
      {
        "value": "Basilikum",
        "normalized": "basilikum"
      },
      {
        "value": "Thymian",
        "normalized": "thymian"
      },
      {
        "value": "Garten",
        "normalized": "garten"
      }
    ]
  }
});

db.ARDBuffetQuizQuestion.find({
  "_id": ObjectId("547f31e650e56f2c26000063")
}).limit(1).limit();

db.CoreMediaAdminVideo.find({
  "_id": ObjectId("547f31d850e56f2c26000031")
}).limit(1).limit();

person con    schedule 03.12.2014    source источник
comment
вы делаете $set createdAt и updatedAt вручную в своем запросе или?   -  person john Smith    schedule 10.12.2014


Ответы (1)


Gedmo\Timestampable установит (новые) значения для $created и $updated, поскольку эти данные отсутствуют при сбросе ObjectManager.

Хотя аннотации в классе Video определяют, что $created и $updated должны быть включены при сериализации такого объекта, JSON, который вы показываете, не содержит этих ключей/свойств.

Вот что происходит:

  • JSON не содержит ключей/свойств created и updated.
  • При десериализации результирующий объект будет иметь null значений для $created и $updated.
  • Когда объект merge() загружается в ObjectManager, с ним ничего не происходит. Объект просто становится «управляемым», а это значит, что во время сброса ObjectManager будет вычислять для него набор изменений и при необходимости обновлять его.
  • При очистке ObjectManager срабатывает Gedmo\Timestampable (из-за прослушивателя событий PreUpdate). Он увидит, что $created и $updated содержат null значений, поэтому назначит новые значения.
  • Затем ObjectManager извлечет «текущие» данные из базы данных, потому что они нужны ему для расчета набора изменений. Обычно при find() обработке объекта он этого не делает, потому что данные уже получены во время этого find().
  • Поскольку значения $created и $updated теперь отличаются от значений, полученных из базы данных, они будут обновлены.

Таким образом, у вас будет 2 варианта:

  1. find() сначала объект, затем измените его в соответствии с JSON.
  2. Убедитесь, что JSON содержит все сопоставленные свойства (включая created и updated).

Также я заметил setter="setCreatedEmpty" и setter="setUpdatedEmpty". Я не уверен, что делают эти методы (потому что вы нам этого не показываете), но имена методов указывают на нечто иное, чем простое присвоение значений.

Ответ на ваши комментарии

Когда объект merge() помещается в ObjectManager, он помечается как "грязный", что инициирует расчет набора изменений. И поскольку ссылка на объекты DateTime изменилась (экземпляры, которые Doctrine получает из базы данных, всегда отличаются от экземпляров, созданных путем десериализации JSON), объект будет обновлен. Затем запустится Gedmo\Timestampable и соответствующим образом изменит свойство $updated.

Если вы не хотите, чтобы это произошло, вам потребуется find() текущий объект и только изменять объекты-значения, когда значение, которое они представляют, действительно изменилось. Скалярные значения не проблема: вы можете установить одно и то же значение, и Doctrine не воспримет это как изменение. Но с объектами-значениями (такими как DateTime) Doctrine увидит изменение при изменении ссылки (когда установлен другой экземпляр).

person Jasper N. Brouwer    schedule 10.12.2014
comment
tl;dr: Вы правы. Это сработало. :) Я использовал setter="setCreatedEmpty", потому что в прошлом проекте (около 14 месяцев назад) у меня была аналогичная проблема с сериализатором доктрины mongodb и jms, и по какой-то причине не снова устанавливал созданные/обновляемые значения при десериализации решил проблему. Так что просто повторно использовал этот обходной путь. Теперь, когда все данные включены, Doctrine по-прежнему выполняет запрос на обновление упомянутых видео, но сохраняет исходную временную метку created, как вы предложили. Я надеялся избежать дополнительных запросов. В любом случае, это работает, и больше нет запутанных «созданных» временных меток. Спасибо. - person con; 12.12.2014
comment
Остался еще один вопрос: когда кто-то другой обновит видео и даст ему правильную новую обновленную временную метку, сделав это таким образом, мы снова перезапишем обновленную временную метку - поэтому, чтобы быть на 100% уверенным, я должен получить объект, на который ссылаются, из базы данных и сравнить значения Правильно? - Или если есть способ избежать дополнительного запроса, то его вообще не будет.. - person con; 12.12.2014