Skip to content

Introduce interfaces for the EventManager#100

Merged
greg0ire merged 1 commit intodoctrine:2.1.xfrom
greg0ire:soft-final
Jan 17, 2026
Merged

Introduce interfaces for the EventManager#100
greg0ire merged 1 commit intodoctrine:2.1.xfrom
greg0ire:soft-final

Conversation

@greg0ire
Copy link
Copy Markdown
Member

@greg0ire greg0ire commented Jan 15, 2026

This should allow using other extension strategies than inheritance in
the future, once other Doctrine project start accepting instances of
this interface.

@stof
Copy link
Copy Markdown
Member

stof commented Jan 15, 2026

making it actually final should probably wait until we have an interface that can be used as parameter/property type. Using a final class as type prevents any kind of extension.

@stof
Copy link
Copy Markdown
Member

stof commented Jan 15, 2026

And the need for an extension point is not theoretical. Symfony implements a lazy-loading event manager.

@greg0ire
Copy link
Copy Markdown
Member Author

@greg0ire greg0ire force-pushed the soft-final branch 2 times, most recently from 8711955 to 25c97cd Compare January 15, 2026 18:23
@greg0ire
Copy link
Copy Markdown
Member Author

@stof I've added an interface that could be implemented by ContainerAwareEventManager and used in other Doctrine packages. Maybe it's a bit early for the soft final annotation, I'm not sure. Do you think it would result in a lot of hard-to-address deprecations?

@greg0ire greg0ire requested a review from stof January 15, 2026 18:26
@stof
Copy link
Copy Markdown
Member

stof commented Jan 15, 2026

@greg0ire if would first require all Doctrine projects to migrate to using the interface as type, before Symfony could implement the interface without extending the class. So I would say it is indeed too early (we need to release the interface before being able to update downstream projects)

@greg0ire
Copy link
Copy Markdown
Member Author

@stof OK, I've removed the soft final from this PR.

@greg0ire greg0ire changed the title Mark class as soft final Introduce EventManagerInterface Jan 16, 2026
* The subscriber is asked for all the events it is interested in and added
* as a listener for these events.
*/
public function addEventSubscriber(EventSubscriber $subscriber): void;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering whether the methods that are configuring the instance should be part of the interface or no.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By comparison, the PSR-14 EventDispatcherInterface defines only a dispatchEvent method, and its ListenerProviderInterface defines a getListenersForEvent method, but the PSR-14 interfaces don't define any interface about how the event dispatcher is actually configured.

Copy link
Copy Markdown
Member Author

@greg0ire greg0ire Jan 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you're on the fence, maybe the solution is to have several interfaces?

Looking at the ORM code, it looks like the methods that are the most frequently used together are hasListeners and dispatchEvent (by far)

Then, you have addEventListener and removeEventListener, for temporary listeners that needs to be cleaned up during hydration.

And finally, you have hasListeners in combination with getListeners and getAllListeners, for debugging and introspection, inside the very recent DebugEventManagerDoctrineCommand

Copy link
Copy Markdown
Member Author

@greg0ire greg0ire Jan 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be frank, hasListeners seems like a micro-optimization, because dispatchEvent is a no-op when there are no listeners for the event passed in argument:

if (! isset($this->listeners[$eventName])) {
return;
}

I'm not saying we should deprecate it, but maybe it does not need to be in the same interface as dispatchEvent, and maybe we should rework the ORM code like so:

-        if ($this->evm->hasListeners(Events::loadClassMetadata)) {
-            $eventArgs = new LoadClassMetadataEventArgs($class, $this->em);
+        $eventArgs = new LoadClassMetadataEventArgs($class, $this->em);
-            $this->evm->dispatchEvent(Events::loadClassMetadata, $eventArgs);
+        $this->evm->dispatchEvent(Events::loadClassMetadata, $eventArgs);
-        }

Copy link
Copy Markdown
Member Author

@greg0ire greg0ire Jan 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks related: doctrine/mongodb-odm#2480

Copy link
Copy Markdown
Member Author

@greg0ire greg0ire Jan 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment shows why we might want to keep it: doctrine/mongodb-odm#2480 (comment)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there wouldn't be much overhead by always dispatching the event and letting $eventArgs->getFoundMetadata() return null when no listener was registered (and so no new metadata could be found).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely not, apparently this was more about readability.

@greg0ire greg0ire force-pushed the soft-final branch 3 times, most recently from 12dae11 to 6683195 Compare January 17, 2026 13:15
@greg0ire
Copy link
Copy Markdown
Member Author

@stof I went with 4 interfaces. 3 of them extend the EventDispatcher one.

/**
* Gets all listeners keyed by event name.
*
* @return array<string, object[]> The event listeners for the specified event, or all event listeners.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @return array<string, object[]> The event listeners for the specified event, or all event listeners.
* @return array<string, object[]> The event listeners for all events.

Getting listeners for a specified event requires using getListeners instead.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively, remove the description entirely, as done in #101, as this is already described in the previous sentence anyway.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I merged #101 up and went consistent with it.

@stof
Copy link
Copy Markdown
Member

stof commented Jan 17, 2026

The PR title needs to be updated, as this does not introduce an EventManagerInterface anymore.

@greg0ire greg0ire changed the title Introduce EventManagerInterface Introduce interfaces for the EventManager Jan 17, 2026
This should allow using other extension strategies than inheritance in
the future, once other Doctrine project start accepting instances of
this interface.
@greg0ire greg0ire added this to the 2.1.0 milestone Jan 17, 2026
@greg0ire greg0ire added the Enhancement New feature or request label Jan 17, 2026
@greg0ire greg0ire merged commit c0abeee into doctrine:2.1.x Jan 17, 2026
11 checks passed
@greg0ire greg0ire deleted the soft-final branch January 17, 2026 22:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants