Mapping interface on entity
makivlach opened this issue · comments
There are sometimes use-cases when we don't want to use mapping on Entity class in relations, but rather interface (possibly for decoupling reasons). That is why Doctrine supports ResolveTargetEntityListener
(documentation on: https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/cookbook/resolve-target-entity-listener.html).
It would be nice to have such configuration placed in config.neon
. Symfony has its own implementation in config.yml
(as described on https://symfony.com/doc/current/doctrine/resolve_target_entity.html at the bottom of the page)
We managed to do a workaround by placing the following code inside EntityManagerDecorator
:
public function __construct(EntityManagerInterface $wrapped)
{
$resolveTargetEntityListener = new ResolveTargetEntityListener();
$resolveTargetEntityListener->addResolveTargetEntity(UserInterface::class, User::class, []);
$wrapped->getEventManager()->addEventListener(Events::loadClassMetadata, $resolveTargetEntityListener);
parent::__construct($wrapped);
}
It's good enough but people still have to remember that.
Thanks for pointing that. Could you please prepare a PR? I will help you.
Hey @f3l1x, is this still relevant? Resolved or just stale?
I think it needs to be proven in tests. And then we can implement it. You can go for it also.
I was just thinking to add a eventListeners
config. With alist of event names and class instances. That way you could re-use it for other purposes.
You would have to define your ResolveTargetEntityListener
instance elsewhere, not in the extension config though. WDYT?
I am proposing something like this:
nettrine.orm:
configuration:
eventListeners:
-
event: 'Events::loadClassMetadata'
listener: @RTEL
services:
RTEL:
factory: ResolveTargetEntityListener
setup:
- addResolveTargetEntity(UserInterface::class, User::class, [])
@f3l1x do you like it?
Do you think is better then group it by event?
nettrine.orm:
configuration:
eventListeners:
Events::loadClassMetadata:
- @RTEL
I'm not sure which one is better. Maybe include other folks?
@f3l1x Yeah I don't mind either way. Your way is definitely shorter, mine is more explicit. No idea where to get people for a poll. I was just in a mood to implement it. Don't really care about specifics.
Yup, let's take the shorter configuration pleas.
@f3l1x So there is an issue. Doctrine will complain if your EventManager
created for Connection
and EntityManager
are not one and the same https://github.com/doctrine/orm/blob/v2.7.2/lib/Doctrine/ORM/EntityManager.php#L904.
Since this library is dependant on Nettrine/dbal
it should be implemented there and then this library automatically carries over the EventManager
into the decorated EntityManager
.
I would, therefore, suggest to close this issue here and open it in Nettrine/dbal
.
How about going the kdyby way - using targetEntityMappings
in orm configuration section?
- I haven't yet encounter a case when I would need to define target entity differently in different connections. YAGNI
- It shields users from the implementation detail that it is done by an event listener.
I have released v0.8 and made a quick look at how to resolve this issue.
The thing is, that is ResolveTargetEntityListener
is listener and must be registered to Doctrine\Common\EventManager
, but the EventManager
is used in nettrine/dbal. So, I think we can elaborate on how to make EventManager
from nettrine/dbal more pluggable. Maybe similar to contributte/event-dispatcher we can lazily lookup for all doctrine-typed listeners and register them automatically.
@f3l1x Hi Felix, I am looking for this functionality right now. What is the state of this in Nettrine?
It's an old issue, we have to refresh it. Totally wanted since 2020 as I remember.
Well it's a great way to make independent packages. I like the symfony/kdyby implementation where you just put entity resolving into orm configuration and it is registered in DI extension automatically.
What would you need to help make this a reality?
Can you show me the symfony/kdyby implementation?
Well this is from symfony docs, this is how the configuration looks.
# config/packages/doctrine.yaml
doctrine:
# ...
orm:
# ...
resolve_target_entities:
App\Model\InvoiceSubjectInterface: App\Entity\Customer
And this is what I found in DI extension for DoctrineBundle
if ($config['resolve_target_entities']) {
$def = $container->findDefinition('doctrine.orm.listeners.resolve_target_entity');
foreach ($config['resolve_target_entities'] as $name => $implementation) {
$def->addMethodCall('addResolveTargetEntity', [
$name,
$implementation,
[],
]);
}
$def
->addTag('doctrine.event_listener', ['event' => Events::loadClassMetadata])
->addTag('doctrine.event_listener', ['event' => Events::onClassMetadataNotFound]);
}
This is registration of the Listener into DI
<!-- listeners -->
<parameter key="doctrine.orm.listeners.resolve_target_entity.class">Doctrine\ORM\Tools\ResolveTargetEntityListener</parameter>
<parameter key="doctrine.orm.listeners.attach_entity_listeners.class">Doctrine\ORM\Tools\AttachEntityListenersListener</parameter>
Sounds great, let's implement it similar way. Maybe we can skip tags.
Alright I will try the listener outside nettrine first and then I will create PR.
Well I registered the listener, modified one of my traits and it looks like it is working like a charm.
services:
targetEntityResolver:
create: Doctrine\ORM\Tools\ResolveTargetEntityListener
setup:
- addResolveTargetEntity(Nette\Security\IIdentity, App\Entity\User, [])
trait Authorable
{
#[ORM\ManyToOne(targetEntity: Identity::class)]
private ?Identity $author = null;
public function setAuthor(?Identity $author): void
{
$this->author = $author;
}
Just to be sure, I did one more test:
bdump($this->entityManager->getClassMetadata(Identity::class));