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

Identity Map Collision with Class Table Inheritance When Using Predefined IDs #11861

Open
juslintek opened this issue Mar 7, 2025 · 0 comments

Comments

@juslintek
Copy link

juslintek commented Mar 7, 2025

Description

When using Class Table Inheritance (CTI) with predefined IDs (e.g., from an external system) for internal representation, retrieving an entity via the parent class and then the child class causes an identity map collision. Doctrine creates a new instance instead of reusing the managed one, resulting in duplicate instances with the same ID in the Unit of Work.

Steps to Reproduce

  1. Set up a CTI hierarchy with BaseEntity (parent) and ChildEntityA (child), using a predefined/external string ID.
  2. Persist a ChildEntityA instance with a predefined ID.
  3. Fetch it via $em->find(BaseEntity::class, $externalId) – returns ChildEntityA.
  4. Fetch it via $em->find(ChildEntityA::class, $externalId) – creates a new instance.
  5. Call $em->flush(), which triggers an identity collision.

Expected Behavior

The second find() should return the existing managed instance from the UOW, even with predefined/external IDs.

Actual Behavior

A new instance is created, causing an identity collision. This occurs specifically when using predefined IDs.

Environment

composer show -i | grep doctrine
beberlei/doctrineextensions                    1.5.0   A set of extensions to Doctrine 2 that add support for additional query functions available in MySQL, Oracle, PostgreSQL and SQLite.
dama/doctrine-test-bundle                      8.3.0   Symfony bundle to isolate doctrine database tests and improve test performance
doctrine/cache                                 2.2.0   PHP Doctrine Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.
doctrine/collections                           2.2.2   PHP Doctrine Collections library that adds additional functionality on top of PHP arrays.
doctrine/common                                3.5.0   PHP Doctrine Common project is a library that provides additional functionality that other Doctrine projects depend on such as better reflection support, p...
doctrine/data-fixtures                         2.0.2   Data Fixtures for all Doctrine Object Managers
doctrine/dbal                                  3.9.4   Powerful PHP database abstraction layer (DBAL) with many features for database schema introspection and management.
doctrine/deprecations                          1.1.4   A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.
doctrine/doctrine-bundle                       2.13.2  Symfony DoctrineBundle
doctrine/doctrine-fixtures-bundle              3.7.1   Symfony DoctrineFixturesBundle
doctrine/doctrine-migrations-bundle            3.4.1   Symfony DoctrineMigrationsBundle
doctrine/event-manager                         2.0.1   The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.
doctrine/inflector                             2.0.10  PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.
doctrine/instantiator                          2.0.0   A small, lightweight utility to instantiate objects in PHP without invoking their constructors
doctrine/lexer                                 3.0.1   PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.
doctrine/migrations                            3.8.2   PHP Doctrine Migrations project offer additional functionality on top of the database abstraction layer (DBAL) for versioning your database schema and easi...
doctrine/orm                                   3.3.2   Object-Relational-Mapper for PHP
doctrine/persistence                           3.4.0   The Doctrine Persistence project is a set of shared interfaces and functionality that the different Doctrine object mappers share.
doctrine/sql-formatter                         1.5.2   a PHP SQL highlighting library
dunglas/doctrine-json-odm                      1.4.2   An object document mapper for Doctrine ORM using JSON types of modern RDBMS.
gedmo/doctrine-extensions                      3.19.0  Doctrine behavioral extensions
phpstan/phpstan-doctrine                       1.5.7   Doctrine extensions for PHPStan
scienta/doctrine-json-functions                6.3.0   A set of extensions to Doctrine that add support for json query functions.
simpod/doctrine-utcdatetime                    0.3.0   Doctrine UTC DateTime
stof/doctrine-extensions-bundle                1.13.0  Integration of the gedmo/doctrine-extensions with Symfony
symfony/doctrine-bridge                        7.2.4   Provides integration for Doctrine with various Symfony components

Using latest stable symfony 7.2.4
Using Api Platform: 3.4.16
Percona MySQL Database: 8.0.36
PHP: 8.3.17 under alpine linux

Reproducible Example

<?php
use Doctrine\ORM\Mapping as ORM;

// Parent entity with predefined ID
#[ORM\Entity]
#[ORM\InheritanceType('JOINED')]
#[ORM\DiscriminatorColumn(name: 'type', type: 'string')]
#[ORM\DiscriminatorMap(['a' => ChildEntityA::class])]
class BaseEntity {
    #[ORM\Id]
    #[ORM\Column(type: 'string')] // Predefined/external ID
    protected $id;

    public function setId($id) { $this->id = $id; }
    public function getId() { return $this->id; }
}

// Child entity
#[ORM\Entity]
class ChildEntityA extends BaseEntity {
    #[ORM\Column(type: 'string)]
    private $fieldA;

    public function setFieldA($value) { $this->fieldA = $value; }
    public function getFieldA() { return $this->fieldA; }
}

// Reproduce the issue
$em = // Your EntityManager setup
$externalId = 'external-123'; // Predefined/external ID

// Create and persist entity
$entity = new ChildEntityA();
$entity->setId($externalId);
$entity->setFieldA('test');
$em->persist($entity);
$em->flush();

// Fetch via parent class
$baseEntity = $em->find(BaseEntity::class, $externalId); // Returns ChildEntityA

// Fetch via child class
$childEntity = $em->find(ChildEntityA::class, $externalId); // New instance created

// Check if they’re the same
var_dump($baseEntity === $childEntity); // Outputs: false

// Trigger collision
$em->flush(); // Throws identity collision error

Additional Notes

This seems related to how the UOW manages identity mapping for CTI with predefined/external IDs when querying child classes directly.

@juslintek juslintek changed the title Identity Map Collision with Class Table Inheritance When Using Predefined/External IDs for Internal Representation Identity Map Collision with Class Table Inheritance When Using Predefined IDs Mar 7, 2025
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

No branches or pull requests

1 participant