Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow multiple entity manager #113

Open
wants to merge 14 commits into
base: master
Choose a base branch
from

Conversation

jdeniau
Copy link

@jdeniau jdeniau commented Dec 27, 2018

See explanations in code

@@ -78,7 +78,7 @@
public="true" />

<service id="fidry_alice_data_fixtures.persistence.persister.doctrine.object_manager_persister"
class="Fidry\AliceDataFixtures\Bridge\Doctrine\Persister\ObjectManagerPersister"
class="Fidry\AliceDataFixtures\Bridge\Doctrine\Persister\ManagerRegistryPersister"
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ObjectManagerPersister take an EntityManager as parameter, but we give a Registy

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's curious that this works...

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand my comment, but it seems that ObjectManagerPersister uses à Doctrine Object manager (the default one I think) when the ManagerRegistryPersister uses doctrine registry, and then is able to select wich ObjectManagerPersister to select.

They both implements the same PersisterInterface

I think that the ObjectManagerPersister should work when using only one entity manager

@@ -42,12 +42,12 @@ public function __construct(ManagerRegistry $registry)
$managers = $registry->getManagers();

foreach ($managers as $manager) {
$this->persisters[get_class($manager)] = new ObjectManagerPersister($manager);
$this->persisters[spl_object_hash($manager)] = new ObjectManagerPersister($manager);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This allows having multiple instance of Doctrine\ORM\EntityManager

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@@ -80,6 +80,6 @@ private function getPersisterForClass(string $class): PersisterInterface
);
}

return $this->persisters[get_class($manager)];
return $this->persisters[spl_object_hash($manager)];
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(this matches previous comment)

src/Bridge/Doctrine/Persister/ObjectManagerPersister.php Outdated Show resolved Hide resolved
Copy link
Owner

@theofidry theofidry left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR @jdeniau!

I'll have a look after new year if you don't mind. It's indeed a tricky problem and there's a couple of open issues and PRs related to this. So I think I'll have to review a bit everything and also assess if there is any deeper issue or work to do as well.

@jdeniau
Copy link
Author

jdeniau commented Dec 28, 2018

I'll have a look after new year if you don't mind.

Of course no problem ! 🎉

@theofidry
Copy link
Owner

@jdeniau sorry this PR went a bit unnoticed. Would it be possible to update it with the latest changes?

@jdeniau jdeniau changed the base branch from feature/multiple-entity-managers to master July 31, 2020 09:26
@jdeniau
Copy link
Author

jdeniau commented Jul 31, 2020

@theofidry My branch was originally based upon your feature/multiple-entity-managers branch.

I rebased this PR on master, we now have our mixed work on multiple entity manager mixed.

The review might be harder to do (or you will review your work too), but basically, I just merged master and resolved some conflicts 😉

I had a conflict on the Purger class, I picked the changes made here

@jdeniau jdeniau changed the title Fix some issues with multiple entity manager Allow multiple entity manager Aug 17, 2020
@jdeniau
Copy link
Author

jdeniau commented Aug 17, 2020

This PR does now superseed #99

@theofidry
Copy link
Owner

theofidry commented Aug 18, 2020

👌 Looks like the CI is failing.

I suppose this should be part of a major release? I'm afraid it is introducing some BC breaks...

@jdeniau jdeniau force-pushed the feature/multiple-entity-managers-fixes branch 3 times, most recently from 5300300 to 3ccaebc Compare August 31, 2020 08:19
@jdeniau jdeniau force-pushed the feature/multiple-entity-managers-fixes branch from 3ccaebc to c1fd2e1 Compare August 31, 2020 08:28
@jdeniau
Copy link
Author

jdeniau commented Aug 31, 2020

👌 Looks like the CI is failing.

Hi @theofidry , I fixed the CI, the bugs came from the Purger class that now extends ObjectManagerPurger and thus the private properties were not enabled anymore.

The only failing test is the same failing test as master.

I suppose this should be part of a major release? I'm afraid it is introducing some BC breaks...
Theoretically, there should be no BC break, as the XML files are updated.

But I am not 100% either, and an internal refactoring might be a valid argument to create a major release, so it looks good to me.

@jdeniau
Copy link
Author

jdeniau commented Aug 31, 2020

For the record if you push a major release, you might want to drop the deprecated classes (Purger for example).
There might be some cleaning to do after thi PR is merged in that case

@jdeniau jdeniau force-pushed the feature/multiple-entity-managers-fixes branch from 5ab90de to 98c0c61 Compare April 19, 2021 21:10
@jdeniau jdeniau force-pushed the feature/multiple-entity-managers-fixes branch from 9937a0e to 98c0c61 Compare April 19, 2021 21:18
@jdeniau
Copy link
Author

jdeniau commented Apr 19, 2021

@theofidry I took time to re-work on that PR again.
The CI is failing but for the same reason as #168 . Probably because the lock file is not set and some dependency did change since last commit on master ? 🤔

I will try to test intensively in the next days with doctrine/orm, and I come back to you if everything is OK.

@COil
Copy link

COil commented Sep 22, 2021

@theofidry I took time to re-work on that PR again.
The CI is failing but for the same reason as #168 . Probably because the lock file is not set and some dependency did change since last commit on master ? 🤔
I will try to test intensively in the next days with doctrine/orm, and I come back to you if everything is OK.

Hi jdeniau, did you have time to make progress on this PR? I am discovering this right now, and I'll be interested in this feature too. See you.

@jdeniau
Copy link
Author

jdeniau commented Sep 27, 2021

Hi @COil ,

I ran into a lot of new issues with this PR, probably due to #155 and the entity id management.

As I was upgrading the whole package of behat / alice and the all ecosystem (Symfony2Extension to SymfonyExtension for the main upgrade), I did not manage to make this work, and won't take anymore time on it.

The solution I came by is the following though:

<?php

declare(strict_types=1);

namespace Mapado\TicketingBundle\Tests\Context;

use App\Entity\SomeEntity;
use Behat\Behat\Context\Context;
use Behat\Gherkin\Node\TableNode;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
use Doctrine\Persistence\ManagerRegistry;
use Nelmio\Alice\FileLoaderInterface;
use Symfony\Component\Yaml\Yaml;

class FixtureLoaderContext implements Context
{
    // we were probably messing up with ids. I managed to remove nearly all ids from fixtures, 
    // but for some files, it was just too complex.
    private const CLASS_WITH_FORCED_IDS = [
        SomeEntity::class,
        // …
    ];

    /**
     * @var FileLoaderInterface
     */
    private $aliceFileLoader;

    /**
     * @var string
     */
    private $basePath;

    /** @var ManagerRegistry */
    private $doctrine;

    public function __construct(
        FileLoaderInterface $aliceFileLoader,
        ManagerRegistry $doctrine,
        string $basePath
    ) {
        $this->aliceFileLoader = $aliceFileLoader;
        $this->doctrine = $doctrine;
        $this->basePath = $basePath;
    }

    /**
     * @SuppressWarnings(PHPMD.UnusedFormalParameter("persister"))
     *
     * @Given the following fixtures are loaded:
     * @Given the following fixtures files are loaded:
     * @Given the following fixtures files are loaded with the persister :persister:
     *
     * @param TableNode<string> $fixtures Path to the fixtures
     */
    public function thereAreSeveralFixtures(TableNode $fixtures, ?string $persister = null): void
    {
        $files = [];
        foreach ($fixtures->getRows() as $row) {
            $files[] = $row[0];
        }

        $this->loadFiles($files);
    }

    /**
     * @SuppressWarnings(PHPMD.UnusedFormalParameter("persister"))
     *
     * @Given the fixtures file :file is loaded with the persister :persister
     */
    public function theFixturesFileIsLoadedWithThePersister(string $file, ?string $persister = null): void
    {
        $this->loadFiles([$file]);
    }

    /**
     * TODO keep this method in case we want to load all file simultaneously (or see following TODO)
     *
     * @param array<string> $files
     */
    private function loadFiles(array $files): void
    {
        $fixturesFiles = [];
        $objects = [];

        foreach ($files as $file) {
            $fixturesFile = $this->basePath . $file;

            $fixturesFiles[] = $fixturesFile;

            // TODO this is probably useless as "do load file" does flush on each file now
            // but it was a list of files that I wanted to be flushed in DB BEFORE other files do load, as we have some db id fetching
            if (in_array($file, ['10.some_file.yml'])) {
                $objects = $this->doLoadFiles($fixturesFiles, $objects);
                $fixturesFiles = [];
            }
        }

        if ($fixturesFiles) {
            $this->doLoadFiles($fixturesFiles, $objects);
        }
    }

    /**
     * @param array<string> $files
     * @param array<object> $objects
     */
    private function doLoadFiles(array $files, array $objects)
    {
        // force offer rule id to be set
        foreach (self::CLASS_WITH_FORCED_IDS as $class) {
            $entityManager = $this->doctrine->getManagerForClass($class);
            if (!$entityManager) {
                throw new \RuntimeException('Unable to get entity manager for class ' . $class);
            }
            $metadata = $entityManager->getClassMetadata($class);
            if (!$metadata instanceof ClassMetadataInfo) {
                throw new \RuntimeException('Class with forced id metata must implement ClassMetadataInfo. This should not happen.');
            }

            // I force the `GENERATOR_TYPE_NONE` here to keep the old alice data fixture compatibility
            // You may want to use something different here ¯\_(ツ)_/¯
            $metadata->setIdGeneratorType(\Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_NONE);
        }

        $this->preventObjectNameConflict($files);

        $loader = $this->aliceFileLoader;

        $parameters = [];
        $objectList = $objects;
        foreach ($files as $file) {
            $objectSet = $loader->loadFile($file, $parameters, $objectList);

            $parameters = $objectSet->getParameters();
            $objectList = $objectSet->getObjects();

            foreach ($objectList as $object) {
                /** @var class-string */
                $objectClass = get_class($object);

                // if object with forced id, check that the id is set
                if (in_array($objectClass, self::CLASS_WITH_FORCED_IDS) && !$object->getId()) {
                    throw new \InvalidArgumentException($objectClass . ' ids must be set in fixtures in file ' . $file);
                }

                $entityManager = $this->doctrine->getManagerForClass($objectClass);

                if (!$entityManager) {
                    throw new \RuntimeException('Unable to find manager for class ' . $objectClass);
                }

                $entityManager->persist($object);
            }

            $entityManager->flush();
        }

        return $objectList;
    }

    /**
     * This function makes sur that there is no key present in several files loaded in the same time both files will be loaded in the same array
     * 
     * @SuppressWarnings(PHPMD.UnusedLocalVariable("localObject"))
     *
     * @param array<string> $files
     */
    private function preventObjectNameConflict(array $files): void
    {
        $objectList = [];
        foreach ($files as $file) {
            $parsedYaml = Yaml::parseFile($file);

            if ($parsedYaml) {
                foreach ($parsedYaml as $localObjectList) {
                    foreach ($localObjectList as $localKey => $localObject) {
                        if (array_key_exists($localKey, $objectList)) {
                            throw new \InvalidArgumentException(
                            sprintf(
                                'key %s of file %s already exist in previously loaded objects',
                                $localKey,
                                $file
                            )
                        );
                        }
                    }
                    $objectList += $localObjectList;
                }
            }
        }
    }
}

If you want to continue working on this PR, feel free to do so.

@COil
Copy link

COil commented Sep 27, 2021

Hello @jdeniau , thanks for the answer and the code. I came with another solution where I decorate the ObjectManagerPersister service. In the persist() function I also use getManagerForClass the get the good entity manager to use, then I force the ObjectManagerPersister.objectManager property with reflection (the persistableClasses property must also be reinitialized when the entity manager is changed).

@theofidry theofidry mentioned this pull request Dec 21, 2021
@theofidry theofidry reopened this Dec 21, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants