Gedmo Timestampable винаги актуализира препратка при десериализация с JMS Serializer

Имам връзка едно към едно в моя проект Symfony2, където Question има препратка към Video - и двете имат създадено и актуализирано Gedmo\Timestampable< /em> поведение, което основно работи според очакванията. Но малко прекалено:

При десериализиране на Question с прикачения Video (само като ID, за да се избегнат други промени в Метаданните за видео) документът 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 получи от db, винаги са различни от екземплярите, които са създадени чрез десериализация на JSON), обектът ще бъде актуализиран. Тогава Gedmo\Timestampable ще започне и съответно ще промени свойството $updated.

Ако не искате това да се случи, ще трябва да find() текущия обект и само да промените стойностните обекти, когато стойността, която те представляват, действително се промени. Скаларните стойности не са проблем: можете да зададете същата стойност и Doctrine няма да я види като промяна. Но с обекти със стойност (като DateTime) обекти, Doctrine ще види промяна, когато има промени в препратката (когато е зададен различен екземпляр).

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