UniqueConstraint worked on annotation on Parent class, not anymore after using Attribute #7358

Open
opened 2026-01-22 15:50:36 +01:00 by admin · 0 comments
Owner

Originally created by @rudy286 on GitHub (Apr 23, 2024).

BC Break Report

Q A
BC Break yes
Version 2.19.4
php 8.1
DB PostgrSQL
Symfony 6.4

Summary

During the upgrade from php 7.4 to php 8.1 (same DB server, with same database_url, from sf 5.4 to 6.4), I have a break while converting Annotation to Attribute: does not work for ORM\UniqueConstraint.

Note: My application extend the user (utilisateur) class from my custom bundle (it is an important detail as we will see in the end):

<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use My\MyBundle\Entity\Utilisateur as BaseUtilisateur;

#[ORM\Table(name: '`utilisateur`')]
#[ORM\Entity(repositoryClass: \App\Repository\UtilisateurRepository::class)]
class Utilisateur extends BaseUtilisateur
{
}

Previous behavior

//...

/**
 * @ORM\MappedSuperclass
 * @ORM\HasLifecycleCallbacks
 * @ORM\Table(
 *  name="utilisateur",
 *  uniqueConstraints={
 *    @ORM\UniqueConstraint(
 *      columns={"identifiant"},
 *      options={"where": "est_actif = true"}
 *    )
 *  },
 * )
 */
class Utilisateur implements UserInterface, EquatableInterface
{
// ...

This code would produce this migration (php bin/console make:migration):

// ...
    public function up(Schema $schema): void
    {
        // this up() migration is auto-generated, please modify it to your needs
        // ...
        $this->addSql('CREATE UNIQUE INDEX UNIQ_1D1C63B3C90409EC ON utilisateur (identifiant) WHERE est_actif = true');
   // ...
}

Current behavior

With attributs, now the code produces this migration (php bin/console make:migration):

// ...
    public function up(Schema $schema): void
    {
        // this up() migration is auto-generated, please modify it to your needs
        $this->addSql('CREATE UNIQUE INDEX UNIQ_1D1C63B3C90409EC ON "utilisateur" (identifiant)');
   // ...
}

How to reproduce

In my class user I tried 2 way to write the UniqueConstraint:

  1. combined with Table:
//...

#[ORM\MappedSuperclass]
#[ORM\HasLifecycleCallbacks]
#[ORM\Table(name: 'utilisateur', uniqueConstraints: [
    new ORM\UniqueConstraint(
        columns: ['identifiant'],
        options: ["where" => "est_actif = true"],
        )
])]
class Utilisateur implements UserInterface, EquatableInterface
{
// ...
  1. separated from Table (default when using rector to automatically migrate anotations to attributes:
//...

#[ORM\MappedSuperclass]
#[ORM\HasLifecycleCallbacks]
#[ORM\Table(
    name: "utilisateur",
)]
#[ORM\UniqueEntity(
    columns: ["identifiant"],
    options: ["where" => "est_actif = true"],
)]
class Utilisateur implements UserInterface, EquatableInterface
{
// ...
  1. I even tryed to remove the prefix ORM to be more "compliant" with the documentation:
//...
use Doctrine\ORM\Mapping\Table;
use Doctrine\ORM\Mapping\UniqueConstraint; // I've checked, this file exists in folder vendor

#[ORM\MappedSuperclass]
#[ORM\HasLifecycleCallbacks]
#[Table(
    name: "utilisateur",
)]
#[UniqueEntity(
    columns: ["identifiant"],
    options: ["where" => "est_actif = true"],
)]
class Utilisateur implements UserInterface, EquatableInterface
{
// ...

According to the documentation, the parameter fieldis required, so I added it, still produce the not expected migration

#[Table(
    name: "utilisateur"
)]
#[UniqueConstraint(
    fields: ["identifiant"],
    columns: ["identifiant"],
    options: ["where" => "est_actif = true"]
)]

I explicitly told doctrine to use attibutes:


## config/packages/doctrine.yaml
### ...
    orm:
        auto_generate_proxy_classes: true
        naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
        auto_mapping: false
        mappings:
            App:
                is_bundle: false
                type: attribute
                dir: '%kernel.project_dir%/src/Entity'
                prefix: 'App\Entity'
                alias: App

Note: If I update the child class 'user' (utilisateur) it will work as the following code shows, BUT, I need my bundle to works for all apps using MyBundle:

<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use My\MyBundle\Entity\Utilisateur as BaseUtilisateur;

#[ORM\Table(name: '`utilisateur`')]
#[ORM\Entity(repositoryClass: \App\Repository\UtilisateurRepository::class)]
#[ORM\UniqueConstraint(
    name: 'uniq_identifiant_actif',    // here I added a name, or it will conflict with parent constraint, i just want to check what happend if i created a constraint here for the science
    columns: ['identifiant'],
    options: ['where' => 'est_actif = true']
)]
class Utilisateur extends BaseUtilisateur
{
}

And the migration created:

// ...
    public function up(Schema $schema): void
    {
        // this up() migration is auto-generated, please modify it to your needs
        $this->addSql('CREATE UNIQUE INDEX UNIQ_1D1C63B3C90409EC ON "utilisateur" (identifiant)'); // this comes from the  is the attribut of the parent user class: FAIL 
        $this->addSql('CREATE UNIQUE INDEX uniq_identifiant_actif ON "utilisateur" (identifiant) WHERE est_actif = true'); // this comes from the attribute of the child user class: SUCCESS

This suggest myBundle can't hold the description of the user entity of all my apps using the bundle.
It looks like all the apps using the same bundle must explicitly tells ORM how to migrate correctly the constraint (why would i create a bundle to extend the parent class then?)

Do you confirm there is a break/regression here, or it is something else?
Any hint welcome !

thanks

Originally created by @rudy286 on GitHub (Apr 23, 2024). <!-- Before reporting a BC break, please consult the upgrading document to make sure it's not an expected change: https://github.com/doctrine/orm/blob/2.9.x/UPGRADE.md --> ### BC Break Report <!-- Fill in the relevant information below to help triage your issue. --> | Q | A |------------ | ------ | BC Break | yes | Version | 2.19.4 | php | 8.1 | DB | PostgrSQL | Symfony | 6.4 #### Summary During the upgrade from php 7.4 to php 8.1 _(same DB server, with same database_url, from sf 5.4 to 6.4)_, I have a break while converting Annotation to **Attribute**: does not work for `ORM\UniqueConstraint`. **Note**: My application extend the user (utilisateur) class from my custom bundle _(it is an important detail as we will see in the end)_: ```php <?php namespace App\Entity; use Doctrine\ORM\Mapping as ORM; use My\MyBundle\Entity\Utilisateur as BaseUtilisateur; #[ORM\Table(name: '`utilisateur`')] #[ORM\Entity(repositoryClass: \App\Repository\UtilisateurRepository::class)] class Utilisateur extends BaseUtilisateur { } ``` #### Previous behavior ```php //... /** * @ORM\MappedSuperclass * @ORM\HasLifecycleCallbacks * @ORM\Table( * name="utilisateur", * uniqueConstraints={ * @ORM\UniqueConstraint( * columns={"identifiant"}, * options={"where": "est_actif = true"} * ) * }, * ) */ class Utilisateur implements UserInterface, EquatableInterface { // ... ``` This code would produce this migration (`php bin/console make:migration`): ```php // ... public function up(Schema $schema): void { // this up() migration is auto-generated, please modify it to your needs // ... $this->addSql('CREATE UNIQUE INDEX UNIQ_1D1C63B3C90409EC ON utilisateur (identifiant) WHERE est_actif = true'); // ... } ``` #### Current behavior With attributs, now the code produces this migration (`php bin/console make:migration`): ```php // ... public function up(Schema $schema): void { // this up() migration is auto-generated, please modify it to your needs $this->addSql('CREATE UNIQUE INDEX UNIQ_1D1C63B3C90409EC ON "utilisateur" (identifiant)'); // ... } ``` #### How to reproduce In my class user I tried 2 way to write the UniqueConstraint: 1. combined with Table: ```php //... #[ORM\MappedSuperclass] #[ORM\HasLifecycleCallbacks] #[ORM\Table(name: 'utilisateur', uniqueConstraints: [ new ORM\UniqueConstraint( columns: ['identifiant'], options: ["where" => "est_actif = true"], ) ])] class Utilisateur implements UserInterface, EquatableInterface { // ... ``` 2. separated from Table (default when using [rector](https://github.com/rectorphp/rector-symfony) to automatically migrate anotations to attributes: ```php //... #[ORM\MappedSuperclass] #[ORM\HasLifecycleCallbacks] #[ORM\Table( name: "utilisateur", )] #[ORM\UniqueEntity( columns: ["identifiant"], options: ["where" => "est_actif = true"], )] class Utilisateur implements UserInterface, EquatableInterface { // ... ``` 3. I even tryed to remove the prefix `ORM` to be more "compliant" with the documentation: ```php //... use Doctrine\ORM\Mapping\Table; use Doctrine\ORM\Mapping\UniqueConstraint; // I've checked, this file exists in folder vendor #[ORM\MappedSuperclass] #[ORM\HasLifecycleCallbacks] #[Table( name: "utilisateur", )] #[UniqueEntity( columns: ["identifiant"], options: ["where" => "est_actif = true"], )] class Utilisateur implements UserInterface, EquatableInterface { // ... ``` According to the [documentation](https://www.doctrine-project.org/projects/doctrine-orm/en/2.18/reference/attributes-reference.html#uniqueconstraint), the parameter `field`is required, so I added it, still produce the not expected migration ```php #[Table( name: "utilisateur" )] #[UniqueConstraint( fields: ["identifiant"], columns: ["identifiant"], options: ["where" => "est_actif = true"] )] ``` I explicitly told doctrine to use attibutes: ```yaml ## config/packages/doctrine.yaml ### ... orm: auto_generate_proxy_classes: true naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware auto_mapping: false mappings: App: is_bundle: false type: attribute dir: '%kernel.project_dir%/src/Entity' prefix: 'App\Entity' alias: App ``` Note: If I update the child class 'user' (utilisateur) it will work as the following code shows, BUT, I need my bundle to works for all apps using MyBundle: ```php <?php namespace App\Entity; use Doctrine\ORM\Mapping as ORM; use My\MyBundle\Entity\Utilisateur as BaseUtilisateur; #[ORM\Table(name: '`utilisateur`')] #[ORM\Entity(repositoryClass: \App\Repository\UtilisateurRepository::class)] #[ORM\UniqueConstraint( name: 'uniq_identifiant_actif', // here I added a name, or it will conflict with parent constraint, i just want to check what happend if i created a constraint here for the science columns: ['identifiant'], options: ['where' => 'est_actif = true'] )] class Utilisateur extends BaseUtilisateur { } ``` And the migration created: ```php // ... public function up(Schema $schema): void { // this up() migration is auto-generated, please modify it to your needs $this->addSql('CREATE UNIQUE INDEX UNIQ_1D1C63B3C90409EC ON "utilisateur" (identifiant)'); // this comes from the is the attribut of the parent user class: FAIL $this->addSql('CREATE UNIQUE INDEX uniq_identifiant_actif ON "utilisateur" (identifiant) WHERE est_actif = true'); // this comes from the attribute of the child user class: SUCCESS ``` This suggest myBundle can't hold the description of the user entity of all my apps using the bundle. It looks like all the apps using the same bundle must explicitly tells ORM how to migrate correctly the constraint (_why would i create a bundle to extend the parent class then?_) Do you confirm there is a break/regression here, or it is something else? Any hint welcome ! thanks
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#7358