diff --git a/config/services.php b/config/services.php index 7d1fefb..2841090 100644 --- a/config/services.php +++ b/config/services.php @@ -2,6 +2,7 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; +use Doctrine\ORM\Events; use DualMedia\DoctrineEventConverterBundle\EventSubscriber\DispatchingSubscriber; use DualMedia\DoctrineEventConverterBundle\Proxy\Generator; use DualMedia\DoctrineEventConverterBundle\Service\DelayableEventDispatcher; @@ -27,12 +28,35 @@ $services->set(VerifierService::class); - $services->set(DispatchingSubscriber::class) + $def = $services->set(DispatchingSubscriber::class) ->arg('$eventService', new Reference(EventService::class)) ->arg('$subEventService', new Reference(SubEventService::class)) ->arg('$verifierService', new Reference(VerifierService::class)) ->arg('$dispatcher', new Reference(DelayableEventDispatcher::class)) - ->tag('doctrine.event_subscriber'); + ->tag('doctrine.event_listener', [ + 'event' => Events::prePersist, + ]) + ->tag('doctrine.event_listener', [ + 'event' => Events::postPersist, + ]) + ->tag('doctrine.event_listener', [ + 'event' => Events::preUpdate, + ]) + ->tag('doctrine.event_listener', [ + 'event' => Events::postUpdate, + ]) + ->tag('doctrine.event_listener', [ + 'event' => Events::preRemove, + ]) + ->tag('doctrine.event_listener', [ + 'event' => Events::postRemove, + ]) + ->tag('doctrine.event_listener', [ + 'event' => Events::preFlush, + ]) + ->tag('doctrine.event_listener', [ + 'event' => Events::postFlush, + ]); $services->set(Generator::class) ->arg(0, '%kernel.cache_dir%/dm-smd-event-distributor-bundle') diff --git a/src/DependencyInjection/CompilerPass/EventDetectionCompilerPass.php b/src/DependencyInjection/CompilerPass/EventDetectionCompilerPass.php index 8cf7345..db698e6 100644 --- a/src/DependencyInjection/CompilerPass/EventDetectionCompilerPass.php +++ b/src/DependencyInjection/CompilerPass/EventDetectionCompilerPass.php @@ -265,8 +265,10 @@ public function process( $output = []; krsort($subEventConstruct, SORT_NUMERIC); // sort by priorities (200 -> 0 -> -200) - foreach ($subEventConstruct as $data) { - $output[] = $data; + foreach ($subEventConstruct as $prioritySortedList) { + foreach ($prioritySortedList as $data) { + $output[] = $data; + } } $subEventService->setArgument('$entries', $output); diff --git a/src/EventSubscriber/DispatchingSubscriber.php b/src/EventSubscriber/DispatchingSubscriber.php index 0a30e65..4f9e5fb 100644 --- a/src/EventSubscriber/DispatchingSubscriber.php +++ b/src/EventSubscriber/DispatchingSubscriber.php @@ -2,7 +2,6 @@ namespace DualMedia\DoctrineEventConverterBundle\EventSubscriber; -use Doctrine\Common\EventSubscriber; use Doctrine\Common\Util\ClassUtils; use Doctrine\ORM\Event\PostFlushEventArgs; use Doctrine\ORM\Event\PostPersistEventArgs; @@ -22,7 +21,7 @@ use DualMedia\DoctrineEventConverterBundle\Service\SubEventService; use DualMedia\DoctrineEventConverterBundle\Service\VerifierService; -class DispatchingSubscriber implements EventSubscriber +class DispatchingSubscriber { private bool $preFlush = false; @@ -48,25 +47,9 @@ public function __construct( ) { } - public function getSubscribedEvents(): array - { - return [ - Events::prePersist, - Events::postPersist, - Events::preUpdate, - Events::postUpdate, - Events::preRemove, - Events::postRemove, - Events::preFlush, - Events::postFlush, - ]; - } - public function prePersist( PrePersistEventArgs $args ): void { - echo 'DISPATCHED!!!!!!!!!!!'; - if ($args->getObject() instanceof EntityInterface) { $this->process(Events::prePersist, $args->getObject()); } @@ -196,7 +179,7 @@ private function subEvents( $subEvent->setEntity($entity) ->setChanges(array_intersect_key( $event->getChanges(), - $model->fieldList + $model->fields )) // save only fields that the event requested, ignore rest ->setEventType($event->getEventType()); diff --git a/src/Model/SubEvent.php b/src/Model/SubEvent.php index 5ab1b00..cce334f 100644 --- a/src/Model/SubEvent.php +++ b/src/Model/SubEvent.php @@ -16,13 +16,13 @@ class SubEvent { /** * @param bool $allMode If all the fields must be meeting the requirements of the event - * @param array $fieldList The fields that must be changed, null means that any change is required, 0 and 1 indexes match before/after + * @param array $fields The fields that must be changed, null means that any change is required, 0 and 1 indexes match before/after * @param array $requirements Required field states for this event to fire * @param list $types Event types in which this event may be triggered */ public function __construct( public readonly bool $allMode, - public readonly array $fieldList, + public readonly array $fields, public readonly array $requirements, public readonly array $types, public readonly bool $afterFlush, diff --git a/src/Service/SubEventService.php b/src/Service/SubEventService.php index 6146381..47d09eb 100644 --- a/src/Service/SubEventService.php +++ b/src/Service/SubEventService.php @@ -34,7 +34,7 @@ public function __construct( [$eventClass, $entities, $allMode, $fieldList, $requirements, $types, $afterFlush] = $entry; foreach ($entities as $entity) { - if (!array_key_exists($entity, $this->events[$entity])) { + if (!array_key_exists($entity, $this->events)) { $this->events[$entity] = []; } diff --git a/src/Service/VerifierService.php b/src/Service/VerifierService.php index 66d8311..83aedf5 100644 --- a/src/Service/VerifierService.php +++ b/src/Service/VerifierService.php @@ -15,7 +15,7 @@ public function __construct( } /** - * @param array> $changes + * @param array $changes */ public function validate( array $changes, @@ -49,7 +49,7 @@ public function validateRequirements( } /** - * @param array> $changes + * @param array $changes */ public function validateFields( array $changes, @@ -57,40 +57,50 @@ public function validateFields( string $type ): bool { if (!in_array($type, [Events::postUpdate, Events::preUpdate], true)) { - return false; + return true; } - if ($model->allMode && count(array_diff_key($model->fieldList, $changes))) { // Event contains keys that haven't changed + if ($model->allMode && count(array_diff_key($model->fields, $changes))) { // Event contains keys that haven't changed return false; - } elseif (!$model->allMode && !count(array_intersect_key($changes, $model->fieldList))) { // Event doesn't contain any of the required keys + } elseif (!$model->allMode && !count(array_intersect_key($changes, $model->fields))) { // Event doesn't contain any of the required keys return false; } $validFields = []; foreach ($changes as $field => $fields) { - if (!array_key_exists($field, $model->fieldList)) { - continue; - } elseif (null === ($modelWantedState = $model->fieldList[$field])) { - // if you set null instead of setting null for key 0 you're dumb and #wontfix - $validFields[$field] = true; + if (!array_key_exists($field, $model->fields)) { continue; } - $count = count($modelWantedState); - - if (1 === $count) { - $existingCounter = array_key_exists(0, $modelWantedState) ? 0 : 1; - $validFields[$field] = $this->equals($fields[$existingCounter], $modelWantedState[$existingCounter]); // @phpstan-ignore-line - } elseif (2 === $count) { - /** @var array{0: mixed, 1: mixed} $modelWantedState */ - $validFields[$field] = $this->equals($fields[0], $modelWantedState[0]) && $this->equals($fields[1], $modelWantedState[1]); - } + $validFields[$field] = null === ($modelWantedState = $model->fields[$field]) + || $this->validateField($fields, $modelWantedState); } $reduced = array_reduce($validFields, fn ($carry, $data) => $carry + ((int)$data)); - return !$model->allMode ? $reduced > 0 : $reduced === count($model->fieldList); + return !$model->allMode ? $reduced > 0 : $reduced === count($model->fields); + } + + /** + * @param array{0: mixed, 1: mixed} $changes + * @param array{0?: mixed, 1?: mixed} $wantedState + */ + public function validateField( + array $changes, + array $wantedState + ): bool { + $count = count($wantedState); + + if (1 === $count) { + $existingCounter = array_key_exists(0, $wantedState) ? 0 : 1; + return $this->equals($changes[$existingCounter], $wantedState[$existingCounter]); // @phpstan-ignore-line + } elseif (2 === $count) { + /** @var array{0: mixed, 1: mixed} $wantedState */ + return $this->equals($changes[0], $wantedState[0]) && $this->equals($changes[1], $wantedState[1]); + } + + return false; } /** diff --git a/tests/Fixtures/Enum/BackedIntEnum.php b/tests/Fixtures/Enum/BackedIntEnum.php new file mode 100644 index 0000000..17dfe34 --- /dev/null +++ b/tests/Fixtures/Enum/BackedIntEnum.php @@ -0,0 +1,8 @@ +service = $this->createRealMockedServiceInstance(EventService::class, [ - 'entries' => [], + 'entries' => [ + [ + ComplexEntityEvent::class, + [ComplexEntity::class], + Events::prePersist, + true, + ], + ], ]); } public function test(): void { - $this->assertTrue(true); + $this->assertNotEmpty( + $events = $this->service->get(Events::prePersist, ComplexEntity::class), + 'There should be exactly 1 event for specified inputs' + ); + $this->assertCount(1, $events, 'There should be exactly 1 event for specified inputs'); + + $this->assertEquals( + ComplexEntityEvent::class, + $events[0]->eventClass + ); + $this->assertTrue($events[0]->afterFlush); + } + + public function testNotFound(): void + { + $this->assertEmpty( + $this->service->get(Events::postRemove, Item::class), + 'No events should be returned from service' + ); } } diff --git a/tests/Service/SubEventServiceTest.php b/tests/Service/SubEventServiceTest.php new file mode 100644 index 0000000..2e03d7a --- /dev/null +++ b/tests/Service/SubEventServiceTest.php @@ -0,0 +1,81 @@ +service = $this->createRealMockedServiceInstance(SubEventService::class, [ + 'entries' => [ + [ + ComplexEntityEvent::class, + [ComplexEntity::class], + false, + [ + 'stuff' => null, + ], + [ + 'requirement' => 42, + ], + [ + Events::prePersist, + ], + true, + ], + ], + ]); + } + + public function test(): void + { + $this->assertNotEmpty( + $events = $this->service->get(ComplexEntity::class), + 'There should be exactly 1 event for specified inputs' + ); + $this->assertCount(1, $events, 'There should be exactly 1 event for specified inputs'); + + $this->assertArrayHasKey( + ComplexEntityEvent::class, + $events + ); + $this->assertCount(1, $events[ComplexEntityEvent::class]); + $event = $events[ComplexEntityEvent::class][0]; + + $this->assertFalse( + $event->allMode + ); + $this->assertEquals([ + 'stuff' => null, + ], $event->fields); + $this->assertEquals([ + 'requirement' => 42, + ], $event->requirements); + $this->assertEquals([ + Events::prePersist, + ], $event->types); + $this->assertTrue( + $event->afterFlush + ); + } + + public function testEmpty(): void + { + $this->assertEmpty( + $this->service->get(Item::class), + 'There should be sub events for specified entity' + ); + } +} \ No newline at end of file diff --git a/tests/Service/VerifierServiceTest.php b/tests/Service/VerifierServiceTest.php new file mode 100644 index 0000000..e252c83 --- /dev/null +++ b/tests/Service/VerifierServiceTest.php @@ -0,0 +1,51 @@ +service = $this->createRealPartialMockedServiceInstance(VerifierService::class, []); + } + + #[TestWith([true, Events::prePersist, [Events::prePersist]])] + #[TestWith([true, Events::prePersist, [Events::prePersist, Events::preUpdate]])] + #[TestWith([false, Events::prePersist, [Events::preUpdate]])] + public function testValidateType( + bool $result, + string $type, + array $types + ): void { + $this->assertEquals( + $result, + $this->service->validateType($type, $types) + ); + } + + #[TestWith([true, 10, 10])] + #[TestWith([false, 5, 10])] + #[TestWith([true, 5, BackedIntEnum::Is5])] + public function testEquals( + bool $result, + mixed $known, + mixed $expected + ): void { + $this->assertEquals( + $result, + $this->service->equals($known, $expected) + ); + } +}