contributte / doctrine-orm

:fire: Well-integrated Doctrine ORM for Nette Framework

Home Page:https://contributte.org/packages/nettrine/orm.html

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Mapping interface on entity

makivlach opened this issue · comments

commented

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?

  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.

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

image