The ability to rename entity properties without a breaking change #7546

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

Originally created by @comma8600 on GitHub (Aug 26, 2025).

Feature Request

I've ask around how it would be possible to rename properties here:
https://stackoverflow.com/questions/79695855/how-to-refactor-doctrine-property-names-while-retaining-aliasing-the-old-names

I've made some progress through the answers suggetion of using a custom repository however the client's of this library also use query builders which I can see no easy way of adding this aliasing behaviour to.

I'm in the unfortunate position of working with a library with a large number of entities that are used across dozens of projects, in these library there are a mix of autogenerated entities and manually added entities over many years that have a mix of property naming conventions, as such it is not possible for anyone in the team to know if it is ->findBy(['userId' => 1]) or ->findBy(['user_id' => 1]) without having to look into the entity definition itself. This counts for both repository methods but also DQL and query builder methods.

This creates a weird situation where despite the property being a private property within the entity, it cannot be renamed without introducing a breaking change. Ideally I'd like the option of starting to use a PSR naming convention for properties but without forcing client's of the library to update all at once.

Note this isn't related to naming strategies, or using #[Column(name='user_id')] (already being used, as the sql and entity have different naming conventions), as I'm not interesting in changing the db's naming schema, that can stay as it is only how our code is able to reference a doctrine column property via naming of the php class's property name.

What

I'm proposing that there should be a new Attribute or a new property on a existing one that allows us to rename an entity's property without a breaking change:

from:

#[Entity]
class Article
{
      #[Column]
      private int $user_id;
}

to:

#[Entity]
class Article
{
      #[Column]
      #[RenamedFrom('user_id', trigger_deprecation_warning: true)]
      private int $userId;
}

doctrine would error if you tried to define a seperate property with the old name $user_id, so it is clear that any reference to 'user_id' in FindBy or in query builders would be understood as an alias for 'userId'.

There should also be an option to trigger a deprecation warning for using the old property name.

I've a second related issue in that large number of foreign keys were added as doctrine properties and not as relationships. Against doctrine best practice: https://www.doctrine-project.org/projects/doctrine-orm/en/3.5/reference/best-practices.html#don-t-map-foreign-keys-to-fields-in-an-entity

This is messy as using auto increment id means the order that our entities are persisted and flushed is important and has to be managed by our code not by doctrine as we cannot set a foreign key until we have an identity of the related entity. Adding a relationship on the same column as a property is not ideal as both need to be set to a consistent value or doctrine doesn't know how to correctly handle it. I've found a partial approach that works using doctrine reference to replace foreign key setter with a relationship but like the renaming issue I don't yet have a solution that works for query builder:
https://alexdawn.github.io/php/2025/08/01/how-to-deprecate-doctrine-properties-that-should-be-a-relationship.html

then for custom repostitories I've been able to add an attribute #[DeprecatedReplacedWithRelationship('part')] that is able to rename the property and and replace the foreign key value with a doctrine reference proxy

Why

Making minor changes and alerting users to the issue via deprecation is an easier path than creating a breaking change and requiring large refactors without notice. private class properties should not be something this difficult to refactor.

Allows bad practice to move towards good practice: https://www.doctrine-project.org/projects/doctrine-orm/en/3.5/reference/best-practices.html#don-t-map-foreign-keys-to-fields-in-an-entity

How

I've a solution that works for custom repository using reflection on the attributes to get a map of old and new property names, the trait wraps the findBy and findOneBy methods and rewrites the arrays arguments for $criteria and $order, however I do not have a way of injecting additional behaviour into DQL or the query builder to alias the property names.

Even if this is something that is not officially wanted I'd be grateful if you'd consider adding some means to extend query builder so I could build my own solution much like I've done for custom repositories.


    /**
     * @return array<string,string> maps old name to new name
     */
    private function getRenames(): array
    {
        // avoid repeat reflection introspection, run once and store results
        if (!isset($this->renames)) {
            // parent repository method make this private so use reflection to get the entity class this class
            // is the repo for
            $entityClass = (new ReflectionProperty(LazyServiceEntityRepository::class, 'entityClass'))->getValue($this);
            $reflectionClass = new ReflectionClass($entityClass);
            $this->renames = [];
            foreach ($reflectionClass->getProperties() as $property) {
                $attribute = $property->getAttributes(RenamedFrom::class)[0] ?? null;
                if ($attribute !== null) {
                    $rename = $attribute->newInstance();
                    $this->renames[$rename->oldName] = $property->getName();
                }
            }
        }
        return $this->renames;
    }

    /**
     * @return array<string,array{class-string, string}> maps old name to new name
     */
    private function getReplacedWithRelationship(): array
    {
        // avoid repeat reflection introspection, run once and store results
        if (!isset($this->replacedByRelationship)) {
            $entityClass = (new ReflectionProperty(LazyServiceEntityRepository::class, 'entityClass'))->getValue($this);
            $reflectionClass = new ReflectionClass($entityClass);
            $this->replacedByRelationship = [];
            foreach ($reflectionClass->getProperties() as $property) {
                $attribute = $property->getAttributes(DeprecatedReplacedWithRelationship::class)[0] ?? null;
                if ($attribute !== null) {
                    $replace = $attribute->newInstance();
                    $replacementRelationship = new ReflectionProperty($entityClass, $replace->relationshipPropertyName);
                    // use current property name if it hasn't been renamed
                    $oldName = $replace->oldPropertyName ?? $property->getName();
                    $relatedEntityName = $replacementRelationship->getType()->getName();
                    $this->replacedByRelationship[$oldName] = [$relatedEntityName, $replacementRelationship->getName()];
                }
            }
        }
        return $this->replacedByRelationship;
    }
Originally created by @comma8600 on GitHub (Aug 26, 2025). ### Feature Request I've ask around how it would be possible to rename properties here: https://stackoverflow.com/questions/79695855/how-to-refactor-doctrine-property-names-while-retaining-aliasing-the-old-names I've made some progress through the answers suggetion of using a custom repository however the client's of this library also use query builders which I can see no easy way of adding this aliasing behaviour to. I'm in the unfortunate position of working with a library with a large number of entities that are used across dozens of projects, in these library there are a mix of autogenerated entities and manually added entities over many years that have a mix of property naming conventions, as such it is not possible for anyone in the team to know if it is `->findBy(['userId' => 1])` or `->findBy(['user_id' => 1])` without having to look into the entity definition itself. This counts for both repository methods but also DQL and query builder methods. This creates a weird situation where despite the property being a private property within the entity, it cannot be renamed without introducing a breaking change. Ideally I'd like the option of starting to use a PSR naming convention for properties but without forcing client's of the library to update all at once. Note this isn't related to naming strategies, or using #[Column(name='user_id')] (already being used, as the sql and entity have different naming conventions), as I'm not interesting in changing the db's naming schema, that can stay as it is only how our code is able to reference a doctrine column property via naming of the php class's property name. #### What I'm proposing that there should be a new Attribute or a new property on a existing one that allows us to rename an entity's property without a breaking change: from: ```php #[Entity] class Article { #[Column] private int $user_id; } ``` to: ```php #[Entity] class Article { #[Column] #[RenamedFrom('user_id', trigger_deprecation_warning: true)] private int $userId; } ``` doctrine would error if you tried to define a seperate property with the old name `$user_id`, so it is clear that any reference to 'user_id' in FindBy or in query builders would be understood as an alias for 'userId'. There should also be an option to trigger a deprecation warning for using the old property name. I've a second related issue in that large number of foreign keys were added as doctrine properties and not as relationships. Against doctrine best practice: https://www.doctrine-project.org/projects/doctrine-orm/en/3.5/reference/best-practices.html#don-t-map-foreign-keys-to-fields-in-an-entity This is messy as using auto increment id means the order that our entities are persisted and flushed is important and has to be managed by our code not by doctrine as we cannot set a foreign key until we have an identity of the related entity. Adding a relationship on the same column as a property is not ideal as both need to be set to a consistent value or doctrine doesn't know how to correctly handle it. I've found a partial approach that works using doctrine reference to replace foreign key setter with a relationship but like the renaming issue I don't yet have a solution that works for query builder: https://alexdawn.github.io/php/2025/08/01/how-to-deprecate-doctrine-properties-that-should-be-a-relationship.html then for custom repostitories I've been able to add an attribute `#[DeprecatedReplacedWithRelationship('part')]` that is able to rename the property and and replace the foreign key value with a doctrine reference proxy #### Why Making minor changes and alerting users to the issue via deprecation is an easier path than creating a breaking change and requiring large refactors without notice. private class properties should not be something this difficult to refactor. Allows bad practice to move towards good practice: https://www.doctrine-project.org/projects/doctrine-orm/en/3.5/reference/best-practices.html#don-t-map-foreign-keys-to-fields-in-an-entity #### How I've a solution that works for custom repository using reflection on the attributes to get a map of old and new property names, the trait wraps the findBy and findOneBy methods and rewrites the arrays arguments for $criteria and $order, however I do not have a way of injecting additional behaviour into DQL or the query builder to alias the property names. Even if this is something that is not officially wanted I'd be grateful if you'd consider adding some means to extend query builder so I could build my own solution much like I've done for custom repositories. ``` /** * @return array<string,string> maps old name to new name */ private function getRenames(): array { // avoid repeat reflection introspection, run once and store results if (!isset($this->renames)) { // parent repository method make this private so use reflection to get the entity class this class // is the repo for $entityClass = (new ReflectionProperty(LazyServiceEntityRepository::class, 'entityClass'))->getValue($this); $reflectionClass = new ReflectionClass($entityClass); $this->renames = []; foreach ($reflectionClass->getProperties() as $property) { $attribute = $property->getAttributes(RenamedFrom::class)[0] ?? null; if ($attribute !== null) { $rename = $attribute->newInstance(); $this->renames[$rename->oldName] = $property->getName(); } } } return $this->renames; } /** * @return array<string,array{class-string, string}> maps old name to new name */ private function getReplacedWithRelationship(): array { // avoid repeat reflection introspection, run once and store results if (!isset($this->replacedByRelationship)) { $entityClass = (new ReflectionProperty(LazyServiceEntityRepository::class, 'entityClass'))->getValue($this); $reflectionClass = new ReflectionClass($entityClass); $this->replacedByRelationship = []; foreach ($reflectionClass->getProperties() as $property) { $attribute = $property->getAttributes(DeprecatedReplacedWithRelationship::class)[0] ?? null; if ($attribute !== null) { $replace = $attribute->newInstance(); $replacementRelationship = new ReflectionProperty($entityClass, $replace->relationshipPropertyName); // use current property name if it hasn't been renamed $oldName = $replace->oldPropertyName ?? $property->getName(); $relatedEntityName = $replacementRelationship->getType()->getName(); $this->replacedByRelationship[$oldName] = [$relatedEntityName, $replacementRelationship->getName()]; } } } return $this->replacedByRelationship; } ```
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#7546