Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using the same entity in both OneToMany and OneToOne #9

Closed
stefi023 opened this issue Feb 7, 2012 · 12 comments · Fixed by #110
Closed

Using the same entity in both OneToMany and OneToOne #9

stefi023 opened this issue Feb 7, 2012 · 12 comments · Fixed by #110

Comments

@stefi023
Copy link

stefi023 commented Feb 7, 2012

I have found an error while versioning entities like: (error is while auditing Customer entity)

class Address
{
    /**
     * @ORM\Column 
     */
    protected $address_text;

    /**
     * @ORM\ManyToOne(targetEntity="Customer", inversedBy="addresses") 
     */
    protected $customer;
}



class Customer 
{
    /**
    * @ORM\OneToMany(targetEntity="Address", mappedBy="customer")
    */
    protected $addresses;

    /**
    * @ORM\OneToOne(targetEntity="Address")
    */
    protected $primary_address;
}

the UnitOfWork::getEntityIdentifier throws an notice "Undefined index: ........"
in UnitOfWork::entityIdentifiers array - the hash is different that the stored one

TESTED ON Doctrine 2.1.3 and 2.1.6

@worenga
Copy link

worenga commented Apr 3, 2012

can confirm

@stefi023
Copy link
Author

stefi023 commented Apr 3, 2012

More info:
this only happens when both entities are NEW (new ...),
if they already exist ($em->getReference(...)), there is no problem.

@zerrvox
Copy link

zerrvox commented Aug 29, 2012

I can confirm this still exist and that the notice is raised by the UnitOfWork getEntityIdentifier() function called on line 203 in LogRevisionsListener.php

I will have a closer look at it later today, but as I do not have much experience with the inner workings of Doctrine2 it might take me some time to figure out what is happening.

I'm using Symfony v.2.1rc2 and Doctrine v2.3beta

@bobemoe
Copy link

bobemoe commented Jan 25, 2013

I am also experiencing this issue, or something very similar.

Class Topic{

    /**
     * @ORM\ManyToOne(targetEntity="Post", inversedBy="Topic")
     * @ORM\JoinColumn(name="last_post_id", referencedColumnName="id")
     */
    protected $lastPost;

    /**
     * @ORM\OneToMany(targetEntity="Post", mappedBy="topic")
     */
    protected $posts;
...
class Post{
    /**
     * @ORM\ManyToOne(targetEntity="Topic", inversedBy="Post")
     * @ORM\JoinColumn(name="topic_id", referencedColumnName="id")
     */
    protected $topic;
...

And in the controller, post and topic are created in one action:

$topic  = new Topic();
$post  = new Post();

$post->setTopic($topic);
$topic->setLastPost($post);

Notice: Undefined index: 000000004f7c8d4500000000349c774d in .../vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php line 2762

If I disable EntityAudit, then everything works fine.

Symfony 2.1.8-DEV
Doctrine 2.3.1

@Bouwdie
Copy link

Bouwdie commented Dec 10, 2013

For a possible solution check: #27

@andrewtch
Copy link
Contributor

@stefi023 , can you confirm that on more recent verions or is this obsolete?

@stefi023
Copy link
Author

Sorry, can't confirm, because I'm not using Doctrine anymore on daily basis. But if I will open some older project, I'll try to check. But can not promise :(

@rflorent
Copy link

Hello, I have same issue, with bidirectional onetoone $user <-> $customer
To see what happen, add @ before the function.

LogRevisionsListener.php

private function saveRevisionEntityData($class, $entityData, $revType)
    {
....
if ($entityData[$field] !== null) {
                    $relatedId = @$this->uow->getEntityIdentifier($entityData[$field]);
                }
....
}

In my case, EntityAudit can't save customer_id in user audit.
Because customer is not persisted yet.

Solutions ?

  • use unidirectional
  • persist first entity and add relation after (not tested), this will create two revision for first entity

@mpclarkson
Copy link

@andrewtch I can confirm that this is still a problem on dev-master. My work around is to persist the first entity and then add the association. It's a bit hacky but works for the time being.

@andrewtch
Copy link
Contributor

@rflorent , @mpclarkson , please give some code to illustrate this )

@mpclarkson
Copy link

Here we go. Note that this is not self-referencing as the original issue mentions but the same problem exists.

When creating a new thread the workflow is:

  1. Post a new comment to a route that advises it is a new thread rather than a reply
  2. Create a new Thread Entity
  3. Create a new Comment Entity
  4. Associate the Comment with the Thread
  5. Handle the form and persist the comment (the association is set to cascade on persist).

Unless I explicitly persist and flush the new Thread BEFORE handling the comment form, the undefined index exception is thrown.

The Thread class

/**
 * @ORM\Entity(repositoryClass="Umm\CommentBundle\Repository\ThreadRepository")
 * @ORM\Table(name="comment_thread",
 *  indexes={
 *      @ORM\Index(name="idx_thread_object", columns={"object_class", "object_id"}),
 *    }
 * )
 * @ORM\ChangeTrackingPolicy("DEFERRED_EXPLICIT")
 * @ORM\HasLifecycleCallbacks
 *
 * @Gedmo\SoftDeleteable(fieldName="deletedAt")
 *
 */
class Thread extends BaseEntity implements ThreadInterface, OwnableInterface
{
    //other properties

    /**
     * The primary comment for the thread
     *
     * @ORM\OneToOne(targetEntity="Comment", cascade={"persist", "remove"})
     * @ORM\JoinColumn(name="primary_comment_id", referencedColumnName="id", nullable=true, onDelete="set null")
     * @JMS\Groups({"detail", "summary"})
     *
     */
    private $primaryComment;

    /**
     * Comment array collection
     *
     * @ORM\OneToMany(targetEntity="Comment", mappedBy="thread", cascade={"persist", "remove"}, orphanRemoval=true)
     * @ORM\OrderBy({"created"="ASC"})
     * @JMS\MaxDepth(2)
     * @JMS\Accessor(getter="getReplies")
     * @JMS\Groups({"detail"})
     * @JMS\Type("array")
     */
    private $comments;

   //other properties
}

The comment class

/**
 * @ORM\Entity(repositoryClass="Umm\CommentBundle\Repository\CommentRepository")
 * @ORM\Table(name="comment_comment")
 * @ORM\ChangeTrackingPolicy("DEFERRED_EXPLICIT")
 * @ORM\HasLifecycleCallbacks
 *
 * @Gedmo\SoftDeleteable(fieldName="deletedAt")
 *
 */
class Comment extends BaseEntity implements CommentInterface, LikeableInterface, OwnableInterface, ProjectTimelineInterface, SerializePermissionsInterface
{
  //other properties
   /**
     *
     * @var Thread
     * @ORM\ManyToOne(targetEntity="Umm\CommentBundle\Entity\Thread", inversedBy="comments",  cascade={"persist"})
     * @JMS\Exclude()
     *
     */
    private $thread;

   //other properties

}

The controller that creates the thread from the new comment

   /**
     * Create a new thread (top level comment) for an object.
     *
     * @Rest\Post("/threads/object/{objectKey}")
     *
     * @param $objectKey the object's thread key
     * @param Request $request
     * @return array|\FOS\RestBundle\View\View|null
     */
    public function postThreadAction($objectKey, Request $request)
    {
        $parent = $this->getObjectFromKeyOr404($objectKey);

        $thread = new Thread($parent);

        //If I do not include the following three lines the audit bundle throws an error. Without the audit bundle it persists fine.
        $em = $this->getDoctrine()->getManager();
        $em->persist($thread);
        $em->flush();

        try {

            $entity = new Comment($thread);
            $thread->setPrimaryComment($entity);

            $this->processForm($entity, $request, 'POST');

            $routeOptions = array(
                'id' => $thread->getId(),
                '_format' => $request->get('_format')
            );

            $event = new StatisticEvent($thread->getProject(), Statistic::COMMENT);
            $this->get('event_dispatcher')->dispatch(StatisticEvents::NEW_STATISTIC, $event);

            return $this->routeRedirectView('api_1_get_comment_thread',  $routeOptions, Codes::HTTP_CREATED)
                ->setData($thread);

        }
        catch (InvalidFormException $exception) {

            //Hacky. If there was an error remove the thread (not required without the audit bundle).
            $em->remove($thread);
            $em->flush();

            //Return the error
            return $exception->getForm();
        }
    }

/**
* Handle the form
*/
private function processForm(CommentInterface $entity, Request $request, $method = "PUT")
    {
        $form = $this->get('form.factory')->createNamed(null, 'comment_type', $entity, array('method' => $method));
        $form->submit($request, 'PATCH' !== $method);

        if ($form->isValid()) {

            $em = $this->getDoctrine()->getManager();
            $entity = $form->getData();
            $em->persist($entity);
            $em->flush();

            return $entity;
        }

        throw new InvalidFormException('Invalid data', $form);
    }

@rflorent
Copy link

I use nested forms.
On saving, I split related entities, save master entity, add related entities, update master entity :

// create or update ?
$userId = $user->getId();

// related entity ? yes => store it
$profil = $user->getProfil();

//if create and related entity
 if(!$userId && $profil) {

        //remove related entity
        $user->setProfil(null);
        $em->persist($user);
        $em->flush();

        // add related entity
        $user->setProfil($profil);
}

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

richgerdes pushed a commit to Double-Star-Systems/EntityAuditBundle that referenced this issue Apr 10, 2020
…ct#9)

Updates the requirements on [symfony/var-dumper](https://github.com/symfony/var-dumper) to permit the latest version.
- [Release notes](https://github.com/symfony/var-dumper/releases)
- [Changelog](https://github.com/symfony/var-dumper/blob/master/CHANGELOG.md)
- [Commits](symfony/var-dumper@v2.7.0...v3.4.31)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants