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

[DoctrineBridge] UniqueEntity: Support a ManyToOne related entity as "field" #58282

Closed
ThomasLandauer opened this issue Sep 16, 2024 · 14 comments

Comments

@ThomasLandauer
Copy link
Contributor

Description

I would like to be able to set up UniqueEntity so that $name must be unique for each $parent.

Example

#[UniqueEntity(fields: ['parent', 'name'])] // <= Name of the property pointing to the related entity
class Child
{
    #[ORM\ManyToOne(targetEntity: Parent::class)]
    private ?Parent $parent = null;

    #[ORM\Column(nullable: true)]
    private ?string $name = null;
}

Right now, this throws an Doctrine\ORM\Persisters\Exception\UnrecognizedField:

Unrecognized field: App\Entity\Parent::$parent

In the parent class, I'm having:

#[ORM\OneToMany(targetEntity: EmpfaengerProdukt::class)]
#[Assert\Valid]
private Collection $children;

And I'm submitting the data with a CollectionType.

@lrlopez
Copy link
Contributor

lrlopez commented Sep 22, 2024

Did you try specifying the name of the column directly? It should work.

#[UniqueEntity(fields: ['parent_id', 'name'])] 

@ThomasLandauer
Copy link
Contributor Author

  • With your suggestion, I'm getting:

    The field "parent_id" is not mapped by Doctrine, so it cannot be validated for uniqueness.

  • And when I change it to (columns: ...), I'm getting:

    Unknown named parameter $columns

When looking at the initial error message again:

Unrecognized field: App\Entity\Parent::$parent

What I don't understand is that the constraint is obviously looking for a $parent property in the Parent entity?!
This observation is also backed by the following:
If I remove parent, and just keep the second field:

#[UniqueEntity(fields: ['position'])]

... I'm getting:

Unrecognized field: App\Entity*Parent*::$position

So it looks like in this case UniqueEntity is getting evaluated in the "wrong" (i.e. unexpected) entity!? Why is this the case, and how can I reference the child entity's fields?

@bobvandevijver
Copy link
Contributor

Aren't you just missing the #[ORM\Entity] attribute on your child class?

@ThomasLandauer
Copy link
Contributor Author

No, mapping is fine. Both entities are getting persisted to the database for years; just wanted to add UniqueEntity now for nicer error messages.

@lrlopez
Copy link
Contributor

lrlopez commented Sep 25, 2024

Ah, sorry. I thought we we're talking about UniqueConstraint.

This should work for you, but uses a different constraint:

#[UniqueConstraint(columns: ['parent_id', 'name'])] 

@ThomasLandauer
Copy link
Contributor Author

Yeah, sure, I already have UniqueConstraint ;-) But I want UniqueEntity too - that's why I opened this issue.

@xabbuh
Copy link
Member

xabbuh commented Sep 25, 2024

PR welcome I guess :)

@bobvandevijver
Copy link
Contributor

I am have been using this constraint for years with ManyToOne relations and it is working great, so there must be some sort of issue with the Doctrine mapping here. Hard to say without a reproducer...

@xabbuh
Copy link
Member

xabbuh commented Sep 25, 2024

@ThomasLandauer Can you create a small example application that allows to reproduce the behaviour that you observe?

@ThomasLandauer
Copy link
Contributor Author

@bobvandevijver Could you show me your syntax please?

@bobvandevijver
Copy link
Contributor

bobvandevijver commented Sep 25, 2024

While copying the stuff below, looks like you might be missing the mappedBy and inversedBy properties on the relation attributes. What's your output of for bin/console doctrine:schema:validate?


Sure, I have for example:

namespace App\Entity\Equipment;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Validator\Constraints as Assert;

#[ORM\Entity]
#[ORM\UniqueConstraint(fields: ['equipment', 'name'])]
#[UniqueEntity(fields: ['equipment', 'name'])]
class EquipmentConsumable
{
  #[ORM\Column(name: 'id')]
  #[ORM\Id]
  #[ORM\GeneratedValue]
  protected ?int $id = null;
  
  #[ORM\ManyToOne(inversedBy: 'consumables')]
  #[ORM\JoinColumn(nullable: false)]
  #[Assert\NotNull]
  private ?Equipment $equipment = null;

  #[ORM\Column(length: 50)]
  protected string $name = '';
}
namespace App\Entity\Equipment;

use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity(repositoryClass: EquipmentRepository::class)]
class Equipment
{
  #[ORM\Column(name: 'id')]
  #[ORM\Id]
  #[ORM\GeneratedValue]
  protected ?int $id = null;

  /** @var Collection<int, EquipmentConsumable> */
  #[ORM\OneToMany(mappedBy: 'equipment', targetEntity: EquipmentConsumable::class, fetch: 'EXTRA_LAZY')]
  private Collection $consumables; // Default in constructor

  public function __construct()
  {
    $this->consumables = new ArrayCollection();
  }
}

@ThomasLandauer
Copy link
Contributor Author

In my real entities I do have mappedBy and inversedBy; bin/console doctrine:schema:validate gives me:

Mapping
-------
 [OK] The mapping files are correct. 

Database
--------                                                                                                                        
 [ERROR] The database schema is not in sync with the current mapping file.

(The second error is strange, cause php bin/console make:migration creates an empty migration)

So one more question before I sit down and create a reproducer :-)
Did you actually try to submit multiple "identical" consumables? (i.e. did you ever see the error message created by UniqueEntity?) Cause in my case everything is fine till I actually submit the form with "wrong" data.

BTW: I think your #[ORM\AssociationOverrides() does nothing.

@bobvandevijver
Copy link
Contributor

The database out of sync shouldn't really matter, but it is still worth looking into that. Using doctrine:schema:update --dump-sql could help.

Did you actually try to submit multiple "identical" consumables? (i.e. did you ever see the error message created by UniqueEntity?) Cause in my case everything is fine till I actually submit the form with "wrong" data.

Yes, the default "This value is already used." is shown as it expected.

BTW: I think your #[ORM\AssociationOverrides() does nothing.

That is because I simplified the definition here by moving my trait inline and forgot to remove that remnant.

@ThomasLandauer
Copy link
Contributor Author

Sorry guys, my fault!
I finally noticed that in my Child entity, I was referring to the wrong Repository:

#[ORM\Entity(repositoryClass: ParentRepository::class)]

Actually, in hindsight, the problem would have been quite obvious from the error message:

Unrecognized field: App\Entity\Parent::$parent

Anyway, now I'm wondering why the entire app has been working at all... ;-)

So I'm closing here - thanks everybody!

BTW: The apparently out-of-sync database is a known issue: doctrine/migrations#1406

@xabbuh xabbuh closed this as not planned Won't fix, can't repro, duplicate, stale Sep 27, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants