zenstruck / foundry

A model factory library for creating expressive, auto-completable, on-demand dev/test fixtures with Symfony and Doctrine.
https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html
MIT License
669 stars 75 forks source link

Custom persister logic? #695

Open mpdude opened 1 month ago

mpdude commented 1 month ago

I'd like to use Foundry for a legacy code base where objects are not persisted through Doctrine ORM, but other means.

Currently, my factories for such legacy model objects call $this->withoutPersisting() in their initialize() method. All clients using these factories call create() or similar and then have to take care of persisting the objects (when necessary) themselves.

Is there a standard way or hook that I could use to centralize custom persistence code in the factories?

nikophil commented 1 month ago

Hi

Basically, what I'd do would be something like this:

abstract class LegacyObjectFactory extends ObjectFactory
{
    public function __construct(
        private LegacyPersister $legacyPersister
    ){}

    public function initialize(): static
    {
        return $this->afterInstatiate(
            function(object $data): void {$this->legacyPersister->save($data)}
        );
    }
}

~Note that you won't be able to use these factories in a data provider, because they will need Symfony to be booted (using a constructor in your factory makes it a "service factory")~ not true anymore since https://github.com/zenstruck/foundry/releases/tag/v2.2.0

in the future, we may offer an extension point dedicated for this, but there's still work to do before that

sirpilan commented 2 weeks ago

Got a similar problem which would be resolved by this.

I am using a prePersist EntityListener on my Entity. In my case i automatically add the current user as owner.

When creating fixtues now:

MyEntity::createOne(['owner' => SOME_OWNER]) MyEntity::createOne(['owner' => ANOTHER_OWNER])

owner will allways be overwritten in my prePersist - so i cant create fixtues i want to have.

A Custom persister-logic would help here too. Or is there another way to avoid this behavior?

nikophil commented 2 weeks ago

Hi @sirpilan

maybe you can change your listener and not override when a value already exists? :sweat_smile:

I'm not sure what would be the benefits of a custom persister in your case? Maybe the best option would be to be able to disable doctrine's listeners from the factories :thinking: A previous attempt exists, but the PR was never finished (and, by the way, it should now target branch 2.x, so I'm not sure something from this PR can be reused)

sirpilan commented 2 weeks ago

maybe you can change your listener and not override when a value already exists? 😅

Well, I need the user at the entity i create explicitly here: MyEntity::createOne(['owner' => SOME_OWNER]) The prePersist listener does the overwriting i dont want. When i send values, i'd have to unset a posted value owner, so it is not set, so the prePersist listener will fill it. I'll try that, thx for the suggestion :)

I'm not sure what would be the benefits of a custom persister in your case? Maybe the best option would be to be able to

A custom persister would give me a tool to bypass the persister without changing my production code. The potential soultion above is technically code I only need so the fixtures for my tests can work, which I'd like to rather not do :D

Simply: I want the fixtures to be in the database exactly as I defined them in my tests - regardless of any doctrine listeners etc.

EDIT: Ok i can manually disable my listener for my tests in setUp:

  $container = self::$container;
  $eventManager = $container->get('doctrine.orm.entity_manager')->getEventManager();
  $listener = $container->get(MyPrePersistListener::class);
  $eventManager->removeEventListener(['prePersist'], $listener);

This solution is "ok" in my book :D