├── packages
├── domain-event
│ ├── .gitignore
│ ├── src
│ │ ├── Exception
│ │ │ ├── ExceptionInterface.php
│ │ │ ├── InvalidOperationException.php
│ │ │ ├── LogicException.php
│ │ │ ├── SafeguardTriggeredException.php
│ │ │ ├── RuntimeException.php
│ │ │ ├── FlushNotAllowedException.php
│ │ │ └── UndispatchedEventsException.php
│ │ ├── DomainEventAwareObjectManager.php
│ │ ├── DomainEventAwareEntityManagerInterface.php
│ │ ├── Event
│ │ │ ├── DomainEventImmediateDispatchEvent.php
│ │ │ ├── DomainEventPostFlushDispatchEvent.php
│ │ │ └── DomainEventPreFlushDispatchEvent.php
│ │ ├── Contracts
│ │ │ └── DomainEventAwareEntityManagerInterface.php
│ │ ├── ImmediateDomainEventDispatcherInstaller.php
│ │ ├── Doctrine
│ │ │ ├── DomainEventReaper.php
│ │ │ ├── DoctrineEventListener.php
│ │ │ └── AbstractManagerRegistryDecorator.php
│ │ ├── DependencyInjection
│ │ │ ├── Constants.php
│ │ │ └── CompilerPass
│ │ │ │ ├── ProfilerWorkaroundPass.php
│ │ │ │ └── EntityManagerDecoratorPass.php
│ │ ├── EventDispatcher
│ │ │ ├── EventDispatchers.php
│ │ │ └── ImmediateEventDispatchingDomainEventDispatcher.php
│ │ ├── DomainEventAwareManagerRegistry.php
│ │ ├── RekalogikaDomainEventBundle.php
│ │ ├── DomainEventManagerInterface.php
│ │ └── Model
│ │ │ ├── DomainEventStore.php
│ │ │ └── TransactionAwareDomainEventStore.php
│ ├── LICENSE
│ ├── composer.json
│ └── config
│ │ └── debug.php
├── domain-event-outbox
│ ├── .gitignore
│ ├── examples
│ │ ├── messenger-outbox.yaml
│ │ └── messenger-default.yaml
│ ├── src
│ │ ├── Exception
│ │ │ ├── ExceptionInterface.php
│ │ │ ├── LogicException.php
│ │ │ ├── RuntimeException.php
│ │ │ └── UnserializeFailureException.php
│ │ ├── Entity
│ │ │ ├── ErrorEvent.php
│ │ │ └── OutboxMessage.php
│ │ ├── OutboxReaderFactoryInterface.php
│ │ ├── MessagePreparerInterface.php
│ │ ├── Stamp
│ │ │ ├── UserIdentifierStamp.php
│ │ │ └── ObjectManagerNameStamp.php
│ │ ├── Message
│ │ │ └── MessageRelayStartMessage.php
│ │ ├── MessageRelayInterface.php
│ │ ├── MessageHandler
│ │ │ └── MessageRelayStartMessageHandler.php
│ │ ├── RekalogikaDomainEventOutboxBundle.php
│ │ ├── DependencyInjection
│ │ │ ├── CompilerPass
│ │ │ │ ├── RemoveUnusedPass.php
│ │ │ │ └── OutboxEntityPass.php
│ │ │ ├── Configuration.php
│ │ │ └── RekalogikaDomainEventOutboxExtension.php
│ │ ├── MessageRelay
│ │ │ └── MessageRelayAll.php
│ │ ├── EventListener
│ │ │ ├── RenameTableListener.php
│ │ │ └── DomainEventDispatchListener.php
│ │ ├── Schedule
│ │ │ └── MessageRelayProvider.php
│ │ ├── MessagePreparer
│ │ │ ├── ChainMessagePreparer.php
│ │ │ └── UserIdentifierMessagePreparer.php
│ │ ├── OutboxReaderInterface.php
│ │ ├── Doctrine
│ │ │ ├── OutboxReaderFactory.php
│ │ │ └── EntityManagerOutboxReader.php
│ │ └── Command
│ │ │ └── MessageRelayCommand.php
│ ├── config
│ │ └── debug.php
│ ├── LICENSE
│ ├── composer.json
│ └── README.md
└── domain-event-contracts
│ ├── .gitignore
│ ├── README.md
│ ├── src
│ ├── EquatableDomainEventInterface.php
│ ├── Attribute
│ │ ├── AsPreFlushDomainEventListener.php
│ │ ├── AsImmediateDomainEventListener.php
│ │ ├── AsPostFlushDomainEventListener.php
│ │ └── AsPublishedDomainEventListener.php
│ ├── DomainEventEmitterInterface.php
│ ├── DomainEventImmediateDispatcher.php
│ └── DomainEventEmitterTrait.php
│ ├── composer.json
│ └── LICENSE
├── tests
├── Framework
│ ├── Resources
│ │ └── config
│ │ │ ├── packages
│ │ │ ├── lock.yaml
│ │ │ ├── routing.yaml
│ │ │ ├── web_profiler.yaml
│ │ │ ├── debug.yaml
│ │ │ ├── framework.yaml
│ │ │ ├── messenger.yaml
│ │ │ ├── monolog.yaml
│ │ │ ├── doctrine.yaml
│ │ │ └── security.yaml
│ │ │ ├── routes.yaml
│ │ │ ├── routes
│ │ │ └── web_profiler.yaml
│ │ │ └── services_test.php
│ ├── Event
│ │ ├── BookChanged.php
│ │ ├── BookChecked.php
│ │ ├── BookCreated.php
│ │ ├── BookRemoved.php
│ │ ├── ReviewChanged.php
│ │ ├── ReviewCreated.php
│ │ ├── ReviewRemoved.php
│ │ ├── BookDummyMethodCalled.php
│ │ ├── BookDummyMethodForFlushCalled.php
│ │ ├── BookDummyMethodForInfiniteLoopCalled.php
│ │ ├── BookDummyMethodForNestedRecordEventCalled.php
│ │ ├── BookDummyChanged.php
│ │ ├── BookReviewAdded.php
│ │ ├── BookReviewRemoved.php
│ │ ├── AbstractBookEvent.php
│ │ └── AbstractReviewEvent.php
│ ├── Event2
│ │ ├── PostChanged.php
│ │ ├── PostCreated.php
│ │ ├── PostRemoved.php
│ │ ├── AbstractPostEvent.php
│ │ └── AbstractCommentEvent.php
│ ├── Tests
│ │ ├── ResetTest.php
│ │ ├── IntegrationTest.php
│ │ ├── EquatableEventTest.php
│ │ ├── OutboxSetupTest.php
│ │ ├── PreFlushTest.php
│ │ ├── RemoveTest.php
│ │ ├── DomainEventTestCase.php
│ │ └── BasicDomainEventTest.php
│ ├── EventListener
│ │ ├── BookDummyMethodCalledListener.php
│ │ ├── BookDummyMethodForFlushListener.php
│ │ ├── BookDummyMethodForInfiniteLoopCalledListener.php
│ │ ├── BookDummyChangedListener.php
│ │ ├── BookEventImmediateListener.php
│ │ ├── BookDummyMethodForNestedRecordEventListener.php
│ │ ├── BookEventPreFlushListener.php
│ │ ├── BookEventPostFlushListener.php
│ │ ├── BookEventEventBusListener.php
│ │ └── PostEventEventBusListener.php
│ ├── Entity
│ │ ├── User.php
│ │ └── Review.php
│ ├── Repository
│ │ ├── ReviewRepository.php
│ │ └── BookRepository.php
│ ├── Security
│ │ └── AccessTokenHandler.php
│ ├── Entity2
│ │ ├── Comment.php
│ │ └── Post.php
│ └── Kernel.php
├── Integration
│ ├── Event
│ │ ├── EntityCreated.php
│ │ ├── EntityRemoved.php
│ │ ├── EntityNameChanged.php
│ │ ├── NonEquatableEvent.php
│ │ ├── AbstractEntityDomainEvent.php
│ │ └── EquatableEvent.php
│ ├── Service
│ │ └── DomainEventEmitterCollectorStub.phpx
│ ├── EventListener
│ │ ├── EquatableEventListener.php
│ │ ├── DomainEventListener.php
│ │ └── FlushingDomainEventListener.php
│ ├── Factory.php
│ └── Model
│ │ └── Entity.php
└── bin
│ └── console
├── .gitignore
├── .github
├── release.yml
├── dependabot.yml
└── workflows
│ ├── php.yml
│ └── split.yml
├── .phive
└── phars.xml
├── monorepo-builder.php
├── phpunit.xml.dist
├── LICENSE
├── phpstan.neon.dist
├── .php-cs-fixer.dist.php
├── Makefile
├── rector.php
├── psalm.xml
└── composer.json
/packages/domain-event/.gitignore:
--------------------------------------------------------------------------------
1 | composer.lock
2 | var
--------------------------------------------------------------------------------
/packages/domain-event-outbox/.gitignore:
--------------------------------------------------------------------------------
1 | composer.lock
2 |
--------------------------------------------------------------------------------
/packages/domain-event-contracts/.gitignore:
--------------------------------------------------------------------------------
1 | composer.lock
2 |
--------------------------------------------------------------------------------
/tests/Framework/Resources/config/packages/lock.yaml:
--------------------------------------------------------------------------------
1 | framework:
2 | lock: flock
3 |
--------------------------------------------------------------------------------
/tests/Framework/Resources/config/routes.yaml:
--------------------------------------------------------------------------------
1 | _app:
2 | resource: 'routes/*.yaml'
3 | type: yaml
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | composer.lock
2 | vendor/
3 | .phpunit.cache
4 | .php-cs-fixer.cache
5 | tools
6 | var/
7 | rector.log
--------------------------------------------------------------------------------
/.github/release.yml:
--------------------------------------------------------------------------------
1 | changelog:
2 | labels:
3 | - "*"
4 | exclude:
5 | labels:
6 | - dependencies
7 | - minor
8 | - release
9 |
--------------------------------------------------------------------------------
/tests/Framework/Resources/config/packages/routing.yaml:
--------------------------------------------------------------------------------
1 | framework:
2 | router:
3 | utf8: true
4 | resource: "%kernel.project_dir%/tests/Framework/Resources/config/routes.yaml"
--------------------------------------------------------------------------------
/.phive/phars.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/packages/domain-event-outbox/examples/messenger-outbox.yaml:
--------------------------------------------------------------------------------
1 | framework:
2 | messenger:
3 | buses:
4 | rekalogika.domain_event.bus:
5 | default_middleware:
6 | allow_no_handlers: true
7 |
8 |
--------------------------------------------------------------------------------
/tests/Framework/Resources/config/packages/web_profiler.yaml:
--------------------------------------------------------------------------------
1 | web_profiler:
2 | toolbar: true
3 | intercept_redirects: false
4 |
5 | framework:
6 | profiler:
7 | only_exceptions: false
8 | collect_serializer_data: true
9 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "composer"
4 | directory: "/"
5 | schedule:
6 | interval: "weekly"
7 | - package-ecosystem: "github-actions"
8 | directory: "/"
9 | schedule:
10 | interval: "weekly"
11 |
--------------------------------------------------------------------------------
/tests/Framework/Resources/config/packages/debug.yaml:
--------------------------------------------------------------------------------
1 | debug:
2 | # Forwards VarDumper Data clones to a centralized server allowing to inspect dumps on CLI or in your browser.
3 | # See the "server:dump" command to start a new server.
4 | dump_destination: "tcp://%env(VAR_DUMPER_SERVER)%"
5 |
--------------------------------------------------------------------------------
/tests/Framework/Resources/config/routes/web_profiler.yaml:
--------------------------------------------------------------------------------
1 | web_profiler_wdt:
2 | resource: "@WebProfilerBundle/Resources/config/routing/wdt.xml"
3 | prefix: /_wdt
4 |
5 | web_profiler_profiler:
6 | resource: "@WebProfilerBundle/Resources/config/routing/profiler.xml"
7 | prefix: /_profiler
8 |
--------------------------------------------------------------------------------
/tests/Framework/Resources/config/packages/framework.yaml:
--------------------------------------------------------------------------------
1 | parameters:
2 | kernel.secret: test
3 |
4 | framework:
5 | test: true
6 | http_method_override: false
7 | handle_all_throwables: true
8 | php_errors:
9 | log: true
10 | uid:
11 | default_uuid_version: 7
12 | time_based_uuid_version: 7
13 | scheduler:
14 | enabled: true
15 |
--------------------------------------------------------------------------------
/tests/Framework/Resources/config/packages/messenger.yaml:
--------------------------------------------------------------------------------
1 | framework:
2 | messenger:
3 | transports:
4 | async: "in-memory://"
5 |
6 | default_bus: messenger.bus.default
7 |
8 | buses:
9 | messenger.bus.default: null
10 | rekalogika.domain_event.bus:
11 | default_middleware:
12 | allow_no_handlers: true
13 |
14 | routing:
15 | 'Rekalogika\DomainEvent\Tests\*': async
--------------------------------------------------------------------------------
/tests/Framework/Event/BookChanged.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Tests\Framework\Event;
15 |
16 | final class BookChanged extends AbstractBookEvent {}
17 |
--------------------------------------------------------------------------------
/tests/Framework/Event/BookChecked.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Tests\Framework\Event;
15 |
16 | final class BookChecked extends AbstractBookEvent {}
17 |
--------------------------------------------------------------------------------
/tests/Framework/Event/BookCreated.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Tests\Framework\Event;
15 |
16 | final class BookCreated extends AbstractBookEvent {}
17 |
--------------------------------------------------------------------------------
/tests/Framework/Event/BookRemoved.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Tests\Framework\Event;
15 |
16 | final class BookRemoved extends AbstractBookEvent {}
17 |
--------------------------------------------------------------------------------
/tests/Framework/Event/ReviewChanged.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Tests\Framework\Event;
15 |
16 | final class ReviewChanged extends AbstractReviewEvent {}
17 |
--------------------------------------------------------------------------------
/tests/Framework/Event/ReviewCreated.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Tests\Framework\Event;
15 |
16 | final class ReviewCreated extends AbstractReviewEvent {}
17 |
--------------------------------------------------------------------------------
/tests/Framework/Event/ReviewRemoved.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Tests\Framework\Event;
15 |
16 | final class ReviewRemoved extends AbstractReviewEvent {}
17 |
--------------------------------------------------------------------------------
/tests/Framework/Event2/PostChanged.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Tests\Framework\Event2;
15 |
16 | final class PostChanged extends AbstractPostEvent {}
17 |
--------------------------------------------------------------------------------
/tests/Framework/Event2/PostCreated.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Tests\Framework\Event2;
15 |
16 | final class PostCreated extends AbstractPostEvent {}
17 |
--------------------------------------------------------------------------------
/tests/Framework/Event2/PostRemoved.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Tests\Framework\Event2;
15 |
16 | final class PostRemoved extends AbstractPostEvent {}
17 |
--------------------------------------------------------------------------------
/packages/domain-event/src/Exception/ExceptionInterface.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Exception;
15 |
16 | interface ExceptionInterface extends \Throwable {}
17 |
--------------------------------------------------------------------------------
/tests/Integration/Event/EntityCreated.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Tests\Integration\Event;
15 |
16 | final class EntityCreated extends AbstractEntityDomainEvent {}
17 |
--------------------------------------------------------------------------------
/tests/Integration/Event/EntityRemoved.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Tests\Integration\Event;
15 |
16 | final class EntityRemoved extends AbstractEntityDomainEvent {}
17 |
--------------------------------------------------------------------------------
/packages/domain-event-outbox/src/Exception/ExceptionInterface.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Outbox\Exception;
15 |
16 | interface ExceptionInterface extends \Throwable {}
17 |
--------------------------------------------------------------------------------
/packages/domain-event/src/Exception/InvalidOperationException.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Exception;
15 |
16 | class InvalidOperationException extends LogicException {}
17 |
--------------------------------------------------------------------------------
/tests/Framework/Event/BookDummyMethodCalled.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Tests\Framework\Event;
15 |
16 | final class BookDummyMethodCalled extends AbstractBookEvent {}
17 |
--------------------------------------------------------------------------------
/tests/Integration/Event/EntityNameChanged.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Tests\Integration\Event;
15 |
16 | final class EntityNameChanged extends AbstractEntityDomainEvent {}
17 |
--------------------------------------------------------------------------------
/tests/Integration/Event/NonEquatableEvent.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Tests\Integration\Event;
15 |
16 | final class NonEquatableEvent extends AbstractEntityDomainEvent {}
17 |
--------------------------------------------------------------------------------
/packages/domain-event/src/Exception/LogicException.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Exception;
15 |
16 | class LogicException extends \LogicException implements ExceptionInterface {}
17 |
--------------------------------------------------------------------------------
/packages/domain-event/src/Exception/SafeguardTriggeredException.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Exception;
15 |
16 | class SafeguardTriggeredException extends RuntimeException {}
17 |
--------------------------------------------------------------------------------
/packages/domain-event/src/Exception/RuntimeException.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Exception;
15 |
16 | class RuntimeException extends \RuntimeException implements ExceptionInterface {}
17 |
--------------------------------------------------------------------------------
/tests/Framework/Event/BookDummyMethodForFlushCalled.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Tests\Framework\Event;
15 |
16 | final class BookDummyMethodForFlushCalled extends AbstractBookEvent {}
17 |
--------------------------------------------------------------------------------
/packages/domain-event-outbox/src/Exception/LogicException.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Outbox\Exception;
15 |
16 | class LogicException extends \LogicException implements ExceptionInterface {}
17 |
--------------------------------------------------------------------------------
/packages/domain-event-outbox/src/Exception/RuntimeException.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Outbox\Exception;
15 |
16 | class RuntimeException extends \RuntimeException implements ExceptionInterface {}
17 |
--------------------------------------------------------------------------------
/tests/Framework/Event/BookDummyMethodForInfiniteLoopCalled.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Tests\Framework\Event;
15 |
16 | final class BookDummyMethodForInfiniteLoopCalled extends AbstractBookEvent {}
17 |
--------------------------------------------------------------------------------
/packages/domain-event-outbox/src/Entity/ErrorEvent.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Outbox\Entity;
15 |
16 | /**
17 | * A sentinel event that represents an error in the outbox.
18 | */
19 | class ErrorEvent {}
20 |
--------------------------------------------------------------------------------
/tests/Framework/Event/BookDummyMethodForNestedRecordEventCalled.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Tests\Framework\Event;
15 |
16 | final class BookDummyMethodForNestedRecordEventCalled extends AbstractBookEvent {}
17 |
--------------------------------------------------------------------------------
/packages/domain-event/src/DomainEventAwareObjectManager.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent;
15 |
16 | use Doctrine\Persistence\ObjectManager;
17 |
18 | interface DomainEventAwareObjectManager extends
19 | ObjectManager,
20 | DomainEventManagerInterface {}
21 |
--------------------------------------------------------------------------------
/tests/Framework/Resources/config/packages/monolog.yaml:
--------------------------------------------------------------------------------
1 | monolog:
2 | channels:
3 | - deprecation # Deprecations are logged in the dedicated "deprecation" channel when it exists
4 | handlers:
5 | main:
6 | type: fingers_crossed
7 | action_level: error
8 | handler: nested
9 | excluded_http_codes: [404, 405]
10 | channels: ["!event"]
11 | formatter: monolog.formatter.json
12 | nested:
13 | type: stream
14 | path: "%kernel.logs_dir%/%kernel.environment%.log"
15 | level: debug
16 |
17 |
--------------------------------------------------------------------------------
/packages/domain-event/src/DomainEventAwareEntityManagerInterface.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent;
15 |
16 | use Doctrine\ORM\EntityManagerInterface;
17 |
18 | interface DomainEventAwareEntityManagerInterface extends
19 | EntityManagerInterface,
20 | DomainEventAwareObjectManager {}
21 |
--------------------------------------------------------------------------------
/packages/domain-event-contracts/README.md:
--------------------------------------------------------------------------------
1 | # rekalogika/domain-event-contracts
2 |
3 | Contains interfaces, traits, attributes, and nominal classes to be used by
4 | domain objects utilizing the `rekalogika/domain-event` framework.
5 |
6 | ## Documentation
7 |
8 | [rekalogika.dev/domain-event](https://rekalogika.dev/domain-event)
9 |
10 | ## License
11 |
12 | MIT
13 |
14 | ## Contributing
15 |
16 | The `rekalogika/domain-event-contracts` repository is a read-only repo split
17 | from the main repo. Issues and pull requests should be submitted to the
18 | [rekalogika/domain-event-src](https://github.com/rekalogika/domain-event-src)
19 | monorepo.
20 |
--------------------------------------------------------------------------------
/packages/domain-event-outbox/src/OutboxReaderFactoryInterface.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Outbox;
15 |
16 | /**
17 | * Creates outbox readers.
18 | */
19 | interface OutboxReaderFactoryInterface
20 | {
21 | public function createOutboxReader(string $managerName): OutboxReaderInterface;
22 | }
23 |
--------------------------------------------------------------------------------
/packages/domain-event-outbox/config/debug.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | use Doctrine\ORM\Mapping\Driver\AttributeDriver;
15 | use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
16 |
17 | return static function (ContainerConfigurator $containerConfigurator): void {
18 | $services = $containerConfigurator->services();
19 | };
20 |
--------------------------------------------------------------------------------
/packages/domain-event/src/Exception/FlushNotAllowedException.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Exception;
15 |
16 | class FlushNotAllowedException extends LogicException
17 | {
18 | public function __construct()
19 | {
20 | parent::__construct('"flush()" is not allowed inside a pre-flush domain event listener.');
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/packages/domain-event-contracts/src/EquatableDomainEventInterface.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\Contracts\DomainEvent;
15 |
16 | /**
17 | * Provides a method that can be used to determine whether two events should be
18 | * considered equal.
19 | */
20 | interface EquatableDomainEventInterface
21 | {
22 | public function getSignature(): string;
23 | }
24 |
--------------------------------------------------------------------------------
/tests/bin/console:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 |
10 | *
11 | * For the full copyright and license information, please view the LICENSE file
12 | * that was distributed with this source code.
13 | */
14 |
15 | use Rekalogika\DomainEvent\Tests\Framework\Kernel;
16 | use Symfony\Bundle\FrameworkBundle\Console\Application;
17 |
18 | require_once dirname(__DIR__).'/../vendor/autoload_runtime.php';
19 |
20 | return function (array $context) {
21 | $kernel = new Kernel();
22 |
23 | return new Application($kernel);
24 | };
25 |
--------------------------------------------------------------------------------
/packages/domain-event/src/Event/DomainEventImmediateDispatchEvent.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Event;
15 |
16 | class DomainEventImmediateDispatchEvent
17 | {
18 | final public function __construct(private readonly object $domainEvent) {}
19 |
20 | public function getDomainEvent(): object
21 | {
22 | return $this->domainEvent;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/packages/domain-event-outbox/src/Exception/UnserializeFailureException.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Outbox\Exception;
15 |
16 | class UnserializeFailureException extends RuntimeException
17 | {
18 | public function __construct(string $serializedText)
19 | {
20 | parent::__construct(\sprintf('Failed to unserialize serialized event object: "%s"', $serializedText));
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/tests/Integration/Event/AbstractEntityDomainEvent.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Tests\Integration\Event;
15 |
16 | use Rekalogika\DomainEvent\Tests\Integration\Model\Entity;
17 |
18 | abstract class AbstractEntityDomainEvent
19 | {
20 | final public function __construct(private readonly Entity $entity) {}
21 |
22 | public function getEntity(): Entity
23 | {
24 | return $this->entity;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/packages/domain-event-outbox/src/MessagePreparerInterface.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Outbox;
15 |
16 | use Symfony\Component\Messenger\Envelope;
17 |
18 | /**
19 | * Prepares the message before it is saved to the outbox table. Returns null if
20 | * the message should not be delivered to the outbox.
21 | */
22 | interface MessagePreparerInterface
23 | {
24 | public function prepareMessage(Envelope $envelope): ?Envelope;
25 | }
26 |
--------------------------------------------------------------------------------
/tests/Integration/Event/EquatableEvent.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Tests\Integration\Event;
15 |
16 | use Rekalogika\Contracts\DomainEvent\EquatableDomainEventInterface;
17 |
18 | final class EquatableEvent extends AbstractEntityDomainEvent implements
19 | EquatableDomainEventInterface
20 | {
21 | #[\Override]
22 | public function getSignature(): string
23 | {
24 | return sha1(serialize($this));
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/packages/domain-event-outbox/src/Stamp/UserIdentifierStamp.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Outbox\Stamp;
15 |
16 | use Symfony\Component\Messenger\Stamp\StampInterface;
17 |
18 | final class UserIdentifierStamp implements StampInterface
19 | {
20 | public function __construct(private readonly string $userIdentifier) {}
21 |
22 | public function getUserIdentifier(): string
23 | {
24 | return $this->userIdentifier;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/monorepo-builder.php:
--------------------------------------------------------------------------------
1 | packageDirectories([__DIR__ . '/packages']);
12 | $mbConfig->defaultBranch('main');
13 | $mbConfig->disableDefaultWorkers();
14 |
15 | $mbConfig->workers([
16 | UpdateReplaceReleaseWorker::class,
17 | SetCurrentMutualDependenciesReleaseWorker::class,
18 | UpdateBranchAliasReleaseWorker::class,
19 | ]);
20 | };
21 |
--------------------------------------------------------------------------------
/packages/domain-event-contracts/src/Attribute/AsPreFlushDomainEventListener.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\Contracts\DomainEvent\Attribute;
15 |
16 | #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
17 | class AsPreFlushDomainEventListener
18 | {
19 | public function __construct(
20 | public ?string $event = null,
21 | public ?string $method = null,
22 | public int $priority = 0,
23 | ) {}
24 | }
25 |
--------------------------------------------------------------------------------
/packages/domain-event-contracts/src/Attribute/AsImmediateDomainEventListener.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\Contracts\DomainEvent\Attribute;
15 |
16 | #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
17 | class AsImmediateDomainEventListener
18 | {
19 | public function __construct(
20 | public ?string $event = null,
21 | public ?string $method = null,
22 | public int $priority = 0,
23 | ) {}
24 | }
25 |
--------------------------------------------------------------------------------
/packages/domain-event-contracts/src/Attribute/AsPostFlushDomainEventListener.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\Contracts\DomainEvent\Attribute;
15 |
16 | #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
17 | class AsPostFlushDomainEventListener
18 | {
19 | public function __construct(
20 | public ?string $event = null,
21 | public ?string $method = null,
22 | public int $priority = 0,
23 | ) {}
24 | }
25 |
--------------------------------------------------------------------------------
/packages/domain-event-contracts/src/Attribute/AsPublishedDomainEventListener.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\Contracts\DomainEvent\Attribute;
15 |
16 | #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
17 | class AsPublishedDomainEventListener
18 | {
19 | public function __construct(
20 | public ?string $event = null,
21 | public ?string $method = null,
22 | public int $priority = 0,
23 | ) {}
24 | }
25 |
--------------------------------------------------------------------------------
/packages/domain-event-outbox/src/Stamp/ObjectManagerNameStamp.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Outbox\Stamp;
15 |
16 | use Symfony\Component\Messenger\Stamp\StampInterface;
17 |
18 | final class ObjectManagerNameStamp implements StampInterface
19 | {
20 | public function __construct(private readonly string $objectManagerName) {}
21 |
22 | public function getObjectManagerName(): string
23 | {
24 | return $this->objectManagerName;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/packages/domain-event-outbox/src/Message/MessageRelayStartMessage.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Outbox\Message;
15 |
16 | /**
17 | * Delivered to the message bus to instruct its handler to start the message
18 | * relay
19 | */
20 | class MessageRelayStartMessage
21 | {
22 | public function __construct(private readonly string $managerName) {}
23 |
24 | public function getManagerName(): string
25 | {
26 | return $this->managerName;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/packages/domain-event/src/Contracts/DomainEventAwareEntityManagerInterface.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Contracts;
15 |
16 | use Rekalogika\DomainEvent\DomainEventAwareEntityManagerInterface as DomainEventDomainEventAwareEntityManagerInterface;
17 |
18 | /**
19 | * @deprecated Please use Rekalogika\DomainEvent\DomainEventAwareEntityManagerInterface instead
20 | */
21 | interface DomainEventAwareEntityManagerInterface extends DomainEventDomainEventAwareEntityManagerInterface {}
22 |
--------------------------------------------------------------------------------
/tests/Framework/Event/BookDummyChanged.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Tests\Framework\Event;
15 |
16 | final class BookDummyChanged
17 | {
18 | public function __construct(
19 | private readonly string $previous,
20 | private readonly string $now,
21 | ) {}
22 |
23 | public function getPrevious(): string
24 | {
25 | return $this->previous;
26 | }
27 |
28 | public function getNow(): string
29 | {
30 | return $this->now;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 | tests
15 |
16 |
17 |
18 |
19 | packages/domain-event/src
20 | packages/domain-event-contracts/src
21 |
22 |
23 |
--------------------------------------------------------------------------------
/tests/Framework/Tests/ResetTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Tests\Framework\Tests;
15 |
16 | final class ResetTest extends DomainEventTestCase
17 | {
18 | public function testEntityManagerReset(): void
19 | {
20 | $entitymanager = static::getEntityManager();
21 | $entitymanager->reset();
22 | }
23 |
24 | public function testManagerRegistryResetManager(): void
25 | {
26 | $managerRegistry = static::getManagerRegistry();
27 | $managerRegistry->resetManager();
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/packages/domain-event/src/Exception/UndispatchedEventsException.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Exception;
15 |
16 | use Rekalogika\DomainEvent\Model\DomainEventStore;
17 |
18 | class UndispatchedEventsException extends LogicException
19 | {
20 | public function __construct(DomainEventStore $preFlushEvents, DomainEventStore $postFlushEvents)
21 | {
22 | $num = \count($preFlushEvents) + \count($postFlushEvents);
23 |
24 | parent::__construct(\sprintf('There are still %d undispatched domain events. If you disable autodispatch, you have to dispatch them manually or clear them.', $num));
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/packages/domain-event-outbox/src/MessageRelayInterface.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Outbox;
15 |
16 | /**
17 | * Get the messages from the outbox and sends them to the message bus.
18 | */
19 | interface MessageRelayInterface
20 | {
21 | /**
22 | * Relays messages from the outbox to the message bus.
23 | *
24 | * @param string $managerName The name of the entity manager to relay
25 | * messages from.
26 | * @return int The amount of messages cleared from the outbox, not
27 | * necessarily sent to the event bus.
28 | */
29 | public function relayMessages(string $managerName): int;
30 | }
31 |
--------------------------------------------------------------------------------
/packages/domain-event-contracts/src/DomainEventEmitterInterface.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\Contracts\DomainEvent;
15 |
16 | /**
17 | * Interface implemented by classes that records domain events, typically your
18 | * domain entities.
19 | */
20 | interface DomainEventEmitterInterface
21 | {
22 | /**
23 | * Returns all domain events recorded by the entity, and delete them.
24 | *
25 | * @return array
26 | */
27 | public function popRecordedEvents(): array;
28 |
29 | /**
30 | * Called when the object is removed from the persistence layer.
31 | */
32 | public function __remove(): void;
33 | }
34 |
--------------------------------------------------------------------------------
/tests/Framework/Event/BookReviewAdded.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Tests\Framework\Event;
15 |
16 | use Rekalogika\DomainEvent\Tests\Framework\Entity\Book;
17 | use Rekalogika\DomainEvent\Tests\Framework\Entity\Review;
18 | use Symfony\Component\Uid\Uuid;
19 |
20 | final class BookReviewAdded extends AbstractBookEvent
21 | {
22 | private readonly Uuid $reviewId;
23 |
24 | public function __construct(Book $book, Review $review)
25 | {
26 | parent::__construct($book);
27 | $this->reviewId = $review->getId();
28 | }
29 |
30 | public function getReviewId(): Uuid
31 | {
32 | return $this->reviewId;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/tests/Framework/Event/BookReviewRemoved.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Tests\Framework\Event;
15 |
16 | use Rekalogika\DomainEvent\Tests\Framework\Entity\Book;
17 | use Rekalogika\DomainEvent\Tests\Framework\Entity\Review;
18 | use Symfony\Component\Uid\Uuid;
19 |
20 | final class BookReviewRemoved extends AbstractBookEvent
21 | {
22 | private readonly Uuid $reviewId;
23 |
24 | public function __construct(Book $book, Review $review)
25 | {
26 | parent::__construct($book);
27 | $this->reviewId = $review->getId();
28 | }
29 |
30 | public function getReviewId(): Uuid
31 | {
32 | return $this->reviewId;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/packages/domain-event/src/Event/DomainEventPostFlushDispatchEvent.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Event;
15 |
16 | use Rekalogika\DomainEvent\DomainEventAwareObjectManager;
17 |
18 | class DomainEventPostFlushDispatchEvent
19 | {
20 | final public function __construct(
21 | private readonly DomainEventAwareObjectManager $objectManager,
22 | private readonly object $domainEvent,
23 | ) {}
24 |
25 | public function getDomainEvent(): object
26 | {
27 | return $this->domainEvent;
28 | }
29 |
30 | public function getObjectManager(): DomainEventAwareObjectManager
31 | {
32 | return $this->objectManager;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/packages/domain-event/src/Event/DomainEventPreFlushDispatchEvent.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Event;
15 |
16 | use Rekalogika\DomainEvent\DomainEventAwareObjectManager;
17 |
18 | class DomainEventPreFlushDispatchEvent
19 | {
20 | final public function __construct(
21 | private readonly DomainEventAwareObjectManager $objectManager,
22 | private readonly object $domainEvent,
23 | ) {}
24 |
25 | public function getDomainEvent(): object
26 | {
27 | return $this->domainEvent;
28 | }
29 |
30 | public function getObjectManager(): DomainEventAwareObjectManager
31 | {
32 | return $this->objectManager;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/tests/Framework/EventListener/BookDummyMethodCalledListener.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Tests\Framework\EventListener;
15 |
16 | use Rekalogika\Contracts\DomainEvent\Attribute\AsPreFlushDomainEventListener;
17 | use Rekalogika\DomainEvent\Tests\Framework\Event\BookDummyMethodCalled;
18 |
19 | final class BookDummyMethodCalledListener
20 | {
21 | private bool $dummyMethodCalled = false;
22 |
23 | #[AsPreFlushDomainEventListener()]
24 | public function onDummyMethodCalled(BookDummyMethodCalled $event): void
25 | {
26 | $this->dummyMethodCalled = true;
27 | }
28 |
29 | public function isDummyMethodCalled(): bool
30 | {
31 | return $this->dummyMethodCalled;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/tests/Framework/EventListener/BookDummyMethodForFlushListener.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Tests\Framework\EventListener;
15 |
16 | use Doctrine\ORM\EntityManagerInterface;
17 | use Rekalogika\Contracts\DomainEvent\Attribute\AsPreFlushDomainEventListener;
18 | use Rekalogika\DomainEvent\Tests\Framework\Event\BookDummyMethodForFlushCalled;
19 |
20 | final class BookDummyMethodForFlushListener
21 | {
22 | public function __construct(private readonly EntityManagerInterface $entityManager) {}
23 |
24 | #[AsPreFlushDomainEventListener()]
25 | public function onDummyMethodCalled(BookDummyMethodForFlushCalled $event): void
26 | {
27 | // flush is not allowed in preFlush
28 | $this->entityManager->flush();
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/packages/domain-event-contracts/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rekalogika/domain-event-contracts",
3 | "description": "Interfaces, Traits and Nominal Classes used by Domain Entities Implementing Domain Events",
4 | "homepage": "https://rekalogika.dev/domain-event",
5 | "keywords": [
6 | "domain-event",
7 | "domain",
8 | "event",
9 | "symfony",
10 | "doctrine",
11 | "event-dispatcher",
12 | "event-listener",
13 | "domain-driven-design",
14 | "ddd",
15 | "cqrs"
16 | ],
17 | "type": "library",
18 | "license": "MIT",
19 | "authors": [
20 | {
21 | "name": "Priyadi Iman Nurcahyo",
22 | "email": "priyadi@rekalogika.com"
23 | }
24 | ],
25 | "autoload": {
26 | "psr-4": {
27 | "Rekalogika\\Contracts\\DomainEvent\\": "src/"
28 | }
29 | },
30 | "require": {
31 | "psr/event-dispatcher": "^1.0"
32 | },
33 | "extra": {
34 | "branch-alias": {
35 | "dev-main": "2.6-dev"
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/packages/domain-event-outbox/src/MessageHandler/MessageRelayStartMessageHandler.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Outbox\MessageHandler;
15 |
16 | use Rekalogika\DomainEvent\Outbox\Message\MessageRelayStartMessage;
17 | use Rekalogika\DomainEvent\Outbox\MessageRelayInterface;
18 |
19 | /**
20 | * Starts the message relay after receiving the message
21 | */
22 | class MessageRelayStartMessageHandler
23 | {
24 | public function __construct(private readonly MessageRelayInterface $messageRelay) {}
25 |
26 | public function __invoke(MessageRelayStartMessage $message): void
27 | {
28 | do {
29 | $messagesRelayed = $this->messageRelay->relayMessages($message->getManagerName());
30 | } while ($messagesRelayed > 0);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/packages/domain-event-outbox/src/RekalogikaDomainEventOutboxBundle.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Outbox;
15 |
16 | use Rekalogika\DomainEvent\Outbox\DependencyInjection\CompilerPass\OutboxEntityPass;
17 | use Rekalogika\DomainEvent\Outbox\DependencyInjection\CompilerPass\RemoveUnusedPass;
18 | use Symfony\Component\DependencyInjection\ContainerBuilder;
19 | use Symfony\Component\HttpKernel\Bundle\Bundle;
20 |
21 | class RekalogikaDomainEventOutboxBundle extends Bundle
22 | {
23 | #[\Override]
24 | public function build(ContainerBuilder $container): void
25 | {
26 | parent::build($container);
27 |
28 | $container->addCompilerPass(new OutboxEntityPass());
29 | $container->addCompilerPass(new RemoveUnusedPass());
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/tests/Framework/Event/AbstractBookEvent.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Tests\Framework\Event;
15 |
16 | use Rekalogika\Contracts\DomainEvent\EquatableDomainEventInterface;
17 | use Rekalogika\DomainEvent\Tests\Framework\Entity\Book;
18 | use Symfony\Component\Uid\Uuid;
19 |
20 | abstract class AbstractBookEvent implements EquatableDomainEventInterface
21 | {
22 | private readonly Uuid $id;
23 |
24 | public function __construct(Book $book)
25 | {
26 | $this->id = $book->getId();
27 | }
28 |
29 | final public function getId(): Uuid
30 | {
31 | return $this->id;
32 | }
33 |
34 | #[\Override]
35 | final public function getSignature(): string
36 | {
37 | return hash('xxh128', serialize($this));
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/tests/Framework/Event2/AbstractPostEvent.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Tests\Framework\Event2;
15 |
16 | use Rekalogika\Contracts\DomainEvent\EquatableDomainEventInterface;
17 | use Rekalogika\DomainEvent\Tests\Framework\Entity2\Post;
18 | use Symfony\Component\Uid\Uuid;
19 |
20 | abstract class AbstractPostEvent implements EquatableDomainEventInterface
21 | {
22 | private readonly Uuid $id;
23 |
24 | public function __construct(Post $book)
25 | {
26 | $this->id = $book->getId();
27 | }
28 |
29 | final public function getId(): Uuid
30 | {
31 | return $this->id;
32 | }
33 |
34 | #[\Override]
35 | final public function getSignature(): string
36 | {
37 | return hash('xxh128', serialize($this));
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/tests/Framework/Event/AbstractReviewEvent.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Tests\Framework\Event;
15 |
16 | use Rekalogika\Contracts\DomainEvent\EquatableDomainEventInterface;
17 | use Rekalogika\DomainEvent\Tests\Framework\Entity\Review;
18 | use Symfony\Component\Uid\Uuid;
19 |
20 | abstract class AbstractReviewEvent implements EquatableDomainEventInterface
21 | {
22 | private readonly Uuid $id;
23 |
24 | public function __construct(Review $review)
25 | {
26 | $this->id = $review->getId();
27 | }
28 |
29 | final public function getId(): Uuid
30 | {
31 | return $this->id;
32 | }
33 |
34 | #[\Override]
35 | final public function getSignature(): string
36 | {
37 | return hash('xxh128', serialize($this));
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/packages/domain-event/src/ImmediateDomainEventDispatcherInstaller.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent;
15 |
16 | use Psr\EventDispatcher\EventDispatcherInterface;
17 | use Rekalogika\Contracts\DomainEvent\DomainEventImmediateDispatcher;
18 |
19 | /**
20 | * Installs and uninstalls the immediate domain event dispatcher.
21 | */
22 | final class ImmediateDomainEventDispatcherInstaller
23 | {
24 | public function __construct(
25 | private readonly EventDispatcherInterface $eventDispatcher,
26 | ) {}
27 |
28 | public function install(): void
29 | {
30 | DomainEventImmediateDispatcher::install($this->eventDispatcher);
31 | }
32 |
33 | public function uninstall(): void
34 | {
35 | DomainEventImmediateDispatcher::uninstall();
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/tests/Framework/Event2/AbstractCommentEvent.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Tests\Framework\Event2;
15 |
16 | use Rekalogika\Contracts\DomainEvent\EquatableDomainEventInterface;
17 | use Rekalogika\DomainEvent\Tests\Framework\Entity2\Comment;
18 | use Symfony\Component\Uid\Uuid;
19 |
20 | abstract class AbstractCommentEvent implements EquatableDomainEventInterface
21 | {
22 | private readonly Uuid $id;
23 |
24 | public function __construct(Comment $book)
25 | {
26 | $this->id = $book->getId();
27 | }
28 |
29 | final public function getId(): Uuid
30 | {
31 | return $this->id;
32 | }
33 |
34 | #[\Override]
35 | final public function getSignature(): string
36 | {
37 | return hash('xxh128', serialize($this));
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2023-present Priyadi Iman Nurcahyo
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is furnished
8 | to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/packages/domain-event/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2023-present Priyadi Iman Nurcahyo
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of
4 | this software and associated documentation files (the “Software”), to deal in
5 | the Software without restriction, including without limitation the rights to
6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7 | the Software, and to permit persons to whom the Software is furnished to do so,
8 | subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/packages/domain-event-outbox/src/DependencyInjection/CompilerPass/RemoveUnusedPass.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Outbox\DependencyInjection\CompilerPass;
15 |
16 | use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
17 | use Symfony\Component\DependencyInjection\ContainerBuilder;
18 | use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
19 |
20 | /**
21 | * @internal
22 | */
23 | final class RemoveUnusedPass implements CompilerPassInterface
24 | {
25 | #[\Override]
26 | public function process(ContainerBuilder $container): void
27 | {
28 | if (!interface_exists(TokenStorageInterface::class)) {
29 | $container->removeDefinition('rekalogika.domain_event.outbox.message_preparer.user_identifier');
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/packages/domain-event-contracts/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2023-present Priyadi Iman Nurcahyo
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is furnished
8 | to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/packages/domain-event-outbox/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2023-present Priyadi Iman Nurcahyo
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is furnished
8 | to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/tests/Framework/Entity/User.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Tests\Framework\Entity;
15 |
16 | use Symfony\Component\Security\Core\User\UserInterface;
17 |
18 | class User implements UserInterface
19 | {
20 | /**
21 | * @param non-empty-string $username
22 | * @param list $roles
23 | */
24 | public function __construct(
25 | private readonly string $username = 'user',
26 | private readonly array $roles = ['ROLE_USER'],
27 | ) {}
28 |
29 | #[\Override]
30 | public function getRoles(): array
31 | {
32 | return $this->roles;
33 | }
34 |
35 | #[\Override]
36 | public function eraseCredentials(): void {}
37 |
38 | #[\Override]
39 | public function getUserIdentifier(): string
40 | {
41 | return $this->username;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/tests/Framework/Tests/IntegrationTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Tests\Framework\Tests;
15 |
16 | use Psr\EventDispatcher\EventDispatcherInterface;
17 | use Rekalogika\DomainEvent\DependencyInjection\Constants;
18 |
19 | final class IntegrationTest extends DomainEventTestCase
20 | {
21 | public function testEventDispatcherWiring(): void
22 | {
23 | $serviceIds = [
24 | Constants::EVENT_DISPATCHER_IMMEDIATE,
25 | Constants::EVENT_DISPATCHER_PRE_FLUSH,
26 | Constants::EVENT_DISPATCHER_POST_FLUSH,
27 | ];
28 |
29 | foreach ($serviceIds as $serviceId) {
30 | $this->assertInstanceOf(
31 | EventDispatcherInterface::class,
32 | static::getContainer()->get('test.' . $serviceId),
33 | );
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/packages/domain-event-outbox/examples/messenger-default.yaml:
--------------------------------------------------------------------------------
1 | # default messenger.yaml with explicit default bus
2 |
3 | framework:
4 | messenger:
5 | failure_transport: failed
6 |
7 | transports:
8 | # https://symfony.com/doc/current/messenger.html#transport-configuration
9 | async:
10 | dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
11 | options:
12 | use_notify: true
13 | check_delayed_interval: 60000
14 | retry_strategy:
15 | max_retries: 3
16 | multiplier: 2
17 | failed: 'doctrine://default?queue_name=failed'
18 | # sync: 'sync://'
19 |
20 | default_bus: messenger.bus.default
21 |
22 | buses:
23 | messenger.bus.default: null
24 |
25 | routing:
26 | Symfony\Component\Mailer\Messenger\SendEmailMessage: async
27 | Symfony\Component\Notifier\Message\ChatMessage: async
28 | Symfony\Component\Notifier\Message\SmsMessage: async
29 |
30 | # Route your messages to the transports
31 | # 'App\Message\YourMessage': async
32 |
--------------------------------------------------------------------------------
/tests/Framework/Repository/ReviewRepository.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Tests\Framework\Repository;
15 |
16 | use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
17 | use Doctrine\Persistence\ManagerRegistry;
18 | use Rekalogika\DomainEvent\Tests\Framework\Entity\Review;
19 |
20 | /**
21 | * @extends ServiceEntityRepository
22 | *
23 | * @method Review|null find($id, $lockMode = null, $lockVersion = null)
24 | * @method Review|null findOneBy(array $criteria, array $orderBy = null)
25 | * @method Review[] findAll()
26 | * @method Review[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
27 | */
28 | class ReviewRepository extends ServiceEntityRepository
29 | {
30 | public function __construct(ManagerRegistry $registry)
31 | {
32 | parent::__construct($registry, Review::class);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/packages/domain-event-outbox/src/MessageRelay/MessageRelayAll.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Outbox\MessageRelay;
15 |
16 | use Doctrine\Persistence\ManagerRegistry;
17 | use Rekalogika\DomainEvent\Outbox\MessageRelayInterface;
18 |
19 | /**
20 | * Relay messages from all managers
21 | */
22 | final class MessageRelayAll
23 | {
24 | public function __construct(
25 | private readonly ManagerRegistry $managerRegistry,
26 | private readonly MessageRelayInterface $messageRelay,
27 | ) {}
28 |
29 | public function relayAll(): void
30 | {
31 | $managerNames = $this->managerRegistry->getManagerNames();
32 |
33 | foreach ($managerNames as $managerName => $serviceId) {
34 | do {
35 | $messagesRelayed = $this->messageRelay->relayMessages($managerName);
36 | } while ($messagesRelayed > 0);
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/tests/Framework/Security/AccessTokenHandler.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Tests\Framework\Security;
15 |
16 | use Rekalogika\DomainEvent\Tests\Framework\Entity\User;
17 | use Symfony\Component\Security\Core\Exception\BadCredentialsException;
18 | use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface;
19 | use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
20 |
21 | class AccessTokenHandler implements AccessTokenHandlerInterface
22 | {
23 | #[\Override]
24 | public function getUserBadgeFrom(string $accessToken): UserBadge
25 | {
26 | return match ($accessToken) {
27 | 'user' => new UserBadge(
28 | 'user',
29 | fn(string $userIdentifier): User => new User(),
30 | ),
31 | default => throw new BadCredentialsException('Invalid credentials.'),
32 | };
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/tests/Integration/Service/DomainEventEmitterCollectorStub.phpx:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Tests\Integration\Service;
15 |
16 | use Doctrine\ORM\UnitOfWork;
17 | use Rekalogika\Contracts\DomainEvent\DomainEventEmitterInterface;
18 | use Rekalogika\DomainEvent\Doctrine\DomainEventEmitterCollectorInterface;
19 |
20 | final class DomainEventEmitterCollectorStub implements
21 | DomainEventEmitterCollectorInterface
22 | {
23 | /**
24 | * @var iterable
25 | */
26 | private iterable $entities;
27 |
28 | public function __construct(DomainEventEmitterInterface ...$entities)
29 | {
30 | $this->entities = $entities;
31 | }
32 |
33 | /**
34 | * @return iterable
35 | */
36 | public function collectEntities(UnitOfWork $unitOfWork): iterable
37 | {
38 | return $this->entities;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/packages/domain-event-outbox/src/DependencyInjection/Configuration.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Outbox\DependencyInjection;
15 |
16 | use Symfony\Component\Config\Definition\Builder\TreeBuilder;
17 | use Symfony\Component\Config\Definition\ConfigurationInterface;
18 |
19 | class Configuration implements ConfigurationInterface
20 | {
21 | #[\Override]
22 | public function getConfigTreeBuilder(): TreeBuilder
23 | {
24 | $treeBuilder = new TreeBuilder('rekalogika_domain_event_outbox');
25 | $rootNode = $treeBuilder->getRootNode();
26 |
27 | $rootNode
28 | ->children()
29 | ->scalarNode('outbox_table')
30 | ->info('Table name used to store the outbox messages.')
31 | ->defaultValue('rekalogika_event_outbox')
32 | ->end()
33 | ->end();
34 |
35 | return $treeBuilder;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/phpstan.neon.dist:
--------------------------------------------------------------------------------
1 | parameters:
2 | level: max
3 | paths:
4 | - packages
5 | - tests
6 | checkBenevolentUnionTypes: true
7 | checkExplicitMixedMissingReturn: true
8 | checkFunctionNameCase: true
9 | checkInternalClassCaseSensitivity: true
10 | reportMaybesInPropertyPhpDocTypes: true
11 | treatPhpDocTypesAsCertain: false
12 | ignoreErrors:
13 | -
14 | message: '#Attribute class Override does not exist#'
15 | reportUnmatched: false
16 | - '#Call to an undefined method Symfony\\Component\\Config\\Definition\\Builder\\NodeDefinition::children#'
17 | -
18 | message: '#Property .* is never assigned .+ so it can be removed from the property type.#'
19 | reportUnmatched: false
20 | -
21 | message: '#has PHPDoc tag @method for method find(One)?By\(\) parameter#'
22 | reportUnmatched: false
23 | includes:
24 | - vendor/phpstan/phpstan-phpunit/extension.neon
25 | - vendor/phpstan/phpstan-phpunit/rules.neon
26 | - vendor/phpstan/phpstan-deprecation-rules/rules.neon
27 | - vendor/bnf/phpstan-psr-container/extension.neon
28 | - vendor/ekino/phpstan-banned-code/extension.neon
29 | - phar://phpstan.phar/conf/bleedingEdge.neon
30 |
--------------------------------------------------------------------------------
/packages/domain-event/src/Doctrine/DomainEventReaper.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Doctrine;
15 |
16 | use Rekalogika\DomainEvent\DomainEventAwareEntityManagerInterface;
17 |
18 | /**
19 | * Clears domain events from DomainEventAwareEntityManager if an exception
20 | * bubbles up to the kernel. This will prevent DomainEventAwareEntityManager
21 | * from adding another, possibly confusing error due to the fact there are
22 | * undispatched events in its queue.
23 | */
24 | final class DomainEventReaper
25 | {
26 | /**
27 | * @param iterable $entityManagers
28 | */
29 | public function __construct(
30 | private readonly iterable $entityManagers,
31 | ) {}
32 |
33 | public function onKernelException(): void
34 | {
35 | foreach ($this->entityManagers as $entityManager) {
36 | $entityManager->clearDomainEvents();
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/tests/Framework/EventListener/BookDummyMethodForInfiniteLoopCalledListener.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Tests\Framework\EventListener;
15 |
16 | use Doctrine\ORM\EntityManagerInterface;
17 | use Rekalogika\Contracts\DomainEvent\Attribute\AsPreFlushDomainEventListener;
18 | use Rekalogika\DomainEvent\Tests\Framework\Entity\Book;
19 | use Rekalogika\DomainEvent\Tests\Framework\Event\BookDummyMethodForInfiniteLoopCalled;
20 |
21 | final class BookDummyMethodForInfiniteLoopCalledListener
22 | {
23 | public function __construct(private readonly EntityManagerInterface $entityManager) {}
24 |
25 | #[AsPreFlushDomainEventListener()]
26 | public function onDummyMethodCalled(BookDummyMethodForInfiniteLoopCalled $event): void
27 | {
28 | $bookId = $event->getId();
29 | $book = $this->entityManager->find(Book::class, $bookId);
30 | \assert($book instanceof Book);
31 |
32 | $book->dummyMethodForInfiniteLoop();
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/tests/Integration/EventListener/EquatableEventListener.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Tests\Integration\EventListener;
15 |
16 | use Rekalogika\DomainEvent\Tests\Integration\Event\EquatableEvent;
17 | use Rekalogika\DomainEvent\Tests\Integration\Event\NonEquatableEvent;
18 |
19 | final class EquatableEventListener
20 | {
21 | private int $equatableEventHeard = 0;
22 |
23 | private int $nonEquatableEventHeard = 0;
24 |
25 | public function onEquatableEvent(EquatableEvent $event): void
26 | {
27 | $this->equatableEventHeard++;
28 | }
29 |
30 | public function onNonEquatableEvent(NonEquatableEvent $event): void
31 | {
32 | $this->nonEquatableEventHeard++;
33 | }
34 |
35 | public function getEquatableEventHeard(): int
36 | {
37 | return $this->equatableEventHeard;
38 | }
39 |
40 | public function getNonEquatableEventHeard(): int
41 | {
42 | return $this->nonEquatableEventHeard;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/packages/domain-event-outbox/src/EventListener/RenameTableListener.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Outbox\EventListener;
15 |
16 | use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
17 | use Rekalogika\DomainEvent\Outbox\Entity\OutboxMessage;
18 |
19 | /**
20 | * Renames the outbox table according to the configuration.
21 | */
22 | class RenameTableListener
23 | {
24 | public function __construct(private readonly string $outboxTable) {}
25 |
26 | public function loadClassMetadata(LoadClassMetadataEventArgs $event): void
27 | {
28 | $metadata = $event->getClassMetadata();
29 | $reflectionClass = $metadata->getReflectionClass();
30 |
31 | // @phpstan-ignore identical.alwaysFalse
32 | if ($reflectionClass === null) {
33 | return;
34 | }
35 |
36 | if ($reflectionClass->getName() === OutboxMessage::class) {
37 | $metadata->setPrimaryTable(['name' => $this->outboxTable]);
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/tests/Framework/EventListener/BookDummyChangedListener.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Tests\Framework\EventListener;
15 |
16 | use Rekalogika\Contracts\DomainEvent\Attribute\AsPostFlushDomainEventListener;
17 | use Rekalogika\Contracts\DomainEvent\Attribute\AsPreFlushDomainEventListener;
18 | use Rekalogika\DomainEvent\Tests\Framework\Event\BookDummyChanged;
19 |
20 | final class BookDummyChangedListener
21 | {
22 | /**
23 | * @var list
24 | */
25 | public array $preFlush = [];
26 |
27 | /**
28 | * @var list
29 | */
30 | public array $postFlush = [];
31 |
32 | #[AsPreFlushDomainEventListener()]
33 | public function onPreFlush(BookDummyChanged $event): void
34 | {
35 | $this->preFlush[] = $event;
36 | }
37 |
38 | #[AsPostFlushDomainEventListener()]
39 | public function onPostFlush(BookDummyChanged $event): void
40 | {
41 | $this->postFlush[] = $event;
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/packages/domain-event-outbox/src/Schedule/MessageRelayProvider.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Outbox\Schedule;
15 |
16 | use Rekalogika\DomainEvent\Outbox\Message\MessageRelayStartMessage;
17 | use Symfony\Component\Scheduler\RecurringMessage;
18 | use Symfony\Component\Scheduler\Schedule;
19 | use Symfony\Component\Scheduler\ScheduleProviderInterface;
20 |
21 | final class MessageRelayProvider implements ScheduleProviderInterface
22 | {
23 | /**
24 | * @param array $entityManagers
25 | */
26 | public function __construct(
27 | private readonly array $entityManagers,
28 | ) {}
29 |
30 | #[\Override]
31 | public function getSchedule(): Schedule
32 | {
33 | $schedule = new Schedule();
34 |
35 | foreach (array_keys($this->entityManagers) as $name) {
36 | $schedule->add(RecurringMessage::every('1 hour', new MessageRelayStartMessage($name)));
37 | }
38 |
39 | return $schedule;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/packages/domain-event-outbox/src/MessagePreparer/ChainMessagePreparer.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Outbox\MessagePreparer;
15 |
16 | use Rekalogika\DomainEvent\Outbox\MessagePreparerInterface;
17 | use Symfony\Component\Messenger\Envelope;
18 |
19 | /**
20 | * Prepares the message before it is saved to the outbox table.
21 | */
22 | class ChainMessagePreparer implements MessagePreparerInterface
23 | {
24 | /**
25 | * @param iterable $messagePreparers
26 | */
27 | public function __construct(private readonly iterable $messagePreparers) {}
28 |
29 | #[\Override]
30 | public function prepareMessage(Envelope $envelope): ?Envelope
31 | {
32 | foreach ($this->messagePreparers as $messagePreparer) {
33 | $envelope = $messagePreparer->prepareMessage($envelope);
34 |
35 | if (null === $envelope) {
36 | return null;
37 | }
38 | }
39 |
40 | return $envelope;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/packages/domain-event-outbox/src/OutboxReaderInterface.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Outbox;
15 |
16 | use Symfony\Component\Messenger\Envelope;
17 |
18 | /**
19 | * Manages the messages in the outbox queue.
20 | */
21 | interface OutboxReaderInterface
22 | {
23 | /**
24 | * Gets messages from the outbox queue. Starting from the earlier first.
25 | * Should implicitly start a transaction, that will be committed using
26 | * `flush()`.
27 | *
28 | * @return iterable
29 | */
30 | public function getOutboxMessages(int $limit): iterable;
31 |
32 | /**
33 | * Removes a message from the outbox queue by its id.
34 | */
35 | public function removeOutboxMessageById(int|string $id): void;
36 |
37 | /**
38 | * Flags a message as errored.
39 | */
40 | public function flagError(int|string $id): void;
41 |
42 | /**
43 | * Commits the transaction started by getOutboxMessages.
44 | */
45 | public function flush(): void;
46 | }
47 |
--------------------------------------------------------------------------------
/packages/domain-event-outbox/src/Doctrine/OutboxReaderFactory.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Outbox\Doctrine;
15 |
16 | use Doctrine\ORM\EntityManagerInterface;
17 | use Doctrine\Persistence\ManagerRegistry;
18 | use Rekalogika\DomainEvent\Outbox\OutboxReaderFactoryInterface;
19 | use Rekalogika\DomainEvent\Outbox\OutboxReaderInterface;
20 |
21 | class OutboxReaderFactory implements OutboxReaderFactoryInterface
22 | {
23 | public function __construct(private readonly ManagerRegistry $managerRegistry) {}
24 |
25 | #[\Override]
26 | public function createOutboxReader(string $managerName): OutboxReaderInterface
27 | {
28 | $manager = $this->managerRegistry->getManager($managerName);
29 |
30 | if ($manager instanceof EntityManagerInterface) {
31 | return new EntityManagerOutboxReader($manager);
32 | }
33 |
34 | throw new \InvalidArgumentException(\sprintf('Object manager with name "%s" is an instance of "%s", but it is unsupported', $managerName, $manager::class));
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/packages/domain-event-outbox/src/MessagePreparer/UserIdentifierMessagePreparer.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Outbox\MessagePreparer;
15 |
16 | use Rekalogika\DomainEvent\Outbox\MessagePreparerInterface;
17 | use Rekalogika\DomainEvent\Outbox\Stamp\UserIdentifierStamp;
18 | use Symfony\Component\Messenger\Envelope;
19 | use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
20 |
21 | /**
22 | * Prepares the message before it is saved to the outbox table.
23 | */
24 | class UserIdentifierMessagePreparer implements MessagePreparerInterface
25 | {
26 | public function __construct(private readonly TokenStorageInterface $tokenStorage) {}
27 |
28 | #[\Override]
29 | public function prepareMessage(Envelope $envelope): ?Envelope
30 | {
31 | $user = $this->tokenStorage->getToken()?->getUser();
32 |
33 | if (null !== $user) {
34 | $envelope = $envelope->with(new UserIdentifierStamp($user->getUserIdentifier()));
35 | }
36 |
37 | return $envelope;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/tests/Framework/Repository/BookRepository.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Tests\Framework\Repository;
15 |
16 | use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
17 | use Doctrine\ORM\EntityManagerInterface;
18 | use Doctrine\Persistence\ManagerRegistry;
19 | use Rekalogika\DomainEvent\Tests\Framework\Entity\Book;
20 |
21 | /**
22 | * @extends ServiceEntityRepository
23 | *
24 | * @method Book|null find($id, $lockMode = null, $lockVersion = null)
25 | * @method Book|null findOneBy(array $criteria, array $orderBy = null)
26 | * @method Book[] findAll()
27 | * @method Book[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
28 | */
29 | class BookRepository extends ServiceEntityRepository
30 | {
31 | public function __construct(ManagerRegistry $registry)
32 | {
33 | parent::__construct($registry, Book::class);
34 | }
35 |
36 | #[\Override]
37 | public function getEntityManager(): EntityManagerInterface
38 | {
39 | return parent::getEntityManager();
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/tests/Framework/EventListener/BookEventImmediateListener.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Tests\Framework\EventListener;
15 |
16 | use Rekalogika\Contracts\DomainEvent\Attribute\AsImmediateDomainEventListener;
17 | use Rekalogika\DomainEvent\Tests\Framework\Event\BookCreated;
18 | use Rekalogika\DomainEvent\Tests\Framework\Event\BookRemoved;
19 |
20 | final class BookEventImmediateListener
21 | {
22 | private bool $onCreateCalled = false;
23 |
24 | private bool $onRemoveCalled = false;
25 |
26 | #[AsImmediateDomainEventListener()]
27 | public function onCreate(BookCreated $event): void
28 | {
29 | $this->onCreateCalled = true;
30 | }
31 |
32 | #[AsImmediateDomainEventListener()]
33 | public function onRemove(BookRemoved $event): void
34 | {
35 | $this->onRemoveCalled = true;
36 | }
37 |
38 | public function onCreateCalled(): bool
39 | {
40 | return $this->onCreateCalled;
41 | }
42 |
43 | public function onRemoveCalled(): bool
44 | {
45 | return $this->onRemoveCalled;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/.php-cs-fixer.dist.php:
--------------------------------------------------------------------------------
1 | in(__DIR__ . '/packages/domain-event/config')
5 | ->in(__DIR__ . '/packages/domain-event/src')
6 | ->in(__DIR__ . '/packages/domain-event-contracts/src')
7 | ->in(__DIR__ . '/packages/domain-event-outbox/src')
8 | ->in(__DIR__ . '/tests');
9 |
10 | $config = new PhpCsFixer\Config();
11 | return $config->setRules([
12 | '@PER-CS2.0' => true,
13 | '@PER-CS2.0:risky' => true,
14 | 'fully_qualified_strict_types' => true,
15 | 'global_namespace_import' => [
16 | 'import_classes' => false,
17 | 'import_constants' => false,
18 | 'import_functions' => false,
19 | ],
20 | 'no_unneeded_import_alias' => true,
21 | 'no_unused_imports' => true,
22 | 'ordered_imports' => [
23 | 'sort_algorithm' => 'alpha',
24 | 'imports_order' => ['class', 'function', 'const']
25 | ],
26 | 'declare_strict_types' => true,
27 | 'native_function_invocation' => ['include' => ['@compiler_optimized']],
28 | 'header_comment' => [
29 | 'header' => <<
33 |
34 | For the full copyright and license information, please view the LICENSE file
35 | that was distributed with this source code.
36 | EOF,
37 | ]
38 | ])
39 | ->setFinder($finder);
40 |
--------------------------------------------------------------------------------
/tests/Integration/EventListener/DomainEventListener.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Tests\Integration\EventListener;
15 |
16 | final class DomainEventListener
17 | {
18 | private bool $entityCreatedHeard = false;
19 |
20 | private bool $entityRemovedHeard = false;
21 |
22 | private bool $entityNameChangedHeard = false;
23 |
24 | public function onEntityCreated(): void
25 | {
26 | $this->entityCreatedHeard = true;
27 | }
28 |
29 | public function onEntityRemoved(): void
30 | {
31 | $this->entityRemovedHeard = true;
32 | }
33 |
34 | public function onEntityNameChanged(): void
35 | {
36 | $this->entityNameChangedHeard = true;
37 | }
38 |
39 | public function isEntityCreatedHeard(): bool
40 | {
41 | return $this->entityCreatedHeard;
42 | }
43 |
44 | public function isEntityRemovedHeard(): bool
45 | {
46 | return $this->entityRemovedHeard;
47 | }
48 |
49 | public function isEntityNameChangedHeard(): bool
50 | {
51 | return $this->entityNameChangedHeard;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/tests/Integration/Factory.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Tests\Integration;
15 |
16 | use Doctrine\ORM\EntityManagerInterface;
17 | use Doctrine\Persistence\ManagerRegistry;
18 | use Psr\EventDispatcher\EventDispatcherInterface;
19 |
20 | class Factory
21 | {
22 | public static function mockEntityManager(): EntityManagerInterface
23 | {
24 | $entityManager = \Mockery::mock(EntityManagerInterface::class);
25 | \assert($entityManager instanceof EntityManagerInterface);
26 |
27 | return $entityManager;
28 | }
29 |
30 | public static function mockManagerRegistry(): ManagerRegistry
31 | {
32 | $managerRegistry = \Mockery::mock(ManagerRegistry::class);
33 | \assert($managerRegistry instanceof ManagerRegistry);
34 |
35 | return $managerRegistry;
36 | }
37 |
38 | public static function mockEventDispatcher(): EventDispatcherInterface
39 | {
40 | $eventDispatcher = \Mockery::mock(EventDispatcherInterface::class);
41 | \assert($eventDispatcher instanceof EventDispatcherInterface);
42 |
43 | return $eventDispatcher;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/packages/domain-event/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rekalogika/domain-event",
3 | "description": "Domain Event Implementation for Symfony and Doctrine",
4 | "homepage": "https://rekalogika.dev/domain-event",
5 | "keywords": [
6 | "domain-event",
7 | "domain",
8 | "event",
9 | "symfony",
10 | "doctrine",
11 | "event-dispatcher",
12 | "event-listener"
13 | ],
14 | "type": "symfony-bundle",
15 | "license": "MIT",
16 | "authors": [
17 | {
18 | "name": "Priyadi Iman Nurcahyo",
19 | "email": "priyadi@rekalogika.com"
20 | }
21 | ],
22 | "autoload": {
23 | "psr-4": {
24 | "Rekalogika\\DomainEvent\\": "src/"
25 | }
26 | },
27 | "require": {
28 | "doctrine/doctrine-bundle": "^2.11.3 || ^2.12",
29 | "doctrine/orm": "^2.16 || ^3.0",
30 | "doctrine/persistence": "^3.2 || ^4.0",
31 | "psr/event-dispatcher": "^1.0",
32 | "rekalogika/domain-event-contracts": "^2.5.2",
33 | "symfony/config": "^6.4 || ^7.0",
34 | "symfony/dependency-injection": "^6.4 || ^7.0",
35 | "symfony/event-dispatcher": "^6.4 || ^7.0",
36 | "symfony/http-kernel": "^6.4 || ^7.0",
37 | "symfony/service-contracts": "^3.0",
38 | "symfony/var-exporter": "^6.4 || ^7.0"
39 | },
40 | "extra": {
41 | "branch-alias": {
42 | "dev-main": "2.6-dev"
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/packages/domain-event/src/DependencyInjection/Constants.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\DependencyInjection;
15 |
16 | final class Constants
17 | {
18 | public const EVENT_DISPATCHERS = 'rekalogika.domain_event.dispatchers';
19 |
20 | public const EVENT_DISPATCHER_IMMEDIATE = 'rekalogika.domain_event.dispatcher.immediate';
21 |
22 | public const EVENT_DISPATCHER_PRE_FLUSH = 'rekalogika.domain_event.dispatcher.pre_flush';
23 |
24 | public const EVENT_DISPATCHER_POST_FLUSH = 'rekalogika.domain_event.dispatcher.post_flush';
25 |
26 | public const IMMEDIATE_EVENT_DISPATCHING_DISPATCHER = 'rekalogika.domain_event.immediate_event_dispatching_dispatcher';
27 |
28 | public const DOCTRINE_EVENT_LISTENER = 'rekalogika.domain_event.doctrine.event_listener';
29 |
30 | public const REAPER = 'rekalogika.domain_event.reaper';
31 |
32 | public const IMMEDIATE_DISPATCHER_INSTALLER = 'rekalogika.domain_event.immediate_dispatcher_installer';
33 |
34 | public const MANAGER_REGISTRY = 'rekalogika.domain_event.doctrine';
35 |
36 | public const REAL_MANAGER_REGISTRY = 'rekalogika.domain_event.doctrine.real';
37 |
38 | private function __construct() {}
39 | }
40 |
--------------------------------------------------------------------------------
/tests/Framework/Resources/config/packages/doctrine.yaml:
--------------------------------------------------------------------------------
1 | doctrine:
2 | dbal:
3 | connections:
4 | default:
5 | driver: pdo_sqlite
6 | memory: true
7 | charset: UTF8
8 | use_savepoints: true
9 | other:
10 | driver: pdo_sqlite
11 | memory: true
12 | charset: UTF8
13 | use_savepoints: true
14 |
15 | orm:
16 | auto_generate_proxy_classes: true
17 | enable_lazy_ghost_objects: true
18 | default_entity_manager: default
19 | entity_managers:
20 | default:
21 | connection: default
22 | naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
23 | mappings:
24 | Book:
25 | is_bundle: false
26 | type: attribute
27 | dir: "%kernel.project_dir%/tests/Framework/Entity"
28 | prefix: 'Rekalogika\DomainEvent\Tests\Framework\Entity'
29 | other:
30 | connection: other
31 | naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
32 | mappings:
33 | Post:
34 | is_bundle: false
35 | type: attribute
36 | dir: "%kernel.project_dir%/tests/Framework/Entity2"
37 | prefix: 'Rekalogika\DomainEvent\Tests\Framework\Entity2'
38 |
--------------------------------------------------------------------------------
/tests/Framework/EventListener/BookDummyMethodForNestedRecordEventListener.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Tests\Framework\EventListener;
15 |
16 | use Doctrine\ORM\EntityManagerInterface;
17 | use Rekalogika\Contracts\DomainEvent\Attribute\AsPreFlushDomainEventListener;
18 | use Rekalogika\DomainEvent\Tests\Framework\Entity\Book;
19 | use Rekalogika\DomainEvent\Tests\Framework\Event\BookDummyMethodForNestedRecordEventCalled;
20 |
21 | final class BookDummyMethodForNestedRecordEventListener
22 | {
23 | private bool $dummyMethodForNestedRecordEventCalled = false;
24 |
25 | public function __construct(private readonly EntityManagerInterface $entityManager) {}
26 |
27 | #[AsPreFlushDomainEventListener()]
28 | public function onDummyMethodCalled(BookDummyMethodForNestedRecordEventCalled $event): void
29 | {
30 | $bookId = $event->getId();
31 | $book = $this->entityManager->find(Book::class, $bookId);
32 | \assert($book instanceof Book);
33 | $book->dummyMethod();
34 |
35 | $this->dummyMethodForNestedRecordEventCalled = true;
36 | }
37 |
38 | public function isDummyMethodForNestedRecordEventCalled(): bool
39 | {
40 | return $this->dummyMethodForNestedRecordEventCalled;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/packages/domain-event-outbox/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rekalogika/domain-event-outbox",
3 | "description": "Implementation of the transactional outbox pattern on top of rekalogika/domain-event",
4 | "homepage": "https://rekalogika.dev/domain-event",
5 | "keywords": [
6 | "domain-event",
7 | "domain",
8 | "event",
9 | "symfony",
10 | "doctrine",
11 | "event-dispatcher",
12 | "event-listener",
13 | "domain-driven-design",
14 | "ddd",
15 | "cqrs",
16 | "outbox",
17 | "microservice"
18 | ],
19 | "type": "symfony-bundle",
20 | "license": "MIT",
21 | "authors": [
22 | {
23 | "name": "Priyadi Iman Nurcahyo",
24 | "email": "priyadi@rekalogika.com"
25 | }
26 | ],
27 | "autoload": {
28 | "psr-4": {
29 | "Rekalogika\\DomainEvent\\Outbox\\": "src/"
30 | }
31 | },
32 | "require": {
33 | "doctrine/doctrine-bundle": "^2.11.3 || ^2.12",
34 | "doctrine/orm": "^2.16 || ^3.0",
35 | "doctrine/persistence": "^3.2 || ^4.0",
36 | "rekalogika/domain-event": "^2.5.2",
37 | "rekalogika/domain-event-contracts": "^2.5.2",
38 | "symfony/config": "^6.4 || ^7.0",
39 | "symfony/dependency-injection": "^6.4 || ^7.0",
40 | "symfony/http-kernel": "^6.4 || ^7.0",
41 | "symfony/lock": "^6.4 || ^7.0",
42 | "symfony/messenger": "^6.4 || ^7.0"
43 | },
44 | "extra": {
45 | "branch-alias": {
46 | "dev-main": "2.6-dev"
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | PHP=php
2 |
3 | .PHONY: test
4 | test: clean phpstan psalm monorepo-validate lint-container phpunit
5 |
6 | .PHONY: clean
7 | clean:
8 | rm -rf var
9 |
10 | .PHONY: monorepo
11 | monorepo: monorepo-validate monorepo-merge
12 |
13 | .PHONY: monorepo-validate
14 | monorepo-validate:
15 | vendor/bin/monorepo-builder validate
16 |
17 | .PHONY: monorepo-merge
18 | monorepo-merge:
19 | $(PHP) vendor/bin/monorepo-builder merge
20 |
21 | .PHONY: monorepo-release-%
22 | monorepo-release-%:
23 | git update-index --really-refresh > /dev/null; git diff-index --quiet HEAD || (echo "Working directory is not clean, aborting" && exit 1)
24 | [ $$(git branch --show-current) == main ] || (echo "Not on main branch, aborting" && exit 1)
25 | $(PHP) vendor/bin/monorepo-builder release $*
26 | git switch -c release/$*
27 | git add .
28 | git commit -m "release: $*"
29 |
30 | .PHONY: lint-container
31 | lint-container:
32 | $(PHP) tests/bin/console lint:container
33 |
34 | .PHONY: phpstan
35 | phpstan:
36 | $(PHP) vendor/bin/phpstan analyse
37 |
38 | .PHONY: psalm
39 | psalm:
40 | $(PHP) vendor/bin/psalm
41 |
42 | .PHONY: phpunit
43 | phpunit: clean
44 | $(eval c ?=)
45 | $(PHP) vendor/bin/phpunit $(c)
46 |
47 | .PHONY: php-cs-fixer
48 | php-cs-fixer: tools/php-cs-fixer
49 | $(PHP) $< fix --config=.php-cs-fixer.dist.php --verbose --allow-risky=yes
50 |
51 | .PHONY: tools/php-cs-fixer
52 | tools/php-cs-fixer:
53 | phive install php-cs-fixer
54 |
55 | .PHONY: dump
56 | dump:
57 | $(PHP) tests/bin/console server:dump
58 |
59 | .PHONY: rector
60 | rector:
61 | $(PHP) vendor/bin/rector process > rector.log
62 | make php-cs-fixer
--------------------------------------------------------------------------------
/packages/domain-event/src/EventDispatcher/EventDispatchers.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\EventDispatcher;
15 |
16 | use Psr\EventDispatcher\EventDispatcherInterface;
17 |
18 | /**
19 | * Contains all the different event dispatchers used in the domain event system.
20 | */
21 | final class EventDispatchers
22 | {
23 | public function __construct(
24 | private readonly EventDispatcherInterface $defaultEventDispatcher,
25 | private readonly EventDispatcherInterface $immediateEventDispatcher,
26 | private readonly EventDispatcherInterface $preFlushEventDispatcher,
27 | private readonly EventDispatcherInterface $postFlushEventDispatcher,
28 | ) {}
29 |
30 | public function getDefaultEventDispatcher(): EventDispatcherInterface
31 | {
32 | return $this->defaultEventDispatcher;
33 | }
34 |
35 | public function getImmediateEventDispatcher(): EventDispatcherInterface
36 | {
37 | return $this->immediateEventDispatcher;
38 | }
39 |
40 | public function getPreFlushEventDispatcher(): EventDispatcherInterface
41 | {
42 | return $this->preFlushEventDispatcher;
43 | }
44 |
45 | public function getPostFlushEventDispatcher(): EventDispatcherInterface
46 | {
47 | return $this->postFlushEventDispatcher;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/packages/domain-event/src/DomainEventAwareManagerRegistry.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent;
15 |
16 | use Doctrine\Persistence\ManagerRegistry;
17 | use Doctrine\Persistence\ObjectManager;
18 |
19 | interface DomainEventAwareManagerRegistry extends ManagerRegistry
20 | {
21 | /**
22 | * Gets the real registry that is being decorated by this instance.
23 | */
24 | public function getRealRegistry(): ManagerRegistry;
25 |
26 | /**
27 | * Gets the manager name of the given object manager.
28 | */
29 | public function getManagerName(ObjectManager $manager): string;
30 |
31 | /**
32 | * The domain-event-aware version of `getManager()`
33 | */
34 | public function getDomainEventAwareManager(
35 | ObjectManager $objectManager,
36 | ): DomainEventAwareObjectManager;
37 |
38 | /**
39 | * The domain-event-aware version of `getManagers()`
40 | *
41 | * @return array
42 | */
43 | public function getDomainEventAwareManagers(): array;
44 |
45 | /**
46 | * The domain-event-aware version of `getManagerForClass()`
47 | *
48 | * @param class-string $class
49 | */
50 | public function getDomainEventAwareManagerForClass(
51 | string $class,
52 | ): ?DomainEventAwareObjectManager;
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/tests/Integration/EventListener/FlushingDomainEventListener.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Tests\Integration\EventListener;
15 |
16 | use Doctrine\ORM\EntityManagerInterface;
17 |
18 | final class FlushingDomainEventListener
19 | {
20 | public function __construct(private readonly EntityManagerInterface $entityManager) {}
21 |
22 | private bool $entityCreatedHeard = false;
23 |
24 | private bool $entityRemovedHeard = false;
25 |
26 | private bool $entityNameChangedHeard = false;
27 |
28 | public function onEntityCreated(): void
29 | {
30 | $this->entityManager->flush();
31 | $this->entityCreatedHeard = true;
32 | }
33 |
34 | public function onEntityRemoved(): void
35 | {
36 | $this->entityManager->flush();
37 | $this->entityRemovedHeard = true;
38 | }
39 |
40 | public function onEntityNameChanged(): void
41 | {
42 | $this->entityManager->flush();
43 | $this->entityNameChangedHeard = true;
44 | }
45 |
46 | public function isEntityCreatedHeard(): bool
47 | {
48 | return $this->entityCreatedHeard;
49 | }
50 |
51 | public function isEntityRemovedHeard(): bool
52 | {
53 | return $this->entityRemovedHeard;
54 | }
55 |
56 | public function isEntityNameChangedHeard(): bool
57 | {
58 | return $this->entityNameChangedHeard;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/tests/Framework/Tests/EquatableEventTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Tests\Framework\Tests;
15 |
16 | use Rekalogika\DomainEvent\Tests\Framework\Entity\Book;
17 | use Rekalogika\DomainEvent\Tests\Framework\EventListener\BookEventPostFlushListener;
18 | use Rekalogika\DomainEvent\Tests\Framework\EventListener\BookEventPreFlushListener;
19 |
20 | final class EquatableEventTest extends DomainEventTestCase
21 | {
22 | public function testWithoutTransaction(): void
23 | {
24 | $preFlushListener = static::getContainer()->get(BookEventPreFlushListener::class);
25 | $this->assertInstanceOf(BookEventPreFlushListener::class, $preFlushListener);
26 |
27 | $postFlushListener = static::getContainer()->get(BookEventPostFlushListener::class);
28 | $this->assertInstanceOf(BookEventPostFlushListener::class, $postFlushListener);
29 |
30 | $book = new Book('Book A', 'Description A');
31 |
32 | $this->entityManager->persist($book);
33 | $this->entityManager->flush();
34 |
35 | $book->setTitle('Book B');
36 | $book->setTitle('Book C');
37 | $book->setTitle('Book D');
38 | $book->setTitle('Book E');
39 | $book->setTitle('Book F');
40 |
41 | $this->entityManager->flush();
42 |
43 | $this->assertEquals(1, $preFlushListener->onChangeCalled());
44 | $this->assertEquals(1, $postFlushListener->onChangeCalled());
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/packages/domain-event-contracts/src/DomainEventImmediateDispatcher.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\Contracts\DomainEvent;
15 |
16 | use Psr\EventDispatcher\EventDispatcherInterface;
17 |
18 | /**
19 | * Dispatches domain events immediately.
20 | */
21 | final class DomainEventImmediateDispatcher
22 | {
23 | private static ?EventDispatcherInterface $eventDispatcher = null;
24 |
25 | /**
26 | * Disallow instantiation
27 | */
28 | private function __construct() {}
29 |
30 | /**
31 | * Called at the beginning of the request to install the event dispatcher.
32 | */
33 | public static function install(EventDispatcherInterface $eventDispatcher): void
34 | {
35 | self::$eventDispatcher = $eventDispatcher;
36 | }
37 |
38 | public static function uninstall(): void
39 | {
40 | self::$eventDispatcher = null;
41 | }
42 |
43 | /**
44 | * Dispatches an event using the installed event dispatcher
45 | *
46 | * @template T of object
47 | * @param T $event
48 | * @return T
49 | */
50 | public static function dispatch(object $event): object
51 | {
52 | if (self::$eventDispatcher === null) {
53 | throw new \RuntimeException('ImmediateDomainEventDispatcher has not been initialized');
54 | }
55 |
56 | /** @var T */
57 | $result = self::$eventDispatcher->dispatch($event);
58 |
59 | return $result;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/tests/Framework/EventListener/BookEventPreFlushListener.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Tests\Framework\EventListener;
15 |
16 | use Rekalogika\Contracts\DomainEvent\Attribute\AsPreFlushDomainEventListener;
17 | use Rekalogika\DomainEvent\Tests\Framework\Event\BookChanged;
18 | use Rekalogika\DomainEvent\Tests\Framework\Event\BookCreated;
19 | use Rekalogika\DomainEvent\Tests\Framework\Event\BookRemoved;
20 |
21 | final class BookEventPreFlushListener
22 | {
23 | private bool $onCreateCalled = false;
24 |
25 | private bool $onRemoveCalled = false;
26 |
27 | private int $onChangeCalled = 0;
28 |
29 | #[AsPreFlushDomainEventListener()]
30 | public function onCreate(BookCreated $event): void
31 | {
32 | $this->onCreateCalled = true;
33 | }
34 |
35 | #[AsPreFlushDomainEventListener()]
36 | public function onChange(BookChanged $event): void
37 | {
38 | $this->onChangeCalled++;
39 | }
40 |
41 | #[AsPreFlushDomainEventListener()]
42 | public function onRemove(BookRemoved $event): void
43 | {
44 | $this->onRemoveCalled = true;
45 | }
46 |
47 | public function onCreateCalled(): bool
48 | {
49 | return $this->onCreateCalled;
50 | }
51 |
52 | public function onRemoveCalled(): bool
53 | {
54 | return $this->onRemoveCalled;
55 | }
56 |
57 | public function onChangeCalled(): int
58 | {
59 | return $this->onChangeCalled;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/tests/Framework/EventListener/BookEventPostFlushListener.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Tests\Framework\EventListener;
15 |
16 | use Rekalogika\Contracts\DomainEvent\Attribute\AsPostFlushDomainEventListener;
17 | use Rekalogika\DomainEvent\Tests\Framework\Event\BookChanged;
18 | use Rekalogika\DomainEvent\Tests\Framework\Event\BookCreated;
19 | use Rekalogika\DomainEvent\Tests\Framework\Event\BookRemoved;
20 |
21 | final class BookEventPostFlushListener
22 | {
23 | private bool $onCreateCalled = false;
24 |
25 | private bool $onRemoveCalled = false;
26 |
27 | private int $onChangeCalled = 0;
28 |
29 | #[AsPostFlushDomainEventListener()]
30 | public function onCreate(BookCreated $event): void
31 | {
32 | $this->onCreateCalled = true;
33 | }
34 |
35 | #[AsPostFlushDomainEventListener()]
36 | public function onChange(BookChanged $event): void
37 | {
38 | $this->onChangeCalled++;
39 | }
40 |
41 | #[AsPostFlushDomainEventListener()]
42 | public function onRemove(BookRemoved $event): void
43 | {
44 | $this->onRemoveCalled = true;
45 | }
46 |
47 | public function onCreateCalled(): bool
48 | {
49 | return $this->onCreateCalled;
50 | }
51 |
52 | public function onRemoveCalled(): bool
53 | {
54 | return $this->onRemoveCalled;
55 | }
56 |
57 | public function onChangeCalled(): int
58 | {
59 | return $this->onChangeCalled;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/tests/Framework/EventListener/BookEventEventBusListener.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Tests\Framework\EventListener;
15 |
16 | use Rekalogika\Contracts\DomainEvent\Attribute\AsPublishedDomainEventListener;
17 | use Rekalogika\DomainEvent\Tests\Framework\Event\BookChanged;
18 | use Rekalogika\DomainEvent\Tests\Framework\Event\BookCreated;
19 | use Rekalogika\DomainEvent\Tests\Framework\Event\BookRemoved;
20 |
21 | final class BookEventEventBusListener
22 | {
23 | private bool $onCreateCalled = false;
24 |
25 | private bool $onRemoveCalled = false;
26 |
27 | private int $onChangeCalled = 0;
28 |
29 | // skipped for testing purpose
30 | // #[AsDomainEventBusListener()]
31 | // public function onCreate(BookCreated $event): void
32 | // {
33 | // $this->onCreateCalled = true;
34 | // }
35 |
36 | #[AsPublishedDomainEventListener()]
37 | public function onChange(BookChanged $event): void
38 | {
39 | $this->onChangeCalled++;
40 | }
41 |
42 | #[AsPublishedDomainEventListener()]
43 | public function onRemove(BookRemoved $event): void
44 | {
45 | $this->onRemoveCalled = true;
46 | }
47 |
48 | public function onCreateCalled(): bool
49 | {
50 | return $this->onCreateCalled;
51 | }
52 |
53 | public function onRemoveCalled(): bool
54 | {
55 | return $this->onRemoveCalled;
56 | }
57 |
58 | public function onChangeCalled(): int
59 | {
60 | return $this->onChangeCalled;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/tests/Framework/EventListener/PostEventEventBusListener.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Tests\Framework\EventListener;
15 |
16 | use Rekalogika\Contracts\DomainEvent\Attribute\AsPublishedDomainEventListener;
17 | use Rekalogika\DomainEvent\Tests\Framework\Event2\PostChanged;
18 | use Rekalogika\DomainEvent\Tests\Framework\Event2\PostCreated;
19 | use Rekalogika\DomainEvent\Tests\Framework\Event2\PostRemoved;
20 |
21 | final class PostEventEventBusListener
22 | {
23 | private bool $onCreateCalled = false;
24 |
25 | private bool $onRemoveCalled = false;
26 |
27 | private int $onChangeCalled = 0;
28 |
29 | // skipped for testing purpose
30 | // #[AsDomainEventBusListener()]
31 | // public function onCreate(PostCreated $event): void
32 | // {
33 | // $this->onCreateCalled = true;
34 | // }
35 |
36 | #[AsPublishedDomainEventListener()]
37 | public function onChange(PostChanged $event): void
38 | {
39 | $this->onChangeCalled++;
40 | }
41 |
42 | #[AsPublishedDomainEventListener()]
43 | public function onRemove(PostRemoved $event): void
44 | {
45 | $this->onRemoveCalled = true;
46 | }
47 |
48 | public function onCreateCalled(): bool
49 | {
50 | return $this->onCreateCalled;
51 | }
52 |
53 | public function onRemoveCalled(): bool
54 | {
55 | return $this->onRemoveCalled;
56 | }
57 |
58 | public function onChangeCalled(): int
59 | {
60 | return $this->onChangeCalled;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/packages/domain-event/src/RekalogikaDomainEventBundle.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent;
15 |
16 | use Rekalogika\DomainEvent\DependencyInjection\CompilerPass\EntityManagerDecoratorPass;
17 | use Rekalogika\DomainEvent\DependencyInjection\CompilerPass\ProfilerWorkaroundPass;
18 | use Rekalogika\DomainEvent\DependencyInjection\Constants;
19 | use Symfony\Component\DependencyInjection\ContainerBuilder;
20 | use Symfony\Component\HttpKernel\Bundle\Bundle;
21 |
22 | class RekalogikaDomainEventBundle extends Bundle
23 | {
24 | #[\Override]
25 | public function build(ContainerBuilder $container): void
26 | {
27 | parent::build($container);
28 |
29 | $container->addCompilerPass(new EntityManagerDecoratorPass());
30 | $container->addCompilerPass(new ProfilerWorkaroundPass());
31 | }
32 |
33 | #[\Override]
34 | public function boot(): void
35 | {
36 | $installer = $this->container?->get(Constants::IMMEDIATE_DISPATCHER_INSTALLER);
37 |
38 | if ($installer instanceof ImmediateDomainEventDispatcherInstaller) {
39 | /** @var ImmediateDomainEventDispatcherInstaller $installer */
40 | $installer->install();
41 | }
42 | }
43 |
44 | #[\Override]
45 | public function shutdown(): void
46 | {
47 | $installer = $this->container?->get(Constants::IMMEDIATE_DISPATCHER_INSTALLER);
48 |
49 | if ($installer instanceof ImmediateDomainEventDispatcherInstaller) {
50 | /** @var ImmediateDomainEventDispatcherInstaller $installer */
51 | $installer->uninstall();
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/tests/Framework/Entity2/Comment.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent\Tests\Framework\Entity2;
15 |
16 | use Doctrine\ORM\Mapping as ORM;
17 | use Rekalogika\Contracts\DomainEvent\DomainEventEmitterInterface;
18 | use Rekalogika\Contracts\DomainEvent\DomainEventEmitterTrait;
19 | use Symfony\Bridge\Doctrine\Types\UuidType;
20 | use Symfony\Component\Uid\Uuid;
21 |
22 | #[ORM\Entity()]
23 | class Comment implements DomainEventEmitterInterface
24 | {
25 | use DomainEventEmitterTrait;
26 |
27 | #[ORM\Id]
28 | #[ORM\Column(type: UuidType::NAME, unique: true, nullable: false)]
29 | private Uuid $id;
30 |
31 | #[ORM\Column]
32 | private ?string $content = null;
33 |
34 | #[ORM\ManyToOne(
35 | targetEntity: Post::class,
36 | inversedBy: 'comment',
37 | )]
38 | private ?Post $post = null;
39 |
40 | public function __construct()
41 | {
42 | $this->id = Uuid::v7();
43 | }
44 |
45 | #[\Override]
46 | public function __remove(): void {}
47 |
48 | public function getId(): Uuid
49 | {
50 | return $this->id;
51 | }
52 |
53 | public function getPost(): ?Post
54 | {
55 | return $this->post;
56 | }
57 |
58 | public function setPost(?Post $post): self
59 | {
60 | $this->post = $post;
61 |
62 | return $this;
63 | }
64 |
65 | public function getContent(): ?string
66 | {
67 | return $this->content;
68 | }
69 |
70 | public function setContent(?string $content): self
71 | {
72 | $this->content = $content;
73 |
74 | return $this;
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/packages/domain-event-contracts/src/DomainEventEmitterTrait.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\Contracts\DomainEvent;
15 |
16 | /**
17 | * Helper trait to implement DomainEventEmitterInterface.
18 | */
19 | trait DomainEventEmitterTrait
20 | {
21 | /**
22 | * @var array
23 | */
24 | private array $recordedDomainEvents = [];
25 |
26 | /**
27 | * @return array
28 | */
29 | final public function popRecordedEvents(): array
30 | {
31 | $recordedEvents = $this->recordedDomainEvents;
32 | $this->recordedDomainEvents = [];
33 |
34 | return $recordedEvents;
35 | }
36 |
37 | /**
38 | * Called by the object to record an event, and immediately dispatch it
39 | * using the DomainEventImmediateDispatcher. Returns the event object as
40 | * returned by the immediate dispatcher. It does not get anything back from
41 | * preflush or postflush events.
42 | *
43 | * @template T of object
44 | * @param T $event
45 | * @return T
46 | */
47 | protected function recordEvent(
48 | object $event,
49 | ): object {
50 | if ($event instanceof EquatableDomainEventInterface) {
51 | $hash = $event->getSignature();
52 | $this->recordedDomainEvents[$hash] = $event;
53 | } else {
54 | $this->recordedDomainEvents[] = $event;
55 | }
56 |
57 | // returns the event object as returned by the immediate dispatcher
58 | return DomainEventImmediateDispatcher::dispatch($event);
59 | }
60 |
61 | public function __remove(): void {}
62 | }
63 |
--------------------------------------------------------------------------------
/packages/domain-event/src/DomainEventManagerInterface.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE file
11 | * that was distributed with this source code.
12 | */
13 |
14 | namespace Rekalogika\DomainEvent;
15 |
16 | use Doctrine\Persistence\ObjectManager;
17 |
18 | interface DomainEventManagerInterface
19 | {
20 | /**
21 | * Gets the original object manager associated with this domain event
22 | * manager
23 | */
24 | public function getObjectManager(): ObjectManager;
25 |
26 | /**
27 | * Enables or disables auto dispatch
28 | */
29 | public function setAutoDispatchDomainEvents(bool $autoDispatch): void;
30 |
31 | /**
32 | * Returns true if auto dispatch is enabled
33 | */
34 | public function isAutoDispatchDomainEvents(): bool;
35 |
36 | /**
37 | * Manually dispatch all pending events before flushing
38 | */
39 | public function dispatchPreFlushDomainEvents(): int;
40 |
41 | /**
42 | * Manually dispatch all pending events after flushing
43 | */
44 | public function dispatchPostFlushDomainEvents(): int;
45 |
46 | /**
47 | * Clears all pending events without dispatching
48 | */
49 | public function clearDomainEvents(): void;
50 |
51 | /**
52 | * Returns and clears all pending domain events. This is taken from the
53 | * post-flush queue.
54 | *
55 | * @return iterable