Gedmo\Loggable регистрирует данные, которые не изменились

Я использую Symfony2.2 со StofDoctrineExtensionsBundle (и, следовательно, Gedmo DoctrineExtensions). У меня есть простая сущность

/**
 * @ORM\Entity
 * @Gedmo\Loggable
 * @ORM\Table(name="person")
 */
class Person {
    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

[...]

    /**
     * @ORM\Column(type="datetime", nullable=true)
     * @Assert\NotBlank()
     * @Assert\Date()
     * @Gedmo\Versioned
     */
    protected $birthdate;
}

При изменении атрибута существующего объекта делается запись журнала в таблице ext_log_entries. Запись в этой таблице журнала содержит только измененные столбцы. Я могу прочитать журнал:

$em = $this->getManager();
$repo = $em->getRepository('Gedmo\Loggable\Entity\LogEntry');
$person_repo = $em->getRepository('Acme\MainBundle\Entity\Person');

$person = $person_repo->find(1);
$log = $repo->findBy(array('objectId' => $person->getId()));
foreach ($log as $log_entry) { var_dump($log_entry->getData()); }

Но я не понимаю, почему поле birthdate всегда содержится в записи журнала, даже если оно не изменено. Вот несколько примеров трех записей журнала:

array(9) {
  ["salutation"]=>
  string(4) "Herr"
  ["firstname"]=>
  string(3) "Max"
  ["lastname"]=>
  string(6) "Muster"
  ["street"]=>
  string(14) "Musterstraße 1"
  ["zipcode"]=>
  string(5) "00000"
  ["city"]=>
  string(12) "Musterhausen"
  ["birthdate"]=>
  object(DateTime)#655 (3) {
    ["date"]=>
    string(19) "1893-01-01 00:00:00"
    ["timezone_type"]=>
    int(3)
    ["timezone"]=>
    string(13) "Europe/Berlin"
  }
  ["email"]=>
  string(17) "[email protected]"
  ["phone"]=>
  NULL
}

array(2) {
  ["birthdate"]=>
  object(DateTime)#659 (3) {
    ["date"]=>
    string(19) "1893-01-01 00:00:00"
    ["timezone_type"]=>
    int(3)
    ["timezone"]=>
    string(13) "Europe/Berlin"
  }
  ["phone"]=>
  string(9) "123456789"
}

array(2) {
  ["birthdate"]=>
  object(DateTime)#662 (3) {
    ["date"]=>
    string(19) "1893-01-01 00:00:00"
    ["timezone_type"]=>
    int(3)
    ["timezone"]=>
    string(13) "Europe/Berlin"
  }
  ["phone"]=>
  NULL
}

Я хочу регистрировать только действительно измененные данные. Есть ли какой-то вариант, который я еще не видел? Кажется, это связано с тем, что birthdate является DateTime объектом, не так ли?

EDIT Это не связано с объектом DateTime. Это происходит даже в других сущностях. У меня есть еще один объект, содержащий простое значение:

/**
 * @ORM\Entity
 * @Gedmo\Loggable
 * @ORM\Entity(repositoryClass="Acme\MainBundle\Repository\ApplicationRepository")
 * @ORM\Table(name="application")
 */
class Application {

[...]

    /**
     * @ORM\Column(type="integer")
     * @Assert\NotBlank(groups={"FormStepOne", "UserEditApplication"})
     * @Gedmo\Versioned
     */
    protected $insurance_number;
}

При открытии формы редактирования в браузере сохранение без изменений, таблица журнала содержит:

update  2013-04-26 11:32:42     Acme\MainBundle\Entity\Application  a:1:{s:16:"insurance_number";s:7:"1234567";}
update  2013-04-26 11:33:17     Acme\MainBundle\Entity\Application  a:1:{s:16:"insurance_number";s:7:"1234567";}

Почему?


person rabudde    schedule 26.04.2013    source источник


Ответы (3)


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

Возможно, стоит попытаться реализовать альтернативную политику отслеживания изменений для сущностей, которые вы хотите зарегистрировать, и посмотреть, исправит ли это что-то - я предполагаю, что это поведение (ведение журнала) не срабатывает, если состояние сущности не является грязным, чего вы можете избежать самостоятельно внедрив отслеживание изменений вручную:

http://docs.doctrine-project.org/en/latest/cookbook/implementing-the-notify-changetracking-policy.html

Не забудьте обновить свою сущность:

YourBundle\Entity\YourThing:
    type: entity
    table: some_table
    changeTrackingPolicy: NOTIFY

Смотрите эту тему:

https://github.com/Atlantic18/DoctrineExtensions/issues/333#issuecomment-16738878

person calumbrodie    schedule 09.05.2013
comment
Даже это старый вопрос, я решил не реализовывать свою собственную функцию уведомления и позволить этому дерьмовому поведению продолжаться :( - person rabudde; 21.09.2013
comment
@rabudde На самом деле не вина Doctrine, а дерьмовое сравнение объектов PHP - person calumbrodie; 23.09.2013
comment
О, конечно, это также может быть ошибкой PHP, извините. Но я и представить себе не мог, что будет так сложно сравнивать объекты самостоятельно, если я буду писать бандл/плагин для приложения на основе PHP, но я бы не стал хакнуть бандл Gedmo. Но все равно спасибо за ответ. - person rabudde; 23.09.2013

Я также столкнулся с этой проблемой сегодня и решил ее. Вот полное решение, работающее для всех значений типа string, float, int и DateTime.

  1. Создайте свой собственный LoggableListener и используйте его вместо Gedmo Listener.

    <?php
    
    namespace MyBundle\Loggable\Listener;
    
    use Gedmo\Loggable\LoggableListener;
    use Gedmo\Tool\Wrapper\AbstractWrapper;
    
    class MyLoggableListener extends LoggableListener
    {
        protected function getObjectChangeSetData($ea, $object, $logEntry)
        {
            $om = $ea->getObjectManager();
            $wrapped = AbstractWrapper::wrap($object, $om);
            $meta = $wrapped->getMetadata();
            $config = $this->getConfiguration($om, $meta->name);
            $uow = $om->getUnitOfWork();
            $values = [];
    
            foreach ($ea->getObjectChangeSet($uow, $object) as $field => $changes) {
                if (empty($config['versioned']) || !in_array($field, $config['versioned'])) {
                    continue;
                }
    
                $oldValue = $changes[0];
                if ($meta->isSingleValuedAssociation($field) && $oldValue) {
                    if ($wrapped->isEmbeddedAssociation($field)) {
                        $value = $this->getObjectChangeSetData($ea, $oldValue, $logEntry);
                    } else {
                        $oid = spl_object_hash($oldValue);
                        $wrappedAssoc = AbstractWrapper::wrap($oldValue, $om);
                        $oldValue = $wrappedAssoc->getIdentifier(false);
                        if (!is_array($oldValue) && !$oldValue) {
                            $this->pendingRelatedObjects[$oid][] = [
                                'log' => $logEntry,
                                'field' => $field,
                            ];
                        }
                    }
                }
    
                $value = $changes[1];
                if ($meta->isSingleValuedAssociation($field) && $value) {
                    if ($wrapped->isEmbeddedAssociation($field)) {
                        $value = $this->getObjectChangeSetData($ea, $value, $logEntry);
                    } else {
                        $oid = spl_object_hash($value);
                        $wrappedAssoc = AbstractWrapper::wrap($value, $om);
                        $value = $wrappedAssoc->getIdentifier(false);
                        if (!is_array($value) && !$value) {
                            $this->pendingRelatedObjects[$oid][] = [
                                'log' => $logEntry,
                                'field' => $field,
                            ];
                        }
                    }
                }
    
                //fix for DateTime, integer and float entries
                if ($value == $oldValue) {
                    continue;
                }
    
                $values[$field] = $value;
            }
    
            return $values;
        }
    }
    
  2. Для приложения Symfony зарегистрируйте слушателя в файле config.yml.

    stof_doctrine_extensions:
        orm:
            default:
                loggable: true
        class:
            loggable: MyBundle\Loggable\Listener\MyLoggableListener
    
  3. Если вы используете поля DateTime в своих объектах, но в базе данных вы храните только дату, вам также необходимо сбросить часть времени во всех сеттерах.

    public function setDateValue(DateTime $dateValue = null)
    {
        $dateValue->setTime(0, 0, 0);
        $this->dateValue = $dateValue;
        return $this;
    }
    

Это должно сделать работу.

person Adrael    schedule 13.04.2017

Для \DateTime я все еще работаю над этим, но для второй части вашего вопроса есть способ решить мою проблему с моими свойствами Numeric:

/**
 * @ORM\Entity
 * @Gedmo\Loggable
 * @ORM\Entity(repositoryClass="Acme\MainBundle\Repository\ApplicationRepository")
 * @ORM\Table(name="application")
 */
class Application {

[...]

    /**
     * @ORM\Column(type="integer")
     * @Assert\NotBlank(groups={"FormStepOne", "UserEditApplication"})
     * @Gedmo\Versioned
     */
    protected $insurance_number;
}

Здесь вы объявляете insurance_number как целочисленное свойство, но, как мы знаем, PHP не имеет типа и выполняет динамическое приведение, что противоречит Gedmo Loggable.

Чтобы решить, просто убедитесь, что вы сами выполняете явное приведение типов либо в Setter Method, либо в Business Logic.

например, замените это (бизнес-логика):

$application->setInsuranceNumber($valueComeFromHtmlForm)

с этим:

$application->setInsuranceNumber( (int)$valueComeFromHtmlForm)

Затем, когда вы сохраните свой объект, вы не увидите никаких записей в своих журналах.

Я думаю, это потому, что Loggable или Doctrine Change Tracker ожидает Integer, а получает String (which is a 'not casted Integer') и поэтому помечает свойство как грязное. Мы можем видеть это в Log Record (S означает, что новое значение — String.)

person Mohammad Eghlima    schedule 18.10.2016