2
votes

I have a lifecycle event. As soon as an order is created the prePersist lifecycle event add a few more details to the order before it is persisted to the database.

This is my prePersist event class;

<?php

namespace Qi\Bss\BaseBundle\Lib\PurchaseModule;

use Qi\Bss\BaseBundle\Entity\Business\PmodOrder;
use Doctrine\ORM\Event\LifecycleEventArgs;

/**
 * Listener class
 * Handles events related to list prices
 */
class OrderUserListener
{

    /**
     * Service container
     * @var type 
     */
    private $serviceContainer;

    /**
     * Performs tasks before destruction
     * @ORM\PrePersist
     */
    public function prePersist(LifecycleEventArgs $args)
    {
        $order = $args->getEntity();

        if ($order instanceof PmodOrder) {
            $user = $this->serviceContainer->get('security.token_storage')->getToken()->getUser();

            if ($user) {
                $order->setCreatedBy($user);
                $order->setCreatedAt(new \DateTime(date('Y-m-d H:i:s')));
                $order->setDepartment($user->getDepartment());
                $order->setStatus(PmodOrder::STATUS_AWAITING_APPROVAL);

                $this->serviceContainer->get('bss.pmod.order_logger')->log($order, 'Order Created');
            }
        }
    }

    /**
     * Sets the sales order exporter object
     * @param type $serviceContainer
     */
    public function setServiceContainer($serviceContainer)
    {
        $this->serviceContainer = $serviceContainer;
    }
}

It works perfectly but this part $this->serviceContainer->get('bss.pmod.order_logger')->log($order, 'Order Created'); doesn't want to work. I try to call a service inside it. I know the service works perfectly inside my controllers, but here I get an error;

A new entity was found through the relationship 'Qi\Bss\BaseBundle\Entity\Business\PmodLog#order' that was not configured to cascade persist operations for entity: Nuwe Test vir logger. To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or configure cascade persist this association in the mapping for example @ManyToOne(..,cascade={"persist"}).

This is how my OrderLogger service class looks like;

<?php

namespace Qi\Bss\BaseBundle\Lib\PurchaseModule;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authorization\AuthorizationChecker;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
use Doctrine\ORM\EntityManager;
use Qi\Bss\BaseBundle\Entity\Business\PmodLog;

/**
 * Class AppLogger. Purchase Module logger.
 * @package FcConnectBundle\Lib
 */
class OrderLogger {

    private $em;
    private $tokenStorage;

    /**
     * Constructor.
     *
     * @param EntityManager $em
     * @param TokenStorage $securityTokenStorage
     */
    public function __construct(EntityManager $em, TokenStorage $securityTokenStorage)
    {
        $this->em = $em;
        $this->tokenStorage = $securityTokenStorage;
    }

    /**
     * Log an order action.
     *
     * @param string $text
     */
    public function log($order, $action)
    {
        $logRecord = new PmodLog();
        if (is_object($this->tokenStorage->getToken())) {
            $user = $this->tokenStorage->getToken()->getUser();
            if (is_object($user)) {
                $logRecord->setUser($user);
            }
        }
        $logRecord->setOrder($order);
        $logRecord->setAction($action);
        $logRecord->setTime(new \DateTime());

        $this->em->persist($logRecord);
        $this->em->flush();
    }

}

I have already tried changing the persist in my log to merge, but that also doesn't work. Can somebody please help and explain what I do wrong?

2
Your service shouldn't call $em->flush() in a prePersist event. - Alsatian
Not sure, but try to pass $args->getEntityManager() to the log method instead of constructor injection for the EntityManager. - Alsatian
@Alsatian I don't understand 100%, do have to remove the flush from my service? But then it's not going to work in my controllers where I call the same service. I need it in a controller and in my prePersist event. - Jack Brummer
Try it. I'm not 100% sure ;) If it solve the problem you can make two different services, one calling the second ... - Alsatian

2 Answers

0
votes

This is not the best architecture, but it will work:

On prePersist add all messages to some kind of private variable (like $logMessages), and add another event

/**
 * @param PostFlushEventArgs $args
 */
public function postFlush(PostFlushEventArgs $args)
{
    $logMessages = $this->logMessages;

    $this->logMessages = array(); //clean to avoid double logging

    if (!empty($logMessages)) {
        foreach ($logMessages as $message) {
            $this->serviceContainer->get('bss.pmod.order_logger')->log($message);
        }

    }
}
0
votes

I fixed the problem by adding a postPersist and call the logger in there instead of inside my prePersist;

/**
 * Performs tasks before destruction
 * @ORM\PostPersist
 */
public function postPersist(LifecycleEventArgs $args)
{
    $order = $args->getEntity();

    if ($order instanceof PmodOrder) {                
        $this->serviceContainer->get('bss.pmod.order_logger')->log($order, 'Order Created');
    }
}

Because what I think is happening is that the logger tries to be executed but the order in the logger doesn't yet exists as it is not yet persisted. This way makes more sense to me, and I think this is the easiest fix. I could be wrong though, any comments and other opinions on my answer are welcome.