Skip to content

Commit

Permalink
[Doctrine] Support for disabling ID generators if ID provided in fixt…
Browse files Browse the repository at this point in the history
…ure - 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.
  • Loading branch information
Dennis L authored and theofidry committed Feb 19, 2018
1 parent c36b8cc commit 7053c75
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 7 deletions.
27 changes: 27 additions & 0 deletions fixtures/Bridge/Doctrine/Entity/DummyWithIdentifier.php
@@ -0,0 +1,27 @@
<?php

/*
* This file is part of the Fidry\AliceDataFixtures package.
*
* (c) Théo FIDRY <theo.fidry@gmail.com>
*
* 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;
}
14 changes: 12 additions & 2 deletions src/Bridge/Doctrine/Persister/ObjectManagerPersister.php
Expand Up @@ -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;

Expand Down Expand Up @@ -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);
Expand Down
33 changes: 33 additions & 0 deletions src/Exception/ObjectGeneratorPersisterExceptionFactory.php
@@ -0,0 +1,33 @@
<?php

/*
* This file is part of the Fidry\AliceDataFixtures package.
*
* (c) Théo FIDRY <theo.fidry@gmail.com>
*
* 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)
));
}
}
50 changes: 45 additions & 5 deletions tests/Bridge/Doctrine/Persister/ObjectManagerPersisterTest.php
Expand Up @@ -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;
Expand Down Expand Up @@ -78,6 +81,8 @@ public function testIsNotClonable()
*/
public function testCanPersistAnEntity($entity, bool $exact = false)
{
$originalEntity = clone $entity;

$this->persister->persist($entity);
$this->persister->flush();

Expand All @@ -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
*/
Expand Down Expand Up @@ -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()
Expand Down

0 comments on commit 7053c75

Please sign in to comment.