0

I have a Symfony project which uses Doctrine2 as the main ORM. The project has multiple entities that are related to each other. I have the following entities:

Order - order in the shop
Product - products in orders
Addon - addons for products

The objects are linked in the following way:

Order
|- Product
|- Product
   |- Addon
   |- Addon

The Order and Product entity both have a "Status"-field. The status of Products is related to the status of the Order. The status of Products and Orders can change in a few ways; and they need to react to each other.

I was thinking of using Doctrine EntityListeners to listen to changes in the Entities. So when the status of an Order-entity happens, the Listener can update the status of the Products.

Detecting changes in the Order entity works great. But when I want to change the status of its Products, I will need to flush the changes in Doctrine. When I flush the changes of the Products from within the OrderListener, Symfony crashes.

TL;DR: What is the best way to detect changes in an Entity's field, and update other entities based on these changes.

Thanks in advance,

Wouter

services.yml

order_listener:
    class: AppBundle\EntityListener\OrderListener
    arguments: [ '@service_container' ]
    tags:
    - {name: doctrine.event_listener, event: preUpdate}
    - {name: doctrine.event_listener, event: postFlush}
product_listener:
    class: AppBundle\EntityListener\ProductListener
    arguments: [ '@service_container' ]
    tags:
    - {name: doctrine.event_listener, event: preUpdate}

OrderListener

namespace AppBundle\EntityListener;

use AppBundle\Entity\Product;
use AppBundle\Entity\Order;
use Doctrine\ORM\Event\PostFlushEventArgs;
use Doctrine\ORM\Event\PreUpdateEventArgs;
use Symfony\Component\DependencyInjection\Container;

class OrderListener {

    /**
     * @var Container
     */
    protected $container;

    /**
     * @var PreUpdateEventArgs[]
     */
    protected $changed = [];

    /**
     * OrderListener constructor.
     *
     * @param Container $container
     */
    public function __construct(Container $container) {

        $this->container = $container;
    }

    public function preUpdate(PreUpdateEventArgs $preUpdateEventArgs){

        if(!$preUpdateEventArgs->getEntity() instanceof Order)
            return;

        $this->changed[] = $preUpdateEventArgs;
    }

    public function postFlush(PostFlushEventArgs $postFlushEventArgs){

        foreach($this->changed as $preUpdateEventArgs){

            /** @var Product $product */
            $em = $this->container->get('doctrine')->getEntityManager();
            foreach($preUpdateEventArgs->getEntity()->getProducts() as $product) {

                $product->setStatus(Product::STATUS_COMPLETED);
                $em->persist($product);
            }

            $em->flush();
        }

        $this->changed = [];
    }
}

ProductListener

namespace AppBundle\EntityListener;

use AppBundle\Entity\Product;
use Doctrine\ORM\Event\PreUpdateEventArgs;
use Symfony\Component\DependencyInjection\Container;

class ProductListener {

    /**
     * @var Container
     */
    protected $container;

    /**
     * OrderListener constructor.
     *
     * @param Container $container
     */
    public function __construct(Container $container) {

        $this->container = $container;
    }

    public function preUpdate(PreUpdateEventArgs $eventArgs){

        if(!$eventArgs->getEntity() instanceof Product)
            return;

        dump($eventArgs);
    }
}

When flushing an Order Symfony dies with this error:

[Symfony\Component\Process\Exception\RuntimeException]  
The process has been signaled with signal "11".   
  • Please, add your code to the question and we'll be able to better help you. – Oscar Pérez Feb 26 '16 at 15:42
  • 1
    You have to read the events section of the Doctrine manual carefully. Lots of limitations and special cases. One thing you could do is to implement "change notification" which would work outside of Doctrine's listeners. More code but easier to reason about. http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/reference/change-tracking-policies.html#notify – Cerad Feb 26 '16 at 16:46
  • I've added the code for the listeners. @OscarPérez – Wouter Mijnhart Feb 26 '16 at 20:02

1 Answers1

0

Any modifications to other entities would have to go in an onFlush event. Calling persist in preUpdate will have unpredictable results. A good explanation with example is posted here.

Track field changes on Doctrine entity Further references can be found here Doctrine documentationin the Doctrine documentation.

tlorens
  • 478
  • 5
  • 16