doctrine-orm icon indicating copy to clipboard operation
doctrine-orm copied to clipboard

Mapping interface on entity

Open makivlach opened this issue 6 years ago • 12 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.

makivlach avatar Jul 10 '19 14:07 makivlach

Thanks for pointing that. Could you please prepare a PR? I will help you.

f3l1x avatar Nov 11 '19 17:11 f3l1x

Hey @f3l1x, is this still relevant? Resolved or just stale?

patrickkusebauch avatar Mar 22 '20 23:03 patrickkusebauch

I think it needs to be proven in tests. And then we can implement it. You can go for it also.

f3l1x avatar Mar 23 '20 08:03 f3l1x

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?

patrickkusebauch avatar Mar 23 '20 09:03 patrickkusebauch

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?

patrickkusebauch avatar Mar 23 '20 14:03 patrickkusebauch

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 avatar Mar 23 '20 15:03 f3l1x

@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.

patrickkusebauch avatar Mar 23 '20 15:03 patrickkusebauch

Yup, let's take the shorter configuration pleas.

f3l1x avatar Mar 23 '20 16:03 f3l1x

@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.

patrickkusebauch avatar Mar 23 '20 21:03 patrickkusebauch

How about going the kdyby way - using targetEntityMappings in orm configuration section?

  1. I haven't yet encounter a case when I would need to define target entity differently in different connections. YAGNI
  2. It shields users from the implementation detail that it is done by an event listener.

PavelJurasek avatar May 09 '20 17:05 PavelJurasek

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 avatar Dec 11 '20 16:12 f3l1x

Ref: https://github.com/nettrine/dbal/issues/56

f3l1x avatar Dec 11 '20 16:12 f3l1x

@f3l1x Hi Felix, I am looking for this functionality right now. What is the state of this in Nettrine?

juniwalk avatar Oct 23 '23 11:10 juniwalk

It's an old issue, we have to refresh it. Totally wanted since 2020 as I remember.

f3l1x avatar Oct 23 '23 13:10 f3l1x

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?

juniwalk avatar Oct 24 '23 07:10 juniwalk

Can you show me the symfony/kdyby implementation?

f3l1x avatar Oct 25 '23 08:10 f3l1x

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>

juniwalk avatar Oct 25 '23 09:10 juniwalk

Sounds great, let's implement it similar way. Maybe we can skip tags.

f3l1x avatar Oct 25 '23 09:10 f3l1x

Alright I will try the listener outside nettrine first and then I will create PR.

juniwalk avatar Oct 25 '23 09:10 juniwalk

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));

image

juniwalk avatar Oct 25 '23 09:10 juniwalk