Как проверить, изменилась ли сущность в Doctrine 2?
Мне нужно проверить, изменился ли постоянный объект и его необходимо обновить в базе данных.
То, что я сделал (и не работало), было следующим:
$product = $entityManager->getRepository('Product')->find(3);
$product->setName('A different name');
var_export($entityManager->getUnitOfWork()->isScheduledForUpdate($product));
Этот код всегда печатает false
, я также попытался выполнить флеш перед проверкой единицы работы, но не работал.
У кого-нибудь есть предложение?
Ответы
Ответ 1
Первое, что я хотел бы проверить, что ваша функция setName на самом деле что-то делает ($ this- > name = $name...) Если он уже работает, тогда вы можете определить прослушиватель событий на своих сервисах. запускается, когда вы вызываете флеш.
entity.listener:
class: YourName\YourBundle\EventListener\EntityListener
calls:
- [setContainer, ["@service_container"]]
tags:
- { name: doctrine.event_listener, event: onFlush }
Затем вы определяете EntityListener
namespace YourName\YourBundle\EventListener;
use Doctrine\ORM\Event;
use Symfony\Component\DependencyInjection\ContainerAware;
class EntityListener extends ContainerAware
{
/**
* Gets all the entities to flush
*
* @param Event\OnFlushEventArgs $eventArgs Event args
*/
public function onFlush(Event\OnFlushEventArgs $eventArgs)
{
$em = $eventArgs->getEntityManager();
$uow = $em->getUnitOfWork();
//Insertions
foreach ($uow->getScheduledEntityInsertions() as $entity) {
# your code here for the inserted entities
}
//Updates
foreach ($uow->getScheduledEntityUpdates() as $entity) {
# your code here for the updated entities
}
//Deletions
foreach ($uow->getScheduledEntityDeletions() as $entity) {
# your code here for the deleted entities
}
}
}
Если вам нужно знать, какие объекты изменяются, но что-то делать с ними после их сохранения в базе данных, просто сохраните сущности, измененные в приватном массиве, затем определите событие onFlush, которое получает объекты из массив.
BTW, чтобы инициировать подобные события, вам нужно добавить @ORM\HasLifecycleCallbacks на объект.
Ответ 2
Вы также можете посмотреть событие PreUpdate, если вам нужен доступ к полям объектов со старыми и новыми значениями.
Немного примера, взятого в основном из предоставленной ссылки:
<?php
class NeverAliceOnlyBobListener
{
public function preUpdate(PreUpdateEventArgs $eventArgs)
{
if ($eventArgs->getEntity() instanceof User) {
if ($eventArgs->hasChangedField('name') && $eventArgs->getNewValue('name') == 'Alice') {
$oldValue = $eventArgs->getOldValue('name');
$eventArgs->setNewValue('name', 'Bob');
}
}
}
}
Ответ 3
Мне не нужно/нужно создавать Listeners для моего дела, поэтому я закончил с
$product->setName('A different name');
$uow = $entityManager->getUnitOfWork();
$uow->computeChangeSets();
if ($uow->isEntityScheduled($product)) {
// My entity has changed
}
Ответ 4
Doctrine2 Docs. 17. Изменение политики отслеживания
Если вы используете третью форму (17.3. Уведомлять), как я, вы можете проверить, изменилось ли ваше суждение:
$uow = $entityManager->getUnitOfWork();
$aChangeSet = $uow->getEntityChangeSet($oEntity);
Если ничего не изменится, он вернет пустой массив.
Ответ 5
Проблема довольно старая, но может быть еще какая-то группа людей, которые могут столкнуться с этой проблемой с другой точки зрения.
UnitOfWork
отлично работает, но возвращает массив изменений. Это может быть болью в прикладе, когда кто-то на самом деле не знает, какие поля могут измениться, и просто хочет, чтобы весь объект был объектом сравнения $oldEntity
и $newEntity
. Даже если имя события является preUpdate, если кто-то попытается извлечь данные из базы данных следующим образом:
$er->find($id);
возвращаемый объект будет содержать все изменения.
Обходной путь довольно прост, но у него есть несколько крючков:
public function preUpdate(Entity $entity, PreUpdateEventArgs $args)
{
$entity = clone $entity; //as Doctrine under the hood
//uses reference to manage entities you might want
//to work on the entity copy. Otherwise,
//the below refresh($entity) will affect both
//old and new entity.
$em = $args->getEntityManager();
$currentEntity = $em->getRepository('AppBundle:Entity')->find($entity->getId());
$em->refresh($currentEntity);
}
Для тех, кто использует другое событие, например preFlush, я быстро его проверил, и обходной путь не сработал, потому что, вероятно, метод refresh()
отбрасывает любые изменения флеша, поэтому нужно сделать это, чтобы вызвать флеш еще раз в слушателе и создать некоторый статический переключатель $alreadyFlushed
, чтобы избежать циклической ссылки.
Ответ 6
Мне интересно, как Doctrine и все документируют postFlush, так как в некоторых случаях у вас есть текущая транзакция.
Я также хотел бы указать postTransactionCommit, который может быть более безопасным в зависимости от того, чего вы пытаетесь достичь в событии postFlush.
Ответ 7
В соответствии с моими потребностями, ответы здесь и документы, я придумал следующее решение для modifiedAt
метки времени в Entity,
/**
* @Doctrine\ORM\Mapping\PreUpdate()
*
* @param \Doctrine\ORM\Event\PreUpdateEventArgs $args
* @return $this
*/
public function preUpdateModifiedAt(\Doctrine\ORM\Event\PreUpdateEventArgs $args)
{
$this->setModifiedAt(new \DateTime('now'));
return $this;
}
Это основано на то, что говорят docs об этом Event
, в отличие от других доступных, таких как PostPersist
и PreFlush
:
PreUpdate является самым ограничивающим для использования событием, поскольку он называется непосредственно перед вызовом update для объекта внутри Метод EntityManager # flush(). Обратите внимание, что это событие не запускается когда вычисленный набор изменений пуст.
Используя PreUpdate
, в отличие от других, вы можете оставить все вычисления и интенсивные вычисления в процессе, уже определенном Doctrine. В ручном запуске вычисления наборов изменений, например, в этих ответы выше, интенсивность сервера. Событие onFlush, например, используемое в принятом ответе, является опцией (продемонстрированным способом), но не если вы полагаетесь на обнаружение изменения в Entity, поскольку вы может с функцией выше (preUpdateModifiedAt(PreUpdateEventArgs $args)
).