From 8227c458a463d0caa7d3db53218970e2e08c6320 Mon Sep 17 00:00:00 2001 From: Zan Baldwin Date: Tue, 2 Jun 2026 13:50:40 +0200 Subject: [PATCH] =?UTF-8?q?bugfix(subject):=20=F0=9F=90=9B=20resolve=20unf?= =?UTF-8?q?lushed=20generated=20ID=20to=20null?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SubjectHelper::getObjectIdentifier() returned Doctrine's raw identifier array for an entity whose id is database-generated but not yet flushed. getIdentifierValues() yields `[id => null]` (or an empty array) so ID was persisted as the literal string `[]`. --- src/Util/SubjectHelper.php | 14 ++++++++--- tests/Fixtures/Doctrine/GeneratedIdEntity.php | 25 +++++++++++++++++++ .../Util/SubjectHelperDoctrineTest.php | 14 ++++++++++- 3 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 tests/Fixtures/Doctrine/GeneratedIdEntity.php diff --git a/src/Util/SubjectHelper.php b/src/Util/SubjectHelper.php index bd36c6c..109b9d2 100644 --- a/src/Util/SubjectHelper.php +++ b/src/Util/SubjectHelper.php @@ -91,9 +91,17 @@ public static function getDoctrineProperties(object $subject): mixed { $class = get_class($subject); try { - $ids = self::$registry?->getManagerForClass($class)?->getClassMetadata($class)?->getIdentifierValues($subject); - if (is_array($ids) && 1 === count($ids)) { - return $ids[array_key_first($ids)]; + $ids = self::$registry + ?->getManagerForClass($class) + ?->getClassMetadata($class) + ?->getIdentifierValues($subject); + if (is_array($ids)) { + if ([] === array_filter($ids, static fn (mixed $value): bool => null !== $value)) { + return null; + } + if (1 === count($ids)) { + return $ids[array_key_first($ids)]; + } } return $ids; } catch (\Throwable) { diff --git a/tests/Fixtures/Doctrine/GeneratedIdEntity.php b/tests/Fixtures/Doctrine/GeneratedIdEntity.php new file mode 100644 index 0000000..dfe7e38 --- /dev/null +++ b/tests/Fixtures/Doctrine/GeneratedIdEntity.php @@ -0,0 +1,25 @@ +id; + } +} diff --git a/tests/Integration/Util/SubjectHelperDoctrineTest.php b/tests/Integration/Util/SubjectHelperDoctrineTest.php index 0d2be91..993edb9 100644 --- a/tests/Integration/Util/SubjectHelperDoctrineTest.php +++ b/tests/Integration/Util/SubjectHelperDoctrineTest.php @@ -2,7 +2,9 @@ namespace WeDevelop\Audit\Tests\Integration\Util; +use WeDevelop\Audit\Entity\Subject; use WeDevelop\Audit\Tests\Fixtures\Doctrine\CompositeIdEntity; +use WeDevelop\Audit\Tests\Fixtures\Doctrine\GeneratedIdEntity; use WeDevelop\Audit\Tests\Fixtures\Doctrine\IntegrationEntity; use WeDevelop\Audit\Tests\Support\DoctrineTestCase; use WeDevelop\Audit\Util\SubjectHelper; @@ -12,7 +14,7 @@ final class SubjectHelperDoctrineTest extends DoctrineTestCase protected function setUp(): void { parent::setUp(); - $this->createSchema([IntegrationEntity::class, CompositeIdEntity::class]); + $this->createSchema([IntegrationEntity::class, CompositeIdEntity::class, GeneratedIdEntity::class]); SubjectHelper::setManagerRegistry($this->registryReturningEntityManager()); } @@ -34,6 +36,16 @@ public function testReadsCompositeIdentifierFromDoctrineMetadata(): void self::assertSame(['first' => 1, 'second' => 'abc'], SubjectHelper::getObjectIdentifier($entity)); } + public function testUnflushedGeneratedIdentifierResolvesToNull(): void + { + $entity = new GeneratedIdEntity(); + $this->em->persist($entity); + // Intentionally not flushed: the generated id is not yet assigned, so + // getIdentifierValues() returns an empty array. + self::assertNull(SubjectHelper::getObjectIdentifier($entity)); + self::assertNull(Subject::fromObject($entity)->identifier); + } + public function testResolvesConcreteClassFromADoctrineProxy(): void { $this->em->persist(new IntegrationEntity(5));