h4cc/AliceFixturesBundle

Loading fixtures which rely on associationKey with generation fails

Closed this issue · 5 comments

Imagine the following 3 (simplified) entities:

Entity\Item:
    type: entity
    table: item
    fields:
        id:
            id: true
            type: integer
            unsigned: true
            nullable: false
            generator:
                strategy: IDENTITY
Entity\Product:
    type: entity
    table: product
    fields:
        id:
            id: true
            type: integer
            unsigned: true
            nullable: false
            generator:
                strategy: IDENTITY
Entity\ItemProduct:
    type: entity
    table: item_product
    id:
      item:
        associationKey: true
      product:
        associationKey: true
    fields:
        updatedAt:
            type: datetime
            nullable: false
    manyToOne:
        item:
            targetEntity: Entity\Item
    oneToOne:
        product:
            targetEntity: Entity\Product

and the following fixture file:

Entity\Item:
  item{1..10}:
    itemName: <sentence()>
    updatedAt: <dateTimeBetween('-1 year', 'now')>

Entity\Product:
  product{1..20}:
    productSku: <bothify('?????????##')>
    updatedAt: <dateTimeBetween('-1 year', 'now')>

Entity\ItemProduct:
  itemProduct{1..40}:
    item: @item<numberBetween(1, 10)>
    product: @product<numberBetween(1, 20)>
    updatedAt: <dateTimeBetween('-1 year', 'now')>

When trying to load this It will always fail with an error such as:

[Doctrine\ORM\ORMException]
  Entity of type Entity\ItemProduct has identity through a foreign entity Entity\Item,
   however this entity has no identity itself. You have to call EntityManager#persist() on the related entity and make sure that an identifier was generated before
  trying to persist 'Entity\ItemProduct'. In case of Post Insert ID Generation (such as MySQL Auto-Increment or PostgreSQL SER
  IAL) this means you have to call EntityManager#flush() between both persist operations.

Since the id for Item and Product is generated by the database if you try to persist the entity with a primary key formed by association keys it will fail since it's not able to find an id to use when persisting.

I kind of "hacked" this behaviour by catching this exception an issuing a flush then but this is far from being the right solution.

I also tried separating the fixture into files doing first Product and Item and then ItemProduct but this way you can't keep the fanciness of using things such as @item<numberBetween(1, 10)> when creating ItemProduct entities.

Another way will be to add a flag to force a flush between files, not the perfect one but it will alleviate the issue. Maybe I'm missing something else here but I'm not able to find a way to do this in a proper way 😞

h4cc commented

@garciall Thanks for your contribution and the superb explanation.

The problem occured because of the ORM detach(), which will lead to unknown references in Doctrine ArrayCollections. This call be removed in the next release.

A workaround could be using cascade={persist}: http://doctrine-orm.readthedocs.org/en/latest/reference/working-with-associations.html#transitive-persistence-cascade-operations

Hi @h4cc,
I seem to be experiencing the same issue with version: 0.5.1. cascade persist doesn't help. Is there other way around this issue?

h4cc commented

@fliespl Do you have a minimal failing example?

@h4cc I have almost the same relation as garciall (different table names).

I have resolved it by loading main tables first (game , publisher) & then n:m relations (gamepublisher) in different set.

\ZapTvBundle\Entity\Game:
  content_game{1..25}:
    originalTitle: <word()>
    description: <paragraph(8)>

\ZapTvBundle\Entity\Publisher:
  publisher{1..10}:
    name: <word()>
    type: <randomElement(array(1, 2))>

\ZapTvBundle\Entity\GamePublisher:
  game_publishers_world{1..25}:
    game: @content_game*
    publisher: @publisher*
h4cc commented

I have resolved it by loading main tables first (game , publisher) & then n:m relations (gamepublisher) in different set.

That should be the recommended way.
Its not a problem with loading/creating the fixtures, its about persisting in doctrine in correct order, which can not be known to alice.