Обновление doctine entity в preUpdate event
Автор Руслан Коваленко 7/06/2012Есть entity:
{
protected $price;
protected $discount;
}
Т.о. у нас есть цена и скидка. Скидка высчитывается из цены, подсчет осуществляется отдельным классом DiscountCalculator, который зареган как service.
Необходимо чтобы каждый раз при изменении цены, обновлялась скидка. Для этого будем перехватывать doctrine event: preUpdate.
Доктрина кидает множество евентов на различные события:
- http://docs.doctrine-project.org/projects/doctrine-orm/en/2.1/reference/events.html
- http://symfony.com/doc/current/cookbook/doctrine/event_listeners_subscribers.html
Если бы вычисления скидки были простыми (например скидка = 10% от цены) — мы бы могли зарегать lifecycle callback в самом классе entity:
* @ORM\HasLifecycleCallbacks
*/
class Product
{
…
/**
* @ORM\PreUpdate
*/
public function updateDiscount()
{
$this->discount = $this->price * 10 / 100;
}
}
Но вычисление скидки происходит через service DiscountCalculator. К service container в классе entity доступа нет, соотв-но и к DiscountCalculator тоже. Поэтому мы создаем отдельный класс-листенер и перехватываем в нем preUpdate event.
{
protected $container;
public function __construct(Container $container)
{
$this->container = $container;
}
public function preUpdate(PreUpdateEventArgs $args)
{
$entity = $args->getEntity();
….
}
}
Подписываемся на событие:
<services>
<service id="product.orm.listener" class="XXX\ProductOrmListener">
<argument type="service" id="service_container" />
<tag name="doctrine.event_listener" event="preUpdate"/>
</service>
</services>
Теперь если значения полей Product изменятся, то при сохранении в базу, будет вызвано событие preUpdate, которое мы перехватим.
Далее нам нужно подсчитать скидку и сохранить ее новое значение.
Вариант 1.
Просто обновить поля entity. В этом случае изменения не сохранятся :)
{
$entity = $args->getEntity();
if ($entity instanceof Product)
{
$entity->setDiscount(
$this->container->get('discount_calculator')->getDiscount($entity->getPrice())
);
}
}
Вариант 2.
После этого обращаем внимание на класс PreUpdateEventArgs. У него есть функция setNewValue( string $field, mixed $value). Попробуем воспользоваться ей:
{
$entity = $args->getEntity();
if ($entity instanceof Product)
{
$args->setNewValue(
"discount",
$this->container->get('discount_calculator')->getDiscount($entity->getPrice())
);
}
}
Однако нас ожидает ошибка:
Эта ошибка возникает из-за того, что придать новые значения мы можем только тем полям, которые поменялись. Т.е. которые присутствуют в так называем changeset. Т.к. мы в коде меняем поле price, то оно будет тут присутствовать, а вот discount нет.
Вариант 3.
Теперь возьмем за основу первый вариант и воспользуемся классом UnitOfWork.
С помощью него можно выполнить так называемый пересчет изменений и т.о. они сохранятся и занесутся в базу. Начиная с версии 2.2 есть метод: recomputeSingleEntityChangeSet.
{
$entity = $args->getEntity();
$em = $args->getEntityManager();
$uow = $em->getUnitOfWork();
if ($entity instanceof Product)
{
$entity->setDiscount(
$this->container->get('discount_calculator')->getDiscount($entity->getPrice())
);
$uow->recomputeSingleEntityChangeSet(
$em->getClassMetadata("XXXBundle:Product"),
$entity
);
}
}
Для полной картины проверим так же что изменения поля цены вообще происходили, чтобы лишний раз не делать пересчет. Также добавим event prePersist, чтобы задавать скидку при создании продукта.
<services>
<service id="product.orm.listener" class="XXX\ProductOrmListener">
<argument type="service" id="service_container" />
<tag name="doctrine.event_listener" event="preUpdate"/>
<tag name="doctrine.event_listener" event="prePersist"/>
</service>
</services>
public function prePersist(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
$entityManager = $args->getEntityManager();
if ($entity instanceof Product)
{
$entity->setDiscount(
$this->container->get('discount_calculator')->getDiscount($entity->getPrice())
);
}
}
public function preUpdate(PreUpdateEventArgs $args)
{
$entity = $args->getEntity();
$em = $args->getEntityManager();
$uow = $em->getUnitOfWork();
if ($entity instanceof Product)
{
$changeset = $uow->getEntityChangeSet($entity);
if(isset($changeset['price']) && ($changeset['price'][0] != $changeset['price'][1]))
{
$entity->setDiscount(
$this->container->get('discount_calculator')->getDiscount($entity->getPrice())
);
$uow->recomputeSingleEntityChangeSet(
$em->getClassMetadata("XXXBundle:Product"),
$entity
);
}
}
}
Категории:


Нет комментариев.