-
-
Notifications
You must be signed in to change notification settings - Fork 5.2k
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
With multiple Entity Managers (EM) the Repository uses only the mapped EM for the Entity #9878
Comments
If this is not working as described, the issue should be reported to the Doctrine issue tracker where the implementing code is located. Though actually I do not see why that should not be working as described. |
Well, after all, it was, as after a few more tests I found out that I had an issue with where the Entities were placed: instead of having them in 'src/Entity/Main' and 'src/Entity/Customer', I had them in 'src/Entity' and 'src/EntityCustomer', with them being configured in that order. With that, Doctrine was mapping the customer entities to the first (and default) entity manager. Anyway, from what I've found out that the second parameter to As so, we can say the following: // Retrieves a repository managed by the "customer" em, the Customer's entity em
$customers = $this->getDoctrine()
->getRepository(Customer::class)
->findAll()
;
// Retrieves a repository managed by the "customer" em
$customers = $this->getDoctrine()
->getRepository(Customer::class, 'customer')
->findAll()
; So, the entity manager used in the repository is always the mapped one of that Entity, and by mapping the entities to a given em, we can't change a different connection to access some Entity, as it will only use the connection for it's em. I propose to add a note in the page specifying that it's not possible to use different entity managers to access the same Entity. |
@xabbuh what do you propose to do here? If it's true that |
Yes, I think we shouldn't have it. |
Hello, A brief introduction to our system: We have a vagrantbox, in there running the new application. Our Doctrine Config doctrine:
dbal:
default_connection: default
types:
uuid_binary_ordered_time: Ramsey\Uuid\Doctrine\UuidBinaryOrderedTimeType
connections:
default:
# configure these for your database server
driver: 'pdo_mysql'
[...]
url: '%env(resolve:DATABASE_URL)%' # Port 3306
[...]
command:
driver: 'pdo_mysql'
[...]
url: '%env(resolve:COMMAND_DATABASE_URL)%' # Port 3333
[...]
orm:
auto_generate_proxy_classes: '%kernel.debug%'
[...]
default_entity_manager: default
entity_managers:
default:
connection: default
naming_strategy: doctrine.orm.naming_strategy.underscore
mappings:
App:
is_bundle: false
type: annotation
dir: '%kernel.project_dir%/src/Entity'
prefix: 'App\Entity'
alias: App
command:
connection: command
naming_strategy: doctrine.orm.naming_strategy.underscore
mappings:
App:
is_bundle: false
type: annotation
dir: '%kernel.project_dir%/src/Entity'
prefix: 'App\Entity'
alias: AppCommand
In our Command Script: $doctrine = $this->container->get('doctrine');
$doctrine->getRepository(Salutation::class, 'command')->findAll() // But Doctrine use the default EntityManager We found in this File: The method "getManagerForClass" ignores the defindet connection foreach ($this->managers as $id) {
$manager = $this->getService($id);
if (! $manager->getMetadataFactory()->isTransient($class)) { // <-- first goal
return $manager;
}
}
```php
[...]
// Debug output
$this = {Doctrine\Bundle\DoctrineBundle\Registry} [7]
...
*Doctrine\Common\Persistence\AbstractManagerRegistry*managers = {array} [2]
default = "doctrine.orm.default_entity_manager" // <-- first goal
command = "doctrine.orm.command_entity_manager"
...
The problem is just by Entitys with defined CustomRepositorys
```php
/**
* @ORM\Table(name="salutations")
* @ORM\Entity(repositoryClass="App\Repository\Person\SalutationRepository") // <-- Without CustomRepository it works
*/
class Salutation {} Without CustomRepository it works because the Method "getRepository" in Class "ContainerRepositoryFactory" found no a $customRepositoryName so the call
but have the method found a CustomRepository the call |
is there any news regarding the resolution of this issue? |
关于解决这个问题的消息有什么消息吗? |
Just don't use a custom repository. This will work. |
I'm in the same situation. I want to use another entity manager to connect to a read-only replica of the original database using the same entities. |
the only way that i found to use both custom repositories and more than one database is to use symfony version 3.4 |
So far I have found this temporary solution for Symfony4 private $registry;
public function __construct(RegistryInterface $registry)
{
$this->registry = $registry;
//parent::__construct($registry, MyEntity::class);
}
public function findAllByFieldName($customFieldName, $connection): array
{
$em = $this->registry->getEntityManager($connection);
$customFields = $em->getRepository(MyEntity::class)->findOneBy(['Field_name'=>$customFieldName]);
/* ...... */
} Ugly but works.. |
this issue just cost me about a day's worth of debugging (I'm just starting out with symfony/doctrine so it took a while). together with a colleague we came to this workaround (we have two types of users, each with its own repositry and entitymanager) namespace App\Repository;
use App\Entity\CustomUser;
use Symfony\Bridge\Doctrine\RegistryInterface;
use Doctrine\ORM\EntityRepository;
class CustomUserRepository extends AbstractUserRepository
{
public function __construct(RegistryInterface $registry)
{
$manager = $registry->getEntityManager('namedentitymanager');
parent::__construct($registry, CustomUser::class);
EntityRepository::__construct($manager, $manager->getClassMetadata(CustomUser::class));
}
} the thought being that the parent::__construct() puts in the default entity manager (the wrong one). the call to EntityRepository::__construct() fixes this. |
Today I've run into similar problem. Long story short - I want to copy data from database no2 into database no1. Both are using same code base. @andrmoel is right - only solution for now is to not use custom repositories. The fastest way will be to use QueryBuilder: https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/query-builder.html#constructing-a-new-querybuilder-object In another words - don't user Example: // get EntityManagers you want to use
$emTo = $registry->getManager(); // manager connected to "main" database
$emFrom = $registry->getManager('migrate'); // manager connected to other database
// create QueryBuilder for Entity from 'migrate' database
$emFrom->createQueryBuilder()
->select('a')
->from('App\Entity\Article', 'a')
;
// add some regular methods and conditions next It looks easy (because it is) and obvious, but it wasn't easy to figure it out. Thank you guys for help! |
Hi! any news regarding this issue? I tried with Symfony 4.3.0, same issue... |
Can someone create a small example application that allows to debug? I would like to find out whether that's an issue in the documentation, in Symfony or in Doctrine, but I don't have such a use case in any of my applications. |
Hi! I created a new installation here: |
config/packages/doctrine.yaml doctrine:
dbal:
default_connection: default
connections:
default:
# configure these for your database server
url: '%env(DATABASE_DB1_URL)%'
driver: 'pdo_mysql'
server_version: '5.7'
charset: utf8mb4
offerbdd:
# configure these for your database server
url: '%env(DATABASE_DB2_URL)%'
driver: 'pdo_mysql'
server_version: '5.7'
charset: utf8mb4
orm:
default_entity_manager: default
entity_managers:
default:
connection: default
mappings:
App:
is_bundle: false
type: annotation
dir: '%kernel.project_dir%/src/Entity'
prefix: 'App\Entity\'
alias: App
offerbdd:
connection: offerbdd
mappings:
Offer:
is_bundle: false
type: annotation
dir: '%kernel.project_dir%/src/Entity'
prefix: 'App\Entity\'
alias: Offer My Repository namespace App\Repository;
use App\Entity\Offer;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Symfony\Bridge\Doctrine\RegistryInterface;
/**
* @method Offer|null find($id, $lockMode = null, $lockVersion = null)
* @method Offer|null findOneBy(array $criteria, array $orderBy = null)
* @method Offer[] findAll()
* @method Offer[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class OfferRepository extends ServiceEntityRepository
{
private $registry;
public function __construct(RegistryInterface $registry)
{
$this->registry = $registry;
}
/**
* @return mixed
*/
public function findListedAll($connection)
{
$sql = "SELECT o FROM App\Entity\Offer o WHERE o.unlisted = :val OR o.unlisted IS NULL "
. "ORDER BY o.id DESC";
return $this->registry->getEntityManager($connection)
->createQuery($sql)
->setParameter('val', false)
->getResult();
}
} My Controller: public function index(Request $request)
{
...
$offerEntityManager = $this->getDoctrine()->getManager('offerbdd');
$offers = $offerEntityManager->getRepository(Offer::class)->findListedAll('offerbdd');
....
} It's work! |
Hey! Same here. Ended up in removing Edit: another possible workaround ArticleRepository extends ServiceEntityRepository
{
...
public function setEntityManager(EntityManagerInterface $entityManager): self
{
$this->_em = $entityManager;
return $this;
}
....
} e.g. $activeEntityManager = ...;
$articles = $activeEntityManager
->getRepository('App:Article')
->setEntityManager($activeEntityManager)
->findAll()
; @xabbuh for me it seems to be doctrine issue. Some debugging brought me to the same point as @delorie already showed in his post. |
@xabbuh did you already find some time to try the reproducer mentioned in #9878 (comment) ? |
The example application didn't make use of the However, if we change the code to actually use the manager registry, we will see no difference in the behaviour. This comes from the fact that |
I'm replying here on the subject of having multiple entity managers managing the same entities and the bug related to it.
Agreed with the description @xabbuh but as @andrmoel said, we can consider there is still a bug here, because there is a different behavior based on whether the entity is defined with a custom repository and how this one is defined or whether no custom repository is used. It's subtle because if the entity uses a custom repository extending the Here is a repro project: https://github.com/ipernet/symfony-doctrine-multi-em-test And the test command: <?php
namespace App\Command;
use App\Entity\Cat;
use App\Entity\Dog;
use App\Entity\Duck;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Doctrine\Common\Persistence\ManagerRegistry;
class SameEntityDualManagerCommand extends Command
{
// the name of the command (the part after "bin/console")
protected static $defaultName = 'app:test';
/**
* @var ManagerRegistry
*/
private $registry;
public function __construct(ManagerRegistry $registry)
{
$this->registry = $registry;
$this->defautEm = $this->registry->getManager('default');
$this->otherEm = $this->registry->getManager('em2');
parent::__construct();
}
protected function configure()
{
// ...
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->output = $output;
$this->testEntityWithNoCustomRepository();
$this->testEntityWithNoCustomRepositoryUsingRegistryRepository();
$this->testEntityWithCustomRepositoryExtendingServiceEntityRepository();
$this->testEntityWithCustomRepositoryExtendingServiceEntityRepositoryUsingRegistryRepository();
$this->testEntityWithCustomRepositoryExtendingEntityRepository();
$this->testEntityWithCustomRepositoryExtendingEntityRepositoryUsingRegistryRepository();
}
/**
*
* @return void
*/
private function testEntityWithNoCustomRepository()
{
$entityDefaultEm = $this->defautEm->getRepository(Dog::class)->findOneBy(['id' => 1]);
$entityEm2 = $this->otherEm->getRepository(Dog::class)->findOneBy(['id' => 1]);
$this->testResult($entityDefaultEm, $entityEm2);
$this->output->writeln(' - '.__FUNCTION__);
}
/**
*
* @return void
*/
public function testEntityWithNoCustomRepositoryUsingRegistryRepository()
{
$entityDefaultEm = $this->registry->getRepository(Dog::class, 'default')->findOneBy(['id' => 1]);
$entityEm2 = $this->registry->getRepository(Dog::class, 'em2')->findOneBy(['id' => 1]);
$this->testResult($entityDefaultEm, $entityEm2);
$this->output->writeln(' - '.__FUNCTION__);
}
/**
* @return void
*/
private function testEntityWithCustomRepositoryExtendingServiceEntityRepository()
{
$entityDefaultEm = $this->defautEm->getRepository(Cat::class)->findOneBy(['id' => 1]);
$entityEm2 = $this->otherEm->getRepository(Cat::class)->findOneBy(['id' => 1]);
$this->testResult($entityDefaultEm, $entityEm2);
$this->output->writeln(' - '.__FUNCTION__);
}
/**
*
* @return void
*/
public function testEntityWithCustomRepositoryExtendingServiceEntityRepositoryUsingRegistryRepository()
{
$entityDefaultEm = $this->registry->getRepository(Cat::class, 'default')->findOneBy(['id' => 1]);
$entityEm2 = $this->registry->getRepository(Cat::class, 'em2')->findOneBy(['id' => 1]);
$this->testResult($entityDefaultEm, $entityEm2);
$this->output->writeln(' - '.__FUNCTION__);
}
/**
*
* @return void
*/
public function testEntityWithCustomRepositoryExtendingEntityRepository()
{
$entityDefaultEm = $this->defautEm->getRepository(Duck::class)->findOneBy(['id' => 1]);
$entityEm2 = $this->otherEm->getRepository(Duck::class)->findOneBy(['id' => 1]);
$this->testResult($entityDefaultEm, $entityEm2);
$this->output->writeln(' - '.__FUNCTION__);
}
/**
*
* @return void
*/
public function testEntityWithCustomRepositoryExtendingEntityRepositoryUsingRegistryRepository()
{
$entityDefaultEm = $this->registry->getRepository(Duck::class, 'default')->findOneBy(['id' => 1]);
$entityEm2 = $this->registry->getRepository(Duck::class, 'em2')->findOneBy(['id' => 1]);
$this->testResult($entityDefaultEm, $entityEm2);
$this->output->writeln(' - '.__FUNCTION__);
}
private function testResult($entityDefaultEm, $entityEm2)
{
if (spl_object_hash($entityDefaultEm) === spl_object_hash($entityEm2)) {
$this->output->write('<error>NOT OK: The two entities should not be the same object</error>');
} else {
$this->output->write('<info>OK: The two entities are different objects</info>');
}
}
} |
Sorry for bothering you Andreas (@alcaeus ), but can you please have a look at this issue and the reproducers and may support us here? Thanks 🙏 🍺 |
No problem. Without reading through the entire issue, there used to be an issue in the manager registry where calling To cut a lot of discussion short: this auto-detection only works when an entity is only managed by a single entity manager. If the same entity is managed by multiple entity managers, In the future, we may consider throwing an exception in One more issue that was highlighted here was that of service repositories. Keep in mind that the entire setup of service repositories has the "90%-use-case" in mind, which in this case would be a single entity manager, or at least having the entity only managed by a single entity manager. Once an entity is managed by multiple entity managers, you'd need to have multiple services for the repository, which the system currently doesn't handle. This is not to say that you can't use service repositories at all in such a scenario, you just need to create more glue code to work around these issues. Throwing an exception if an entity is managed by multiple entity managers would also fix this, as the repository service will no longer be instantiable, forcing the user to realise that they've taken a wrong turn somewhere. So, to summarise:
|
Hi @alcaeus Thanks for your time and the very detailed information given here. I understand the limits of the "service repositories" you describe and so can only agree with the conclusion you draw. II'll try to contribute to the improvement of the How to Work with multiple Entity Managers and Connections documentation to include the juicy bits of information we have here. Thanks! |
Awesome, thanks @ipernet! |
Hi @alcaeus, as a possible answer to these limitations I have put an idea here: doctrine/DoctrineBundle#1044 |
The project I have is to build an API to manage 3 db on 3 different sites. So I have to use 1 entity on 3 Managers. |
After a year still documentation is not enough to cover this issue and still not working in a straight-forward way ... https://github.com/r2my/symfomem this repo is still not working. Being told : this is not an ignorable problem imho , at least a proper workaround should be added to docs to be prepared for future updates. |
Woh, I'm having the same problem with Symfony 4.4. $data_niveau = $this->doctrine->getRepository(DataNiveau::class, $connection)->findAll(); Works perfectly fine on SF 3.4. It completely break my application, cannot upgrade to SF 4.4 for now. |
This bug is reproduced over and over. Symfony 5.04 still has the same issue. |
Same problem on a Symfony 3.4 app. The default connection is always used :( |
Hi everyone, since Symfony 5 the provided workaround from @tech-no-logical (#9878 (comment)) does not longer work. Has anybody a new solution? I'm struggling at this point. Thank you very much! |
@Ph0xEn I managed to use a 2nd EM with @soeren-helbig's solution #9878 (comment). At least it works :). |
Hi @Glioburd, thank you for the link. It works like a charm :) |
Very confused. These issues are a result of using the ServiceEntityRepository in order to support autowire. Extend your repository from EntityRepository, exclude your repositories from autowire and (if needed) go back to manually creating and injecting repository services. |
Is the workaround of @soeren-helbig in his comment still working? Could someone give me a more explicit code-example? I can't get his code to run. Or is there already an official solution to use two entity managers for one entity? |
The DoctrineBundle no official solution to manage one entity type with multiple managers. It has been that way ever since the ServiceEntityRepository class was introduced and seems unlikely to change. Best approach is to simply not do that. You can however:
At this point you should be good to go as long as you are fine with always using $em->getRepository(Entity::class) to get your repositories. If you have the urge to inject the repositories as services then you need to manually define them using a factory service definition. It is what we used to do back in the pre-autowire days. It is also instructive to compare the DefaultRepositoryFactory with the ContainerRepositoryFactory to see why it's difficult to support multiple entity managers with autowire. One final note: I have sometime seen stuff about how resetting an entity manager does not work properly with the DoctrineBundle code. I don't do that sort of thing and I don't recall exactly what the problem was. Just be careful if you do happen to use reset. |
I use this workaround : #9878 (comment). |
@pouetman87 Pretty sure that work around is not doing what you think it is. As long as the ContainerRepositoryFactory is being used then you will never get different repository instances. And swapping around which manager a repository belongs to seems like asking for trouble. |
Is there a way to add an entity manager in the doctrine config so that it is specifically NEVER used when injecting a repository that extends |
What's about using the
This should solve the problem? A EntityRepository autowireable |
In the manual page How to Work with multiple Entity Managers and Connections for Symfony 4, we can see in the end the following code snippet:
Using the ORM configuration code indicated in the beginning of that document, where the Customer entity is mapped to the 'customer' entity manager, the
'customer'
parameter for thegetRepository()
method is ignored.As so, this last code about repository calls should be updated.
Also, I suggest to add the code on how to use multiple entity managers for a single Entity, or to add a note that it's not possible if that's the case.
The text was updated successfully, but these errors were encountered: