Symfony 2 UniqueEntity with two associations is not working. #6727

Closed
Sukhrob opened this Issue Jan 14, 2013 · 19 comments

Comments

Projects
None yet
9 participants
@Sukhrob

Sukhrob commented Jan 14, 2013

use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;

/**
 * @UniqueEntity({"field1", "field2"})
 * @ORM\Table(name = "myentity")
 */
class MyEntity
{
    /**
     * @ORM\ManyToOne(.....)
     * @ORM\JoinColumn(.....)
     */
     protected $field1;

   /**
    * @ORM\ManyToOne(.....)
    * @ORM\JoinColumn(.....)
    */
    protected $field2;
}

It works with non-association fields, though. I am using Symfony 2.1.4 and Doctrine (>=2.2.3,<2.4-dev). Can anyone help? Thanks.

@Sukhrob

This comment has been minimized.

Show comment
Hide comment
@Sukhrob

Sukhrob Jan 18, 2013

I suppose it is not implemented yet. Is that right?

Sukhrob commented Jan 18, 2013

I suppose it is not implemented yet. Is that right?

@xphere

This comment has been minimized.

Show comment
Hide comment
@xphere

xphere Mar 8, 2013

Contributor

Is this bug still around? Can you explain exactly the meaning of "is not working"?

Contributor

xphere commented Mar 8, 2013

Is this bug still around? Can you explain exactly the meaning of "is not working"?

@sp0ken

This comment has been minimized.

Show comment
Hide comment
@sp0ken

sp0ken Apr 19, 2013

I can confirm that the bug is still in Symfony 2.2.1.
When specifying a @uniqueentity() with two associations as fields the validation does nothing. To be more precise, Apache process takes 100% of the CPU and the request times out with no message or info after around 25s.

sp0ken commented Apr 19, 2013

I can confirm that the bug is still in Symfony 2.2.1.
When specifying a @uniqueentity() with two associations as fields the validation does nothing. To be more precise, Apache process takes 100% of the CPU and the request times out with no message or info after around 25s.

@Sukhrob

This comment has been minimized.

Show comment
Hide comment
@Sukhrob

Sukhrob Apr 19, 2013

@xphere Well, I think you got the answer from @sp0ken.

Sukhrob commented Apr 19, 2013

@xphere Well, I think you got the answer from @sp0ken.

@xphere

This comment has been minimized.

Show comment
Hide comment
@xphere

xphere Apr 19, 2013

Contributor

@Sukhrob @sp0ken having a minimal project that reproduces the bug would help, I'll try to do that and see what happens

Contributor

xphere commented Apr 19, 2013

@Sukhrob @sp0ken having a minimal project that reproduces the bug would help, I'll try to do that and see what happens

@sp0ken

This comment has been minimized.

Show comment
Hide comment
@sp0ken

sp0ken Apr 19, 2013

Here is my entity with the failing UniqueEntity if it helps. And I'm manually validating it in my controller (ajax request).


namespace Jacotte\UserBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
use Symfony\Component\Validator\Constraints as Assert;
use Jacotte\ProductBundle\Validator\Constraints as JacotteAssert;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;

/**
 * Like
 *
 * @ORM\Table(name="Liked", uniqueConstraints={
 *     @ORM\UniqueConstraint(columns={ "user_id", "product_id"}),
 *     @ORM\UniqueConstraint(columns={ "user_id", "comment_id"}),
 *     @ORM\UniqueConstraint(columns={ "user_id", "photo_id"}),
 *     @ORM\UniqueConstraint(columns={ "user_id", "video_id"})
 * })
 * @ORM\Entity(repositoryClass="Jacotte\UserBundle\Entity\LikeRepository")
 * @ORM\UniqueEntity(fields={"user", "product"})
 */
class Like
{
    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * Liked product
     * @var \Jacotte\ProductBundle\Entity\Product $product
     * 
     * @ORM\ManyToOne(targetEntity="\Jacotte\ProductBundle\Entity\Product", inversedBy="likes")
     */
    private $product;

    /**
     * Liked comment
     * @var \Jacotte\ProductBundle\Entity\Comment $comment
     * 
     * @ORM\ManyToOne(targetEntity="\Jacotte\UserBundle\Entity\Comment", inversedBy="likes")
     */
    private $comment;

    /**
     * Liked video
     * @var \Jacotte\MediaBundle\Entity\Video $video
     * 
     * @ORM\ManyToOne(targetEntity="\Jacotte\MediaBundle\Entity\Video", inversedBy="likes")
     */
    private $video;

    /**
     * Liked Photo
     * @var \Jacotte\MediaBundle\Entity\Photo $Photo
     * 
     * @ORM\ManyToOne(targetEntity="\Jacotte\MediaBundle\Entity\Photo", inversedBy="likes")
     */
    private $photo;

    /**
     * Reviewer
     * @var \Jacotte\UserBundle\Entity\User $user
     * 
     * @ORM\ManyToOne(targetEntity="\Jacotte\UserBundle\Entity\User", inversedBy="likes")
     */
    private $user;

    /**
     * Date of creation of this comment
     * @var datetime $created_at
     *
     * @Gedmo\Timestampable(on="create")
     * @ORM\Column(type="datetime")
     */
    private $created_at;

    /**
     * Date of the last update of this comment
     * @var datetime $updated_at
     *
     * @Gedmo\Timestampable(on="update")
     * @ORM\Column(type="datetime")
     */
    private $updated_at;

And here is part of the controller :

$like = new Like();
$like->setUser($this->getUser());
$like->setProduct($product);

$validator = $this->get('validator');
$errorList = $validator->validate($product);

sp0ken commented Apr 19, 2013

Here is my entity with the failing UniqueEntity if it helps. And I'm manually validating it in my controller (ajax request).


namespace Jacotte\UserBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
use Symfony\Component\Validator\Constraints as Assert;
use Jacotte\ProductBundle\Validator\Constraints as JacotteAssert;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;

/**
 * Like
 *
 * @ORM\Table(name="Liked", uniqueConstraints={
 *     @ORM\UniqueConstraint(columns={ "user_id", "product_id"}),
 *     @ORM\UniqueConstraint(columns={ "user_id", "comment_id"}),
 *     @ORM\UniqueConstraint(columns={ "user_id", "photo_id"}),
 *     @ORM\UniqueConstraint(columns={ "user_id", "video_id"})
 * })
 * @ORM\Entity(repositoryClass="Jacotte\UserBundle\Entity\LikeRepository")
 * @ORM\UniqueEntity(fields={"user", "product"})
 */
class Like
{
    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * Liked product
     * @var \Jacotte\ProductBundle\Entity\Product $product
     * 
     * @ORM\ManyToOne(targetEntity="\Jacotte\ProductBundle\Entity\Product", inversedBy="likes")
     */
    private $product;

    /**
     * Liked comment
     * @var \Jacotte\ProductBundle\Entity\Comment $comment
     * 
     * @ORM\ManyToOne(targetEntity="\Jacotte\UserBundle\Entity\Comment", inversedBy="likes")
     */
    private $comment;

    /**
     * Liked video
     * @var \Jacotte\MediaBundle\Entity\Video $video
     * 
     * @ORM\ManyToOne(targetEntity="\Jacotte\MediaBundle\Entity\Video", inversedBy="likes")
     */
    private $video;

    /**
     * Liked Photo
     * @var \Jacotte\MediaBundle\Entity\Photo $Photo
     * 
     * @ORM\ManyToOne(targetEntity="\Jacotte\MediaBundle\Entity\Photo", inversedBy="likes")
     */
    private $photo;

    /**
     * Reviewer
     * @var \Jacotte\UserBundle\Entity\User $user
     * 
     * @ORM\ManyToOne(targetEntity="\Jacotte\UserBundle\Entity\User", inversedBy="likes")
     */
    private $user;

    /**
     * Date of creation of this comment
     * @var datetime $created_at
     *
     * @Gedmo\Timestampable(on="create")
     * @ORM\Column(type="datetime")
     */
    private $created_at;

    /**
     * Date of the last update of this comment
     * @var datetime $updated_at
     *
     * @Gedmo\Timestampable(on="update")
     * @ORM\Column(type="datetime")
     */
    private $updated_at;

And here is part of the controller :

$like = new Like();
$like->setUser($this->getUser());
$like->setProduct($product);

$validator = $this->get('validator');
$errorList = $validator->validate($product);
@Sukhrob

This comment has been minimized.

Show comment
Hide comment
@Sukhrob

Sukhrob Apr 19, 2013

@sp0ken Why your fields are private? They should be protected, I think. It was written in doctrine's documentation. Try to use protected. Maybe, it works. But, it didn't work in 2.1.4 as I remember.

Sukhrob commented Apr 19, 2013

@sp0ken Why your fields are private? They should be protected, I think. It was written in doctrine's documentation. Try to use protected. Maybe, it works. But, it didn't work in 2.1.4 as I remember.

@sp0ken

This comment has been minimized.

Show comment
Hide comment
@sp0ken

sp0ken Apr 19, 2013

They're set on private by default when using symfony console to generate entities. No luck however when making them protected.

sp0ken commented Apr 19, 2013

They're set on private by default when using symfony console to generate entities. No luck however when making them protected.

@Sukhrob

This comment has been minimized.

Show comment
Hide comment
@Sukhrob

Sukhrob Apr 19, 2013

Well, I remember that they were protected by default before 2.1.4. Well, it seems it has changed in 2.2.

Sukhrob commented Apr 19, 2013

Well, I remember that they were protected by default before 2.1.4. Well, it seems it has changed in 2.2.

@stof

This comment has been minimized.

Show comment
Hide comment
@stof

stof Apr 19, 2013

Member

It has nothing to do with Symfony 2.1 or 2.2. The EntityGenerator is not part of Symfony but of Doctrine

Member

stof commented Apr 19, 2013

It has nothing to do with Symfony 2.1 or 2.2. The EntityGenerator is not part of Symfony but of Doctrine

@xphere

This comment has been minimized.

Show comment
Hide comment
@xphere

xphere Apr 20, 2013

Contributor

@sp0ken @Sukhrob I'm unable to reproduce the buggy behaviour in 2.2.1 nor in 2.1.4
I'm using this simpler case: https://gist.github.com/xphere/5425313
Could you try it?

Contributor

xphere commented Apr 20, 2013

@sp0ken @Sukhrob I'm unable to reproduce the buggy behaviour in 2.2.1 nor in 2.1.4
I'm using this simpler case: https://gist.github.com/xphere/5425313
Could you try it?

@sp0ken

This comment has been minimized.

Show comment
Hide comment
@sp0ken

sp0ken Apr 22, 2013

It's now working for me, but I'm not sure what really changed as it is also working with my previous code. Anyways thanks @xphere

sp0ken commented Apr 22, 2013

It's now working for me, but I'm not sure what really changed as it is also working with my previous code. Anyways thanks @xphere

@fabpot fabpot closed this Apr 22, 2013

@xphere

This comment has been minimized.

Show comment
Hide comment
@xphere

xphere Apr 22, 2013

Contributor

@sp0ken Glad it worked now, I hope it does too for @Sukhrob

Contributor

xphere commented Apr 22, 2013

@sp0ken Glad it worked now, I hope it does too for @Sukhrob

@VincentChalnot

This comment has been minimized.

Show comment
Hide comment
@VincentChalnot

VincentChalnot Jul 20, 2017

I think I'm experiencing the same problem. (PHP7.1, Symfony 3.2.12, Doctrine ORM 2.5.6)

I've tracked down the source of the issue and it's happening during the comparison of the two objects:
\Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntityValidator:162

$entity === ($result instanceof \Iterator ? $result->current() : current($result))

While both "entities" are clearly the same (same ids, same values), I think the actual PHP instances are slightly different. I'm not even modifying anything on the entity before validation.

Maybe this has something to do with entities hydration and proxies ? Depending on how you query objects you might hydrate relations slightly differently...

I think the usage of the equality operator should be dropped in favor of something more appropriate, like class & primary key comparison...

Here's an example of what I'm talking about:
screenshot_20170720_165216

Of course it doesn't mean much because we should be looking at the entire value's tree of each objects to really compare them but in my opinion this test should match all entities with the same class and same primary keys, leaving any other values untested.

I think I'm experiencing the same problem. (PHP7.1, Symfony 3.2.12, Doctrine ORM 2.5.6)

I've tracked down the source of the issue and it's happening during the comparison of the two objects:
\Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntityValidator:162

$entity === ($result instanceof \Iterator ? $result->current() : current($result))

While both "entities" are clearly the same (same ids, same values), I think the actual PHP instances are slightly different. I'm not even modifying anything on the entity before validation.

Maybe this has something to do with entities hydration and proxies ? Depending on how you query objects you might hydrate relations slightly differently...

I think the usage of the equality operator should be dropped in favor of something more appropriate, like class & primary key comparison...

Here's an example of what I'm talking about:
screenshot_20170720_165216

Of course it doesn't mean much because we should be looking at the entire value's tree of each objects to really compare them but in my opinion this test should match all entities with the same class and same primary keys, leaving any other values untested.

@xabbuh

This comment has been minimized.

Show comment
Hide comment
@xabbuh

xabbuh Jul 20, 2017

Member

If you are able to provide an example project that allows to reproduce your issue, please open a new issue containing all the needed information.

Member

xabbuh commented Jul 20, 2017

If you are able to provide an example project that allows to reproduce your issue, please open a new issue containing all the needed information.

@VincentChalnot

This comment has been minimized.

Show comment
Hide comment
@VincentChalnot

VincentChalnot Jul 21, 2017

Ok, I've found the explanation:

  • Both Doctrine entities are the same
  • PHP objects are differents

One entity was detached from the Unit of Work and not the other.

The current code is good because it prevents detached entities from passing the test, thus preventing exceptions on insert queries.
👍

VincentChalnot commented Jul 21, 2017

Ok, I've found the explanation:

  • Both Doctrine entities are the same
  • PHP objects are differents

One entity was detached from the Unit of Work and not the other.

The current code is good because it prevents detached entities from passing the test, thus preventing exceptions on insert queries.
👍

@ebuildy

This comment has been minimized.

Show comment
Hide comment
@ebuildy

ebuildy Jan 2, 2018

Also, pay attention that the validation is made before Doctrine trigger events (such as prePersist/postPersist), so some entity fields could not be set yet if you use Doctrine event listener to fill some fields.

ebuildy commented Jan 2, 2018

Also, pay attention that the validation is made before Doctrine trigger events (such as prePersist/postPersist), so some entity fields could not be set yet if you use Doctrine event listener to fill some fields.

@nik21

This comment has been minimized.

Show comment
Hide comment
@nik21

nik21 Jan 24, 2018

And what is good solution of this problem?

nik21 commented Jan 24, 2018

And what is good solution of this problem?

@stof

This comment has been minimized.

Show comment
Hide comment
@stof

stof Jan 24, 2018

Member

@nik21 the validator component has a concept of initializer, which will be called before running the validation on the object. So the solution is to use this feature instead of setting the value only in the Doctrine listener.

Member

stof commented Jan 24, 2018

@nik21 the validator component has a concept of initializer, which will be called before running the validation on the object. So the solution is to use this feature instead of setting the value only in the Doctrine listener.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment