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

Mapping interface on entity #52

Closed
makivlach opened this issue Jul 10, 2019 · 20 comments
Closed

Mapping interface on entity #52

makivlach opened this issue Jul 10, 2019 · 20 comments
Assignees
Labels

Comments

@makivlach
Copy link

makivlach commented Jul 10, 2019

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.

@f3l1x
Copy link
Member

f3l1x commented Nov 11, 2019

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

@f3l1x f3l1x added the feature label Nov 11, 2019
@patrickkusebauch
Copy link
Contributor

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

@f3l1x
Copy link
Member

f3l1x commented Mar 23, 2020

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

@patrickkusebauch
Copy link
Contributor

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
Copy link
Contributor

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?

@f3l1x
Copy link
Member

f3l1x commented Mar 23, 2020

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?

@patrickkusebauch
Copy link
Contributor

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

@f3l1x
Copy link
Member

f3l1x commented Mar 23, 2020

Yup, let's take the shorter configuration pleas.

@patrickkusebauch
Copy link
Contributor

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

@PavelJurasek
Copy link

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.

@f3l1x
Copy link
Member

f3l1x commented Dec 11, 2020

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
Copy link
Member

f3l1x commented Dec 11, 2020

Ref: contributte/doctrine-dbal#56

@f3l1x f3l1x self-assigned this Dec 11, 2020
@juniwalk
Copy link
Contributor

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

@f3l1x
Copy link
Member

f3l1x commented Oct 23, 2023

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

@juniwalk
Copy link
Contributor

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?

@f3l1x
Copy link
Member

f3l1x commented Oct 25, 2023

Can you show me the symfony/kdyby implementation?

@juniwalk
Copy link
Contributor

juniwalk commented Oct 25, 2023

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>

@f3l1x
Copy link
Member

f3l1x commented Oct 25, 2023

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

@juniwalk
Copy link
Contributor

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

@juniwalk
Copy link
Contributor

juniwalk commented Oct 25, 2023

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

@f3l1x f3l1x closed this as completed in 1bd93ee Oct 25, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Development

No branches or pull requests

5 participants