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

WIP Allow usage of Symfony EventDispatcher instead of Doctrine EventManager #2686

Draft
wants to merge 6 commits into
base: 2.10.x
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"phpunit/phpunit": "^10.4",
"squizlabs/php_codesniffer": "^3.5",
"symfony/cache": "^5.4 || ^6.0 || ^7.0",
"symfony/event-dispatcher-contracts": "^2.0 || ^3.0",
"vimeo/psalm": "~5.24.0"
},
"conflict": {
Expand Down
54 changes: 45 additions & 9 deletions lib/Doctrine/ODM/MongoDB/DocumentManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,20 @@
use Doctrine\ODM\MongoDB\Repository\GridFSRepository;
use Doctrine\ODM\MongoDB\Repository\RepositoryFactory;
use Doctrine\ODM\MongoDB\Repository\ViewRepository;
use Doctrine\ODM\MongoDB\Utility\EventDispatcher;
use Doctrine\Persistence\Mapping\ProxyClassNameResolver;
use Doctrine\Persistence\ObjectManager;
use InvalidArgumentException;
use Jean85\PrettyVersions;
use LogicException;
use MongoDB\Client;
use MongoDB\Collection;
use MongoDB\Database;
use MongoDB\Driver\ReadPreference;
use MongoDB\GridFS\Bucket;
use ProxyManager\Proxy\GhostObjectInterface;
use RuntimeException;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use Throwable;

use function array_search;
Expand Down Expand Up @@ -79,7 +82,12 @@ class DocumentManager implements ObjectManager
/**
* The event manager that is the central point of the event system.
*/
private EventManager $eventManager;
private ?EventManager $eventManager;

/**
* The event dispatcher that is the central point of the event system.
*/
private EventDispatcherInterface $eventDispatcher;

/**
* The Hydrator factory instance.
Expand Down Expand Up @@ -141,11 +149,23 @@ class DocumentManager implements ObjectManager
* Creates a new Document that operates on the given Mongo connection
* and uses the given Configuration.
*/
protected function __construct(?Client $client = null, ?Configuration $config = null, ?EventManager $eventManager = null)
protected function __construct(?Client $client = null, ?Configuration $config = null, EventManager|EventDispatcherInterface|null $eventDispatcher = null)
{
$this->config = $config ?: new Configuration();
$this->eventManager = $eventManager ?: new EventManager();
$this->client = $client ?: new Client(
$this->config = $config ?: new Configuration();

if ($eventDispatcher instanceof EventDispatcherInterface) {
// This is a new feature, we can accept that the EventManager
// is not available when and EventDispatcher is injected.
$this->eventManager = null;
$this->eventDispatcher = $eventDispatcher;
} else {
// Backward compatibility with Doctrine EventManager
// @todo deprecate and create a new Symfony EventDispatcher instance
$this->eventManager = $eventDispatcher ?? new EventManager();
$this->eventDispatcher = new EventDispatcher($this->eventManager);
}

$this->client = $client ?: new Client(
'mongodb://127.0.0.1',
[],
[
Expand Down Expand Up @@ -173,13 +193,13 @@ protected function __construct(?Client $client = null, ?Configuration $config =
$hydratorNs = $this->config->getHydratorNamespace();
$this->hydratorFactory = new HydratorFactory(
$this,
$this->eventManager,
$this->eventDispatcher,
$hydratorDir,
$hydratorNs,
$this->config->getAutoGenerateHydratorClasses(),
);

$this->unitOfWork = new UnitOfWork($this, $this->eventManager, $this->hydratorFactory);
$this->unitOfWork = new UnitOfWork($this, $this->eventDispatcher, $this->hydratorFactory);
$this->schemaManager = new SchemaManager($this, $this->metadataFactory);
$this->proxyFactory = new StaticProxyFactory($this);
$this->repositoryFactory = $this->config->getRepositoryFactory();
Expand All @@ -197,16 +217,32 @@ public function getProxyFactory(): ProxyFactory
* Creates a new Document that operates on the given Mongo connection
* and uses the given Configuration.
*/
public static function create(?Client $client = null, ?Configuration $config = null, ?EventManager $eventManager = null): DocumentManager
public static function create(?Client $client = null, ?Configuration $config = null, EventManager|EventDispatcherInterface|null $eventDispatcher = null): DocumentManager
{
return new static($client, $config, $eventManager);
return new static($client, $config, $eventDispatcher);
}

/**
* @todo should we return a {@see Symfony\Component\EventDispatcher\EventDispatcherInterface} instead?
* So that it's explicitly possible to add/remove listeners. Or we just rely on
* the object that is injected and let the user validate the subtype.
*/
public function getEventDispatcher(): EventDispatcherInterface
{
return $this->eventDispatcher;
}

/**
* Gets the EventManager used by the DocumentManager.
*
* @deprecated Use getEventDispatcher() instead.
*/
public function getEventManager(): EventManager
{
if (! $this->eventManager) {
throw new LogicException('Use getEventDispatcher() instead of getEventManager()');
}

return $this->eventManager;
}

Expand Down
19 changes: 13 additions & 6 deletions lib/Doctrine/ODM/MongoDB/Hydrator/HydratorFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
use Doctrine\ODM\MongoDB\Types\Type;
use Doctrine\ODM\MongoDB\UnitOfWork;
use Doctrine\ODM\MongoDB\Utility\EventDispatcher;
use ProxyManager\Proxy\GhostObjectInterface;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;

use function array_key_exists;
use function chmod;
Expand Down Expand Up @@ -47,9 +49,9 @@
private DocumentManager $dm;

/**
* The EventManager associated with this Hydrator
* The Event Dispatcher associated with this Hydrator
*/
private EventManager $evm;
private EventDispatcherInterface $evm;

/**
* Which algorithm to use to automatically (re)generate hydrator classes.
Expand All @@ -74,7 +76,7 @@
private array $hydrators = [];

/** @throws HydratorException */
public function __construct(DocumentManager $dm, EventManager $evm, ?string $hydratorDir, ?string $hydratorNs, int $autoGenerate)
public function __construct(DocumentManager $dm, EventManager|EventDispatcherInterface $evm, ?string $hydratorDir, ?string $hydratorNs, int $autoGenerate)
{
if (! $hydratorDir) {
throw HydratorException::hydratorDirectoryRequired();
Expand All @@ -85,7 +87,7 @@
}

$this->dm = $dm;
$this->evm = $evm;
$this->evm = $evm instanceof EventManager ? new EventDispatcher($evm) : $evm;
$this->hydratorDir = $hydratorDir;
$this->hydratorNamespace = $hydratorNs;
$this->autoGenerate = $autoGenerate;
Expand Down Expand Up @@ -433,7 +435,12 @@
$metadata->invokeLifecycleCallbacks(Events::preLoad, $document, $args);
}

$this->evm->dispatchEvent(Events::preLoad, new PreLoadEventArgs($document, $this->dm, $data));
$eventArgs = new PreLoadEventArgs($document, $this->dm, $data);
if ($this->evm instanceof EventDispatcherInterface) {
$this->evm->dispatch($eventArgs, Events::preLoad);
} else {

Check failure on line 441 in lib/Doctrine/ODM/MongoDB/Hydrator/HydratorFactory.php

View workflow job for this annotation

GitHub Actions / Static Analysis with PHPStan (8.2)

Else branch is unreachable because previous condition is always true.
$this->evm->dispatchEvent(Events::preLoad, $eventArgs);

Check failure on line 442 in lib/Doctrine/ODM/MongoDB/Hydrator/HydratorFactory.php

View workflow job for this annotation

GitHub Actions / Static Analysis with Psalm (8.2)

UndefinedInterfaceMethod

lib/Doctrine/ODM/MongoDB/Hydrator/HydratorFactory.php:442:25: UndefinedInterfaceMethod: Method Symfony\Contracts\EventDispatcher\EventDispatcherInterface::dispatchEvent does not exist (see https://psalm.dev/181)
}

// alsoLoadMethods may transform the document before hydration
if (! empty($metadata->alsoLoadMethods)) {
Expand Down Expand Up @@ -470,7 +477,7 @@
$metadata->invokeLifecycleCallbacks(Events::postLoad, $document, [new LifecycleEventArgs($document, $this->dm)]);
}

$this->evm->dispatchEvent(Events::postLoad, new LifecycleEventArgs($document, $this->dm));
$this->evm->dispatch(new LifecycleEventArgs($document, $this->dm), Events::postLoad);

return $data;
}
Expand Down
24 changes: 11 additions & 13 deletions lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace Doctrine\ODM\MongoDB\Mapping;

use Doctrine\Common\EventManager;
use Doctrine\ODM\MongoDB\Configuration;
use Doctrine\ODM\MongoDB\ConfigurationException;
use Doctrine\ODM\MongoDB\DocumentManager;
Expand All @@ -21,6 +20,7 @@
use Doctrine\Persistence\Mapping\Driver\MappingDriver;
use Doctrine\Persistence\Mapping\ReflectionService;
use ReflectionException;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;

use function assert;
use function get_class_methods;
Expand Down Expand Up @@ -52,8 +52,8 @@
/** @var MappingDriver The used metadata driver. */
private MappingDriver $driver;

/** @var EventManager The event manager instance */
private EventManager $evm;
/** @var EventDispatcherInterface The event dispatcher instance */
private EventDispatcherInterface $evm;

public function setDocumentManager(DocumentManager $dm): void
{
Expand All @@ -77,20 +77,16 @@
}

$this->driver = $driver;
$this->evm = $this->dm->getEventManager();
$this->evm = $this->dm->getEventDispatcher();
$this->initialized = true;
}

/** @param string $className */
protected function onNotFoundMetadata($className)
{
if (! $this->evm->hasListeners(Events::onClassMetadataNotFound)) {
return null;
}

$eventArgs = new OnClassMetadataNotFoundEventArgs($className, $this->dm);

$this->evm->dispatchEvent(Events::onClassMetadataNotFound, $eventArgs);
$this->evm->dispatch($eventArgs, Events::onClassMetadataNotFound);

return $eventArgs->getFoundMetadata();
}
Expand Down Expand Up @@ -195,10 +191,12 @@

$class->setParentClasses($nonSuperclassParents);

$this->evm->dispatchEvent(
Events::loadClassMetadata,
new LoadClassMetadataEventArgs($class, $this->dm),
);
$eventArgs = new LoadClassMetadataEventArgs($class, $this->dm);
if ($this->evm instanceof EventDispatcherInterface) {
$this->evm->dispatch($eventArgs, Events::loadClassMetadata);
} else {

Check failure on line 197 in lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataFactory.php

View workflow job for this annotation

GitHub Actions / Static Analysis with PHPStan (8.2)

Else branch is unreachable because previous condition is always true.
$this->evm->dispatchEvent(Events::loadClassMetadata, $eventArgs);

Check failure on line 198 in lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataFactory.php

View workflow job for this annotation

GitHub Actions / Static Analysis with Psalm (8.2)

UndefinedInterfaceMethod

lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataFactory.php:198:25: UndefinedInterfaceMethod: Method Symfony\Contracts\EventDispatcher\EventDispatcherInterface::dispatchEvent does not exist (see https://psalm.dev/181)
}

// phpcs:ignore SlevomatCodingStandard.ControlStructures.EarlyExit.EarlyExitNotUsed
if ($class->isChangeTrackingNotify()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ final class StaticProxyFactory implements ProxyFactory
public function __construct(DocumentManager $documentManager)
{
$this->uow = $documentManager->getUnitOfWork();
$this->lifecycleEventManager = new LifecycleEventManager($documentManager, $this->uow, $documentManager->getEventManager());
$this->lifecycleEventManager = new LifecycleEventManager($documentManager, $this->uow, $documentManager->getEventDispatcher());
$this->proxyFactory = $documentManager->getConfiguration()->buildGhostObjectFactory();
}

Expand Down
17 changes: 9 additions & 8 deletions lib/Doctrine/ODM/MongoDB/UnitOfWork.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
use MongoDB\Driver\WriteConcern;
use ProxyManager\Proxy\GhostObjectInterface;
use ReflectionProperty;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use Throwable;
use UnexpectedValueException;

Expand Down Expand Up @@ -234,9 +235,9 @@ final class UnitOfWork implements PropertyChangedListener
private DocumentManager $dm;

/**
* The EventManager used for dispatching events.
* The Event Dispatcher used for dispatching events.
*/
private EventManager $evm;
private EventDispatcherInterface $evm;

/**
* Additional documents that are scheduled for removal.
Expand Down Expand Up @@ -292,10 +293,10 @@ final class UnitOfWork implements PropertyChangedListener
/**
* Initializes a new UnitOfWork instance, bound to the given DocumentManager.
*/
public function __construct(DocumentManager $dm, EventManager $evm, HydratorFactory $hydratorFactory)
public function __construct(DocumentManager $dm, EventManager|EventDispatcherInterface $evm, HydratorFactory $hydratorFactory)
{
$this->dm = $dm;
$this->evm = $evm;
$this->evm = $evm instanceof EventManager ? new Utility\EventDispatcher($evm) : $evm;
$this->hydratorFactory = $hydratorFactory;
$this->lifecycleEventManager = new LifecycleEventManager($dm, $this, $evm);
$this->reflectionService = new RuntimeReflectionService();
Expand Down Expand Up @@ -425,7 +426,7 @@ public function commit(array $options = []): void
}

// Raise preFlush
$this->evm->dispatchEvent(Events::preFlush, new Event\PreFlushEventArgs($this->dm));
$this->evm->dispatch(new Event\PreFlushEventArgs($this->dm), Events::preFlush);

// Compute changes done since last commit.
$this->computeChangeSets();
Expand Down Expand Up @@ -454,7 +455,7 @@ public function commit(array $options = []): void
}
}

$this->evm->dispatchEvent(Events::onFlush, new Event\OnFlushEventArgs($this->dm));
$this->evm->dispatch(new Event\OnFlushEventArgs($this->dm), Events::onFlush);

if ($this->useTransaction($options)) {
$session = $this->dm->getClient()->startSession();
Expand All @@ -473,7 +474,7 @@ function (Session $session) use ($options): void {
}

// Raise postFlush
$this->evm->dispatchEvent(Events::postFlush, new Event\PostFlushEventArgs($this->dm));
$this->evm->dispatch(new Event\PostFlushEventArgs($this->dm), Events::postFlush);

// Clear up
foreach ($this->visitedCollections as $collections) {
Expand Down Expand Up @@ -2430,7 +2431,7 @@ public function clear(?string $documentName = null): void
$event = new Event\OnClearEventArgs($this->dm, $documentName);
}

$this->evm->dispatchEvent(Events::onClear, $event);
$this->evm->dispatch($event, Events::onClear);
}

/**
Expand Down
44 changes: 44 additions & 0 deletions lib/Doctrine/ODM/MongoDB/Utility/EventDispatcher.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

declare(strict_types=1);

namespace Doctrine\ODM\MongoDB\Utility;

use Doctrine\Common\EventArgs;
use Doctrine\Common\EventManager;
use InvalidArgumentException;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;

/**
* Used to wrap a Doctrine EventManager as a Symfony EventDispatcherInterface
* when a Doctrine EventManager is injected into the DocumentManager.
*
* @internal
*/
class EventDispatcher implements EventDispatcherInterface
{
public function __construct(private EventManager $eventManager)
{
}

/**
* Dispatches an event to all registered listeners.
*
* @param T $event The event to pass to the event handlers/listeners
* @param string $eventName The name of the event to dispatch.
*
* @return T The passed $event MUST be returned
*
* @template T of EventArgs
*/
public function dispatch(object $event, string|null $eventName = null): object

Check failure on line 34 in lib/Doctrine/ODM/MongoDB/Utility/EventDispatcher.php

View workflow job for this annotation

GitHub Actions / Static Analysis with Psalm (8.2)

MoreSpecificImplementedParamType

lib/Doctrine/ODM/MongoDB/Utility/EventDispatcher.php:34:37: MoreSpecificImplementedParamType: Argument 1 of Doctrine\ODM\MongoDB\Utility\EventDispatcher::dispatch has the more specific type 'Doctrine\Common\EventArgs', expecting 'object' as defined by Symfony\Contracts\EventDispatcher\EventDispatcherInterface::dispatch (see https://psalm.dev/140)

Check failure on line 34 in lib/Doctrine/ODM/MongoDB/Utility/EventDispatcher.php

View workflow job for this annotation

GitHub Actions / Static Analysis with Psalm (8.2)

MoreSpecificImplementedParamType

lib/Doctrine/ODM/MongoDB/Utility/EventDispatcher.php:34:37: MoreSpecificImplementedParamType: Argument 1 of Doctrine\ODM\MongoDB\Utility\EventDispatcher::dispatch has the more specific type 'Doctrine\Common\EventArgs', expecting 'object' as defined by Psr\EventDispatcher\EventDispatcherInterface::dispatch (see https://psalm.dev/140)
{
if (! $eventName) {
throw new InvalidArgumentException('Event name is required, none given.');
}

$this->eventManager->dispatchEvent($eventName, $event);

return $event;
}
}
Loading
Loading