From 7053c75de4d5a91c5bd7db43918a907706c7c990 Mon Sep 17 00:00:00 2001 From: Dennis L Date: Mon, 19 Feb 2018 13:36:50 +0100 Subject: [PATCH] [Doctrine] Support for disabling ID generators if ID provided in fixture - part 2 (#89) When we find an entity with a provided ID, we disable the auto ID generator strategy for the whole class. Unfortunately, an entity can have a provided ID but the next one may not and as a result the persisting will fail because Doctrine expected an ID but none where found. This issue cannot be fixed because the auto ID generation strategy is a configuration done for the whole class and not on an entity basis. So if this happens, we now bail out with an comprehensive message instead of a cryptic one. --- .../Doctrine/Entity/DummyWithIdentifier.php | 27 ++++++++++ .../Persister/ObjectManagerPersister.php | 14 +++++- ...jectGeneratorPersisterExceptionFactory.php | 33 ++++++++++++ .../Persister/ObjectManagerPersisterTest.php | 50 +++++++++++++++++-- 4 files changed, 117 insertions(+), 7 deletions(-) create mode 100644 fixtures/Bridge/Doctrine/Entity/DummyWithIdentifier.php create mode 100644 src/Exception/ObjectGeneratorPersisterExceptionFactory.php diff --git a/fixtures/Bridge/Doctrine/Entity/DummyWithIdentifier.php b/fixtures/Bridge/Doctrine/Entity/DummyWithIdentifier.php new file mode 100644 index 00000000..c002b696 --- /dev/null +++ b/fixtures/Bridge/Doctrine/Entity/DummyWithIdentifier.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Fidry\AliceDataFixtures\Bridge\Doctrine\Entity; + +/** + * @Entity + */ +class DummyWithIdentifier +{ + /** + * @Id + * @Column(type="integer") + * @GeneratedValue + */ + public $id; +} diff --git a/src/Bridge/Doctrine/Persister/ObjectManagerPersister.php b/src/Bridge/Doctrine/Persister/ObjectManagerPersister.php index c83dfcaa..e745071c 100644 --- a/src/Bridge/Doctrine/Persister/ObjectManagerPersister.php +++ b/src/Bridge/Doctrine/Persister/ObjectManagerPersister.php @@ -18,6 +18,8 @@ use Doctrine\ODM\MongoDB\Mapping\ClassMetadataInfo as ODMClassMetadataInfo; use Doctrine\ORM\Id\AssignedGenerator as ORMAssignedGenerator; use Doctrine\ORM\Mapping\ClassMetadataInfo as ORMClassMetadataInfo; +use Doctrine\ORM\ORMException; +use Fidry\AliceDataFixtures\Exception\ObjectGeneratorPersisterExceptionFactory; use Fidry\AliceDataFixtures\Persistence\PersisterInterface; use Nelmio\Alice\IsAServiceTrait; @@ -79,9 +81,17 @@ public function persist($object) // Do nothing: not supported. } - $this->objectManager->persist($object); + try { + $this->objectManager->persist($object); + } catch (ORMException $exception) { + if ($metadata->idGenerator instanceof ORMAssignedGenerator) { + throw ObjectGeneratorPersisterExceptionFactory::createForEntityMissingAssignedIdForField($object); + } + + throw $exception; + } - if (null !== $generator) { + if (null !== $generator && false === $generator->isPostInsertGenerator()) { // Restore the generator if has been temporary unset $metadata->setIdGeneratorType($generatorType); $metadata->setIdGenerator($generator); diff --git a/src/Exception/ObjectGeneratorPersisterExceptionFactory.php b/src/Exception/ObjectGeneratorPersisterExceptionFactory.php new file mode 100644 index 00000000..ff8ad275 --- /dev/null +++ b/src/Exception/ObjectGeneratorPersisterExceptionFactory.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Fidry\AliceDataFixtures\Exception; + +use LogicException; + +/** + * @private + */ +final class ObjectGeneratorPersisterExceptionFactory +{ + + public static function createForEntityMissingAssignedIdForField($entity): LogicException + { + return new LogicException(sprintf('No ID found for the entity "%1$s". If this entity has an auto ID generator, ' . + 'this may be due to having it disabled because one instance of the entity had an ID assigned. ' . + 'Either remove this assigned ID to allow the auto ID generator to operate or generate and ID for ' . + 'all the "%1$s" entities.', + get_class($entity) + )); + } +} diff --git a/tests/Bridge/Doctrine/Persister/ObjectManagerPersisterTest.php b/tests/Bridge/Doctrine/Persister/ObjectManagerPersisterTest.php index 596a9380..5c0eef5c 100644 --- a/tests/Bridge/Doctrine/Persister/ObjectManagerPersisterTest.php +++ b/tests/Bridge/Doctrine/Persister/ObjectManagerPersisterTest.php @@ -15,12 +15,15 @@ use Doctrine\Common\DataFixtures\Purger\ORMPurger; use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\ORMException; use Doctrine\ORM\ORMInvalidArgumentException; use Fidry\AliceDataFixtures\Bridge\Doctrine\Entity\Dummy; use Fidry\AliceDataFixtures\Bridge\Doctrine\Entity\DummyEmbeddable; use Fidry\AliceDataFixtures\Bridge\Doctrine\Entity\DummySubClass; use Fidry\AliceDataFixtures\Bridge\Doctrine\Entity\DummyWithEmbeddable; +use Fidry\AliceDataFixtures\Bridge\Doctrine\Entity\DummyWithIdentifier; use Fidry\AliceDataFixtures\Bridge\Doctrine\Entity\MappedSuperclassDummy; +use Fidry\AliceDataFixtures\Exception\ObjectGeneratorPersisterException; use Fidry\AliceDataFixtures\Persistence\PersisterInterface; use PHPUnit\Framework\TestCase; use ReflectionClass; @@ -78,6 +81,8 @@ public function testIsNotClonable() */ public function testCanPersistAnEntity($entity, bool $exact = false) { + $originalEntity = clone $entity; + $this->persister->persist($entity); $this->persister->flush(); @@ -88,10 +93,45 @@ public function testCanPersistAnEntity($entity, bool $exact = false) $this->assertEquals(1, count($result)); if ($exact) { - $this->assertEquals($entity, $result[0]); + $this->assertEquals($originalEntity, $result[0]); } } + public function testCanPersistMultipleEntitiesWithExplicitIdentifierSet() + { + $dummy = new DummyWithIdentifier(); + $dummy->id = 100; + $this->persister->persist($dummy); + + $dummy = new DummyWithIdentifier(); + $dummy->id = 200; + $this->persister->persist($dummy); + + $this->persister->flush(); + + $entity = $this->entityManager->getRepository(DummyWithIdentifier::class)->find(200); + $this->assertInstanceOf(DummyWithIdentifier::class, $entity); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessageRegExp /^No ID found for the entity ".*". If this entity has an auto ID generator, this may be due to having it disabled because one instance of the entity had an ID assigned. Either remove this assigned ID to allow the auto ID generator to operate or generate and ID for all the ".*" entities.$/ + */ + public function testPersistingMultipleEntitiesWithAndWithoutExplicitIdentifierSetWillThrowORMException() + { + $dummy = new DummyWithIdentifier(); + $this->persister->persist($dummy); + + $dummy = new DummyWithIdentifier(); + $dummy->id = 100; + $this->persister->persist($dummy); + + $dummy = new DummyWithIdentifier(); + $this->persister->persist($dummy); + + $this->persister->flush(); + } + /** * @dataProvider provideNonPersistableEntities */ @@ -139,13 +179,13 @@ public function provideEntities() yield 'entity with explicit ID' => [ (function () { - $dummy = new Dummy(); - $dummy->id = 200; + $dummy = new DummyWithIdentifier(); + $dummy->id = 300; return $dummy; - })(), - true + })() ]; + } public function provideNonPersistableEntities()