1

I'm working on some kind of "complex" form in my project where entities are persisted on each steps since individual forms are split on them. Then I've a first step (lets call it step1) where I persist a entity and also store it on the session, see code below:

$productoSolicitudEntity = new Entity\ProductoSolicitud();
$productoSolicitudForm = $this->createForm(new Form\ProductoSolicitudForm(), $productoSolicitudEntity);
$productoSolicitudForm->handleRequest($request);

if ($productoSolicitudForm->isValid()) {
    $productoSolicitudRequest = $request->get('productoSolicitud');

    try {
        $producto = $em->getRepository("AppBundle:Producto")->find($productoSolicitudRequest['producto']['nombre']);
        $productoSolicitudEntity->setProducto($producto);

        $condicionProducto = $em->getRepository("AppBundle:CondicionProducto")->find($productoSolicitudRequest['condicion_producto']);
        $productoSolicitudEntity->setCondicionProducto($condicionProducto);

        $finalidadProducto = $em->getRepository("AppBundle:FinalidadProducto")->find($productoSolicitudRequest['finalidad_producto']);
        $productoSolicitudEntity->setFinalidadProducto($finalidadProducto);

        $procedenciaProducto = $em->getRepository("AppBundle:ProcedenciaProducto")->find($productoSolicitudRequest['procedencia_producto']);
        $productoSolicitudEntity->setProcedenciaProducto($procedenciaProducto);

        $productoSolicitudEntity->setLote($productoSolicitudRequest['lote']);
        $solicitudUsuario = $em->getRepository("AppBundle:SolicitudUsuario")->find($session->get('solicitudUsuarioEntity')->getId());
        $productoSolicitudEntity->setSolicitudUsuario($solicitudUsuario);

        $em->persist($productoSolicitudEntity);
        $em->flush();

        $session->set('productoSolicitudEntity', $productoSolicitudEntity);
        $response['success'] = true;
    } catch (Exception $ex) {
        $status = 400;
        $response['error'] = $ex->getMessage();
    }
} else {
    $status = 400;
    $response['error'] = $this->get('translator')->trans('formularioNoValido');
    $response['formError'] = $this->getFormErrors($productoSolicitudForm);
}

Then in the four step (lets call it step4) I need to attach that entity to a new one since they are related and this is the code involve:

$productoSolicitud = $session->get('productoSolicitudEntity');

if (! $productoSolicitud) {
    $status = 400;
    $response['error'] = 'No se encontró la solicitud';
}

$distribuidorEntity = new Entity\FabricanteDistribuidor();
$distribuidorForm = $this->createForm(new Form\DistribuidorForm(), $distribuidorEntity);

$distribuidorForm->handleRequest($request);

if ($distribuidorForm->isValid()) {
    $em->persist($distribuidorEntity);
    $em->flush();
    $session->set('distribuidorEntity', $distribuidorEntity);

    $distribuidorProductoSolicitudEntity = new Entity\DistribuidorProductoSolicitud();
    $distribuidorProductoSolicitudEntity->setProductoSolicitud($productoSolicitud);
    $distribuidorProductoSolicitudEntity->setFabricanteDistribuidor($distribuidorEntity);
    $em->persist($distribuidorProductoSolicitudEntity);
    $em->flush();
    $session->set('distribuidorEntity', $distribuidorEntity);
}

But I'm getting this error:

A new entity was found through the relationship 'AppBundle\Entity\DistribuidorProductoSolicitud#producto_solicitud' that was not configured to cascade persist operations for entity: AppBundle\Entity\ProductoSolicitud@000000000a1f3e9d00007f88c54033f8. 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"}). If you cannot find out which entity causes the problem implement 'AppBundle\Entity\ProductoSolicitud#__toString()' to get a clue.

Since the conflictive entity seems to be DistribuidorProductoSolicitud then I made this change on it:

/**
 * @ORM\ManyToOne(targetEntity="\AppBundle\Entity\ProductoSolicitud", cascade={"persist"})
 * @ORM\JoinColumn(name="producto_solicitud_id", referencedColumnName="id")
 */
protected $producto_solicitud;

But does not solve the issue, any help? What's is wrong? What I'm missing here? I should add a method __toString() at ProductoSolicitud entity but what this should return?

This are the entities involved on the issue:

class DistribuidorProductoSolicitud
{
    use IdentifierAutogeneratedEntityTrait;

    /**
     * @ORM\ManyToOne(targetEntity="AppBundle\Entity\FabricanteDistribuidor")
     * @ORM\JoinColumn(name="fabricante_distribuidor_id", referencedColumnName="id")
     */
    protected $fabricante_distribuidor;

    /**
     * @ORM\ManyToOne(targetEntity="AppBundle\Entity\ProductoSolicitud", cascade={"persist"})
     * @ORM\JoinColumn(name="producto_solicitud_id", referencedColumnName="id")
     */
    protected $producto_solicitud;

    /**
     * @ORM\ManyToMany(targetEntity="AppBundle\Entity\Pais", inversedBy="distribuidorProductoSolicitudPais", cascade={"persist"})
     * @ORM\JoinTable(name="nomencladores.pais_distribuidor_producto_solicitud", schema="nomencladores",
     *      joinColumns={@ORM\JoinColumn(name="distribuidor_producto_solicitud_id", referencedColumnName="id")},
     *      inverseJoinColumns={@ORM\JoinColumn(name="pais_id", referencedColumnName="id")}
     * )
     */
    protected $paisesDistribuidorProductoSolicitudPais;
}


class ProductoSolicitud
{
    use IdentifierAutogeneratedEntityTrait;

    /**
     * @var \Producto
     *
     * @ORM\ManyToOne(targetEntity="AppBundle\Entity\Producto")
     * @ORM\JoinColumn(name="producto_id", referencedColumnName="id")
     */
    protected $producto;

    /**
     * @var \SolicitudUsuario
     *
     * @ORM\ManyToOne(targetEntity="AppBundle\Entity\SolicitudUsuario", cascade={"persist"})
     * @ORM\JoinColumn(name="solicitud_usuario_id", referencedColumnName="id")
     */
    protected $solicitud_usuario;

    /**
     * @var \CondicionProducto
     *
     * @ORM\ManyToOne(targetEntity="AppBundle\Entity\CondicionProducto")
     * @ORM\JoinColumn(name="condicion_producto_id", referencedColumnName="id")
     */
    protected $condicion_producto;

    /**
     * @var \FinalidadProducto
     *
     * @ORM\ManyToOne(targetEntity="AppBundle\Entity\FinalidadProducto")
     * @ORM\JoinColumn(name="finalidad_producto_id", referencedColumnName="id")
     */
    protected $finalidad_producto;

    /**
     * @ORM\Column(name="lote", type="integer", nullable=false)
     */
    protected $lote;

    /**
     * @var \ProcedenciaProducto
     *
     * @ORM\ManyToOne(targetEntity="AppBundle\Entity\ProcedenciaProducto")
     * @ORM\JoinColumn(name="procedencia_producto_id", referencedColumnName="id")
     */
    protected $procedencia_producto;
}

Where the cascade={"persist"} should go in order to fix it?

I've found this post but it's no helpful.

Community
  • 1
  • 1
ReynierPM
  • 17,594
  • 53
  • 193
  • 363

2 Answers2

7

Saving (or serializing) a Doctrine entity to the session is problematic (here's a relevant SO question/answer on the matter) - since it loses the private properties that are needed to detect a hydrated Doctrine object that can be recognized in the system.

Since those private properties of a hydrated Doctrine object are missing, it perceives these unhydrated objects as entirely new (and the other associated objects.)

Your best solution is to only store the Object identifier in the session and retrieve them later with the find() helper function.

To store:

$this->get('session')->set('objectId', $object->getId());

To fetch later:

$objectId = $this->get('session')->get('objectId');
$object = $this->getDoctrine()->getRepository('AcmeBundle:Entity')->find($objectId);
Community
  • 1
  • 1
sjagr
  • 15,983
  • 5
  • 40
  • 67
  • What you mean with "only store the Object identifier in the session and retrieve them later with the find() helper function"? Can you write a piece of code demonstrating what you mean? I didn't know how to do this – ReynierPM Nov 20 '14 at 15:18
  • 1
    It's basic stuff - I've added the examples. Make sure you substitute in the correct entity names. – sjagr Nov 20 '14 at 15:21
  • Ok, fine, one more extra question, should I remove the session before set it? For example: `$session->remove('solicitudUsuarioId'); $session->set('solicitudUsuarioId', $solicitudUsuarioEntity->getId());` or all the time it's overwrite? – ReynierPM Nov 20 '14 at 15:26
  • 2
    @ReynierPM It behaves like a regular PHP array - if you're using the same key every time, it will overwrite :) – sjagr Nov 20 '14 at 15:28
  • Instead of find you can use reference proxy (it doesn't perform any db query). – Karol Wojciechowski Nov 21 '14 at 10:48
  • @KarolWojciechowski I know about entity references. OP needs to manipulate the entity data and persist it back to the database. – sjagr Nov 21 '14 at 14:06
  • How does this work? If the object is not persisted then there will be no ID? – Jake N Jul 24 '15 at 00:03
  • I faced similar situation, and after a lot of debug, I was looking for the "why", thanks for the clarification – Charaf JRA Mar 04 '17 at 15:11
1

Try to add cascade={"persist"} to both sides of your ManyToOne (in ProductoSolicitud and DistribuidorProductoSolicitud).

If this ManyToOne is unidirectional, try to change it to a OneToMany bidirectional with cascade persist on both sides.

In class ProductoSolicitud:

/**
 * @OneToMany(targetEntity="AppBundle\Entity\DistribuidorProductoSolicitud", mappedBy="producto_solicitud", cascade={"persist"})
   @var \Doctrine\Common\Collections\ArrayCollection
 **/
 private $distribuidor_producto_solicidudes;

In class DistribuidorProductoSolicidud:

/**
 * @ORM\ManyToOne(targetEntity="AppBundle\Entity\ProductoSolicitud", inversedBy="distribuidor_producto_solicidudes", cascade={"persist"})
 * @ORM\JoinColumn(name="producto_solicitud_id", referencedColumnName="id")
   @var \AppBundle\Entity\ProductoSolicidud
 */
 protected $producto_solicitud;
Yoann Wai
  • 11
  • 3
  • Ok, give me a minute to edit the post and add the involved entities since I get lost and don't know where exactly I should add `cascade={"persist"}` – ReynierPM Nov 20 '14 at 15:08
  • done, take a look please and improve your answer with code example if you can and thanks in advance – ReynierPM Nov 20 '14 at 15:12