10

I am using Symfony2 with Doctrine and I'm using the Alice Fixture bundle to generate my fixtures for testing.

In one case I need to create a fixture where the id is 148 for a specific test. All of our ids are configured to auto_increment, so I am currently creating 147 dummy records just to create the one I need.

I was hoping to use this definition to force the id to be set to 148:

 invoiceClassNoClass (extends invoiceClass):
    id: 148
    sortOrder: 1
    label: 'No Class'

Unfortunately this does not work. From a google search I read a brief comment stating that I first need to add a setId method to my entity. I tried that but it did not make a difference. In reality I do not want to add as setId method if I do not need to, as it violates our integrity rules where we never allow the setting of an id.

Perhaps there is reflection class that could be used? Perhaps this is built into Alice and I do not know about it?

kc_rob
  • 147
  • 1
  • 9
  • *I first need to add a setId method to my entity. I tried that but it did not make a difference.* Please explain it more precisely, did you get an error? Adding `setId` is probably the best and simple solution to your problem. – A.L Apr 29 '16 at 16:15
  • The ID generator type will override a what you set with setId, so just setting it on the object won't do the trick. – David Apr 29 '16 at 19:48
  • @David Are you sure? IIRC, auto incrementing is only used when the primary id is null. – A.L Apr 30 '16 at 00:07
  • @A.L - You may be right but from other reading, I got the impression it's not that simple. – David Apr 30 '16 at 00:56
  • 1
    @A.L I did not get an error, it just simply did not set the id. As David stated, the ID generator has priority. – kc_rob May 04 '16 at 13:08

3 Answers3

10

If you want doctrine to save a certain value for an auto increment Id, then you have to deactivate auto increment in doctrine metadata. See

Explicitly set Id with Doctrine when using "AUTO" strategy

$this->em->persist($entity);

$metadata = $this->em->getClassMetaData(get_class($entity));
$metadata->setIdGenerator(new \Doctrine\ORM\Id\AssignedGenerator());
Community
  • 1
  • 1
Carsten Ulrich
  • 179
  • 1
  • 4
  • In other words, can we say that this code is necessary to be able to call a `setId()` function? – A.L May 04 '16 at 15:14
  • 1
    No, you can call setId also without that code, but doctrine will just ignore your id value and will still use the auto increment value. – Carsten Ulrich May 04 '16 at 19:43
  • Thanks, that's what I was thinking, my sentence was badly formulated. – A.L May 04 '16 at 20:14
  • 1
    May I add *You can then call `public function setId(){$this->id = $id;}` and the value of the `id` won't be overwritten.* at the end of your answer? I think it's better to tell this explicitly. – A.L May 04 '16 at 20:21
  • Doesn't really answer the question which is about Alice Fixtures. Your answer is too generic. – Ryall Jul 31 '17 at 22:29
9

UPDATE: After further research on the subject, I realised that hardcoding ids in fixtures comes with its own problems and is generally not a good idea. Instead, it's better to get fixtures by their reference in the yml file when you need to check for a specific record.


Since the question is about the Alice fixtures bundle, I found the following to work for me:

protected $idGeneratorTypes = [];

protected function allowFixedIdsFor(array $entityClasses)
{
    $em = $this->getContainer()->get('doctrine')->getManager();
    foreach ($entityClasses as $entityClass) {
        $metadata = $em->getClassMetadata($entityClass);
        $this->idGeneratorTypes[$entityClass] = $metadata->generatorType;
        $metadata->setIdGeneratorType(\Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_NONE);
    }
}

protected function recoverIdGenerators()
{
    $em = $this->getContainer()->get('doctrine')->getManager();
    foreach ($this->idGeneratorTypes as $entityClass => $idGeneratorType) {
        $metadata = $em->getClassMetadata($entityClass);
        $metadata->setIdGeneratorType($idGeneratorType);
    }
}

Then in the test's setUp() method, I have

    $this->allowFixedIdsFor([
        MyEntity::class,
    ]);


    $this->loadFixtureFiles([
        './tests/DataFixtures/ORM/my_entities.yml',
    ]);

    $this->recoverIdGenerators();

It looks like you don't event need to have a setId() method in your entity.

pmishev
  • 896
  • 9
  • 22
6

I also ran into this issue since I wanted some ids to be the same every run since I want to refer them in my debug apps using the UUID strategy.

What I have done:

1) Wrote a command that wraps hautelook:fixtures:load:

protected function execute(InputInterface $input, OutputInterface $output)
{
    $container     = $this->getContainer();
    $entityManager = $container->get('doctrine.orm.default_entity_manager');

    $metadata = $entityManager->getClassMetaData(App::class);
    $metadata->setIdGeneratorType(\Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_NONE);
    $metadata->setIdGenerator(new \Doctrine\ORM\Id\AssignedGenerator());

    $application = new Application($kernel);
    $application->setAutoExit(false);

    $this->loadFixtures($application, $output);
}

Do the $metadata-stuff for all classes you want to have custom ids (App::class).

protected function loadFixtures(Application $application, OutputInterface $output)
{
    $loadFixturesInput = new ArrayInput(array(
        'command' => 'hautelook:fixtures:load',
        '--no-interaction' => 'true',
        '--verbose' => '3'
    ));

    $application->run($loadFixturesInput, $output);
}

2) I created a template to generate the id like doctrine would.

id (template):
    id (unique):   '<uuid()>'

3) In my custom entity fixture, now I can do the following:

app_custom (extends id):
    id:    'whatever-i-want'
    title: 'My custom entity'

4) And if I don't want to use a custom id, I just don't overwrite the id field:

app_another (extends id):
    title: 'Just another app with an id I do not care about'

Proof:

enter image description here

Thomas Kekeisen
  • 4,355
  • 4
  • 35
  • 54