All notable changes to this project will be documented in this file. This project adheres to Semantic Versioning and this changelog format.
5.2.0 - 2026-01-05
- New domain event dispatcher features:
- Can now use a PSR container for the domain event dispatcher to resolve both handlers and middleware. Inject the service container via the first constructor argument. This works for all three domain event dispatchers.
- Domain events can now be mapped to handlers on a domain event dispatcher class via the
ListonToattribute. - Middleware can now be added to a domain event dispatcher via the
Throughattribute.
- The unit of work domain event dispatcher will now log debug messages when deferring and executing listeners. To enable this, inject an optional logger instance via the constructor.
5.1.0 - 2026-01-01
- New command bus features:
- Can now use a PSR container for the command bus to resolve both handlers and middleware. Inject the service container via the first constructor argument.
- Commands can now be mapped to handlers on a command bus class via the
WithCommandattribute. - Middleware can now be added to a command bus via the
Throughattribute.
- New query bus features:
- Can now use a PSR container for the query bus to resolve both handlers and middleware. Inject the service container via the first constructor argument.
- Queries can now be mapped to handlers on a query bus class via the
WithQueryattribute. - Middleware can now be added to a query bus via the
Throughattribute.
- New inbound event bus features:
- Can now use a PSR container for the inbound event bus to resolve both handlers and middleware. Inject the service container via the first constructor argument.
- Integration events can now be mapped to handlers on an inbound event bus class via the
WithEventattribute. - The default handler can be set on the inbound event bus via the
WithDefaultattribute. - Middleware can now be added to an inbound event bus via the
Throughattribute.
- New outbound event bus features, when using the component publisher:
- Can now use a PSR container for the outbound event bus to resolve both publishers and middleware. Inject the service container via the constructor.
- Integration events can now be mapped to publishers on a publisher handler container class via the
Publishesattribute. - The default publisher can be set on the outbound event publisher via the
DefaultPublisherattribute. - Middleware can now be added to an outbound event publisher via the
Throughattribute.
- New queue features, when using the component queue:
- Can now use a PSR container for the queue to resolve both enqueuers and middleware. Inject the service container via the constructor.
- Commands can now be mapped to enqueuers on a publisher handler container class via the
Queuesattribute. - The default enqueuer can be set on the outbound event publisher via the
DefaultEnqueuerattribute. - Middleware can now be added to the queue via the
Throughattribute.
- In the Application layer, the
QueryHandlerContainer,CommandHandlerContainerandEventHandlerContainerclasses can now fall back to resolving handlers from a PSR service container. Inject the service container via their constructors. - In the Infrastructure layer, the
PublisherHandlerContainerandEnqueuerContainercan now fall back to resolving handlers/enqueuers from a PSR service container. Inject the service container via the constructor. - The outbound event bus
ClosurePublisherand theClosureQueueclasses now both accept a PSR container for their middleware. Additionally, middleware can be set on instances of closure publishers via theThroughattribute. - The pipeline
PipeContainerclass can now fall back to resolving pipes from a PSR service container. Inject the service container via the pipe container's only constructor argument. - The
FakeUnitOfWorkclass now has integer properties for the number of attempts, commits and rollbacks. - New
FakeContainerclass for faking a PSR container in tests. - Added
UuidV4andUuidV7identifiers, for use by implementations that need to enforce use of specific UUID versions.
5.0.0 - 2025-12-09
- PHP 8.5 is now supported, without any changes required.
5.0.0-rc.4 - 2025-10-29
- The following fake classes now all implement array access to access captured items by index, plus can now be iterated
over:
Testing\FakeDomainEventDispatcherTesting\FakeExceptionReporterTesting\FakeOutboundEventPublisherTesting\FakeQueue
5.0.0-rc.3 - 2025-10-14
- BREAKING Command and query validators can now return early on the first validation failure, by marking the
validator with the
stopOnFirstFailure()method. This is technically a breaking change as the method was added to the validator interface; however, if you are using theValidatorclass provided by this package, it now implements this method. - Middleware that extend
ValidateCommandandValidateQuerycan mark themselves as stopping on the first failure by implementing theBailinterface, or overloading thestopOnFirstFailure()method to returntrue.
5.0.0-rc.2 - 2025-10-14
- Added a static
tryFrom()method to theIntegerId,StringIdandUuididentifier classes. This method returnsnullif the value provided cannot be cast to the identifier type. - The static
from()method on all the identifier classes now acceptsnullbut throws in this scenario. This allows it to be used where the value you are casting is possibly null. - New methods on the
ListOfErrorsinterface:find()to find the first matching error.sole()to get the first error, but only if exactly one error exists.any()to determine if any of the errors match.every()to determine if every error matches.filter()to get a new list containing only matching errors.
- The
FailedResultExceptionnow has a message when the result has an error code but no error message. - The
FakeExceptionReporternow hasnone()andexpect()helper methods, to prevent exceptions from being swallowed in tests. - BREAKING: Changes to the result interface to support the following features. Although technically breaking, this
will not affect the majority of implementations are the concrete result class provided by this package has been
updated.
- Can now pass a default value to the
Result::error()method. This default value can either be a string or a closure that receives the error code. (Errors always have codes if they do not have messages.) - Added
Result::code()method to get the first error code in the result's errors.
- Can now pass a default value to the
- The
ListOfErrors::contains()method is deprecated and will be removed in 6.0. Use the newany()method instead. - Calling the
ListOfErrors::first()method with arguments is deprecated and will be removed in 6.0. Use the newfind()method instead.
5.0.0-rc.1 - 2025-10-09
- BREAKING: The
Identifierinterface now has anany()method that returnstrueif any of the given identifiers match the identifier. If you have implemented any custom identifiers, you can add theIsIdentifiertrait to implement this new method. - BREAKING: Add
message()andmessages()helper methods to theErrorListinterface. Although technically breaking, this will not affect most implementations as the concrete error list class provided this package has been updated.
4.1.0 - 2025-10-03
- Log context for objects that have date time properties will now convert those properties to strings.
- Log context for objects that have date time zones will now convert the time zone to its name.
4.0.0 - 2025-09-12
- BREAKING: Added
code()method to theListOfErrorsinterface. This returns the first error code in the list, ornullif there are no error codes.
- BREAKING: The constructor argument order for the
Errorclass has changed. The new order iscode,message,key. Previously it waskey,message,codebecause code was added later. However, in most instancescodeis the most important property as it is used to programmatically detect specific error scenarios. This hopefully will not be breaking, as the docs specified that named arguments should be used when constructing error objects. - BREAKING The error and error list interfaces now accept
UnitEnuminstead ofBackedEnumfor error codes. Although technically breaking, this will only affect your implementation if you have implemented these interfaces. All concrete classes provided by this package have been updated. - BREAKING: The key of an error can now be an enum - previously only strings were accepted. This is only breaking if you have implemented the interface yourself.
- Updated
KeyedSetOfErrorsto handle error keys now being strings or enums. - BREAKING: The
Guid::make()method will now convert a string that is a UUID to a UUID GUID. Previously it would use a string id. - BREAKING: Updated the
GuidTypeMapclass so that it now supports enum aliases, enum types, and UUID identifiers. This means thetype()method now returns a string or enum, whereas previously it returned just a string. - Object log context now casts Ramsey UUIDs and enums to strings.
- Package no longer supports PHP 8.1. The minimum supported version is now PHP 8.2.
- BREAKING: Removed the
Guid::type()method - useenum_value($guid->type)orenum_string($guid->type)instead. - Removed the following classes that were deprecated in 3.4:
LazyListOfGuidsLazyListOfIdentifiersLazyListOfIntegerIdsLazyListOfStringIdsLazyListOfUuidsListOfIdentifiers
3.4.0 - 2025-09-09
- All identifier list classes are deprecated and will be removed in 4.0. These were not documented and are being removed
as they contain very limited functionality. Implementations can ship their own identifier list classes depending on
their unique needs. The classes deprecated are:
LazyListOfGuidsLazyListOfIdentifiersLazyListOfIntegerIdsLazyListOfStringIdsLazyListOfUuidsListOfIdentifiers
3.3.1 - 2025-08-10
- Ensure that the
Resultclass uses@template-covariantfor its result value. This now matches the result interface, which already used template covariant.
3.3.0 - 2025-05-24
- New
enum_valueandenum_stringhelper functions for getting a scalar or string value from an enum. - The
Guid::fromUuid()method now also accepts aUuidobject, so that it can be used to create a GUID from a UUID identifier. Previously it required a Ramsey UUID object.
3.2.0 - 2025-04-19
- New
ApplicationExceptionfor errors originating from the application layer. - Can now pass a closure to the
Contracts::assert()method for the message. The closure is only invoked if the precondition fails.
- Make the
idproperty on theIsEntitytrait readonly. This is considered non-breaking because theIsEntitytrait is expected to be used on an entity that always has an identifier, so the id should be set via the constructor. This fixes a bug where the trait could not be used on a readonly class.
3.1.0 - 2025-02-15
- Can now provide an enum as the type of the GUID identifier.
- Can now provide multiple types when checking if a GUID is of a type - the
Guid::isType()method. It returnstrueif any of the provided types match the type of the GUID. - The
LazyListOfGuidsiterator now has anonly()method. This can be provided a list of types, and the iterator will yield only GUIDs with a matching type.
3.0.0 - 2025-01-29
- The
Uuidclass now has a statictryFrom()method. This will returnnullif the value provided cannot be cast to a UUID identifier. - New
PsrLogExceptionReporterprovides a default exception reporter implementation that logs the exception as an error. - Updated doc block for
Contracts::assert()to add PHPStan assertion that the precondition istrueif the method call does not throw. - The following fake classes are now countable, with the count representing the number of items they have captured:
Testing\FakeDomainEventDispatcherTesting\FakeExceptionReporterTesting\FakeOutboundEventPublisherTesting\FakeQueue
- New
ContextFactoryinterface for converting messages and result objects to log context. - New
Contextualinterface for converting value objects to log context. This is now extended by theIdentifierinterface. - All middleware that log messages are now injected with the new log context factory class. This allows the conversion of messages and result objects to be customised by writing an implementation of this interface. This dependency injection is optional, as the package provides its own implementation that is used by default.
- BREAKING The
ObjectContextclass has been renamed toObjectDecoratorand the staticfrom()method has been removed. Use the newContextFactoryimplementation instead. - BREAKING The
ResultContextclass has been renamed toResultDecoratorand the staticfrom()method has been removed. Use the newContextFactoryimplementation instead.
3.0.0-rc.2 - 2025-01-18
- New test classes for driven ports and the domain event dispatcher. These are intended to make setting up unit and
integration tests easier. They can also be used as fakes while you build your real implementation. The classes are in
the
Testingnamespace and are:Testing\FakeDomainEventDispatcherTesting\FakeExceptionReporterTesting\FakeOutboundEventPublisherTesting\FakeQueueTesting\FakeUnitOfWork
- Properties on message classes can now be marked as sensitive so that they are not logged. This is an alternative to
having to implement the
ContextProviderinterface. Mark a property as sensitive using theCloudCreativity\Modules\Toolkit\Loggable\Sensitiveattribute.
3.0.0-rc.1 - 2025-01-12
- Commands can now be queued by the presentation and delivery layer via the
CommandQueuerport. Refer to the command documentation for details.
- BREAKING Moved the
Message,Command,Query, andIntegrationEventinterfaces to theToolkit\Messagesnamespace. This is to make it clearer that these are part of the toolkit, not the application or infrastructure layers. It matches theResultandErrorinterfaces that are already in theToolkit\Resultnamespace. I.e. now the toolkit contains both the input and output interfaces.
- BREAKING The
CommandDispatcherdriving port no longer has aqueue()method. Use the newCommandQueuerport instead.
2.0.0 - 2024-12-07
- BREAKING Removed the sub-namespaces for ports provided by this package, i.e.:
Contracts\Application\Ports\Drivenall interfaces are no longer in sub-namespaces; andContracts\Application\Ports\Drivenalso has the same change.
- BREAKING Renamed the
InboundEventBus\EventDispatcherport toInboundEventDispatcher. - BREAKING Renamed the
OutboundEventBus\EventPublisherport toOutboundEventPublisher. - Upgraded to PHPStan v2.
- The result class now has a
Result::fail()static method to create a failed result. This is an alias of the existingResult::failed()method. - BREAKING The
Entityinterface (and therefore theAggregateinterface too) now has agetIdOrFail()method on it. Although technically breaking, if you are using theIsEntityorIsEntityWithNullableIdtraits then this method is already implemented. - New
AggregateRootinterface so that an aggregate root can be distinguished from a regular aggregate or entity.
- Remove deprecation message in PHP 8.4.
- The
Uuididentifier class now has agetBytes()method - Can now get a nil UUID from the
Uuid::nil()static method.
- Made resolution of inner handlers lazy in all buses. In several the handler was immediately resolved, so that the
handler middleware could be calculated. Buses that support handler middleware now first pipe through the bus
middleware, then resolve the inner handler, then pipe through the handler middleware. This allows inner handler
constructor injected dependencies to be lazily resolved after the bus middleware has executed. This is important when
using the setup and teardown middleware for bootstrapping services that may be injected into the inner handler. Buses
that now lazily resolve inner handlers are:
- Command bus
- Query bus
- Inbound integration event bus
- Outbound integration event bus
- Queue bus
Refer to the Upgrade Guide.
- BREAKING The command bus interface now has a
queue()method. Our command dispatcher implementation has been updated to accept a queue factory closure as its third constructor argument. This is an optional argument, so this change only breaks your implementation if you have manually implemented the command dispatch interface. - The
FailedResultExceptionnow implementsContextProviderto get log context from the exception's result object. In Laravel applications, this means the exception context will automatically be logged. - The
Resultinterface has a newabort()method. This throws aFailedResultExceptionif the result is not successful. - The inbound integration event handler container now accepts an optional factory for a default handler. This can be
used to swallow inbound events that the bounded context does not need to consume. We have also added
a
SwallowInboundEventhandler that can be used in this scenario.
- BREAKING Package now uses a hexagonal architecture approach, which helps clarify the relationship between the
application and infrastructure layers. This means a number of interfaces have been moved to
the
Contracts\Application\Portsnamespace, with them differentiated between driving and driven ports. - BREAKING As a number of interfaces had to be moved to a
Portsnamespace, we've tidied them all up by removing theInterfacesuffix and moving them to aContractsnamespace. - BREAKING We've also removed the
Traitsuffix from traits. To avoid collisions with interfaces, we've used anIsprefix where it makes sense. For example,EntityTraithas becomeIsEntity. - BREAKING The
DomainEventDispatchingnamespace has been moved fromInfrastructuretoApplication. This was needed for the new hexagonal architecture approach, but also makes it a lot clearer that domain events are the way the domain layer communicates with the application layer. - BREAKING The event bus implementation has been split into an inbound event bus (in the application layer) and an outbound event bus (in the infrastructure layer). With the new hexagonal architecture, this changes was required because inbound events are received via a driving port, while outbound events are published via a driven port.
- BREAKING Refactored the queue implementation so that commands are queued. The queue implementation was previously not documented. There is now complete documentation in the Asynchronous Processing chapter Refer to that documentation to upgrade your implementation.
- BREAKING For clarity, the following classes have had their
pipelineconstructor argument renamed tomiddleware. You will need to update the construction of these classes if you are using named arguments:Application\Bus\CommandDispatcherApplication\Bus\QueryDispatcherApplication\InboundEventBus\EventDispatcherApplication\DomainEventDispatching\DispatcherApplication\DomainEventDispatching\DeferredDispatcherApplication\DomainEventDispatching\UnitOfWorkAwareDispatcher
- BREAKING The command and query validators have been merged into a single class -
Application\Bus\Validator. This is because there was no functional difference between the two, so this tidies up the implementation. - BREAKING Renamed the bus
MessageMiddlewareinterface toBusMiddlewareinterface. Also changed the type-hint for the message fromMessagetoCommand|Query. This makes this interface clearer about its purpose, as it is intended only for use with commands and queries - i.e. not integration event messages. - BREAKING the
ResultContextandObjectContexthelper classes have been moved to theToolkit\Loggablenamespace. - BREAKING The
Result::value()method now throws aFailedResultExceptionif the result is not successful. Previously it threw aContractException. - BREAKING The unit of work implementation has been moved to the
Application\UnitOfWorknamespace. Previously it was inInfrastructure\Persistence. This reflects the fact that the unit of work manager is an application concern. The unit of work interface is now a driven port.
- BREAKING The pipeline builder factory was no longer required, so the following classes/interfaces have been
deleted. Although breaking, this is unlikely to affect your implementation as these classes were only used internal
within our bus and dispatch implementations.
Toolkit\Pipeline\PipelineBuilderFactoryInterfaceToolkit\Pipeline\PipelineBuilderFactory
- BREAKING Removed the following previously deprecated event bus middleware:
LogOutboundIntegrationEvent- useInfrastructure\OutboundEventBus\Middleware\LogOutboundEventinstead.LogInboundIntegrationEvent- useApplication\InboundEventBus\Middleware\LogInboundEventinstead.
- BREAKING Removed the
Infrastructure::assert()helper. This was not documented so is unlikely to be breaking.
- New integration event middleware:
NotifyInUnitOfWorkfor notifiers that need to be executed in a unit of work. Note that the documentation for Integration Events incorrectly showed theExecuteInUnitOfWorkcommand middleware being used.SetupBeforeEventfor doing setup work before an integration event is published or notified, and optionally teardown work after.TeardownAfterEventfor doing teardown work after an integration event is published or notified.LogInboundEventfor logging that an integration event is being received.LogOutboundEventfor logging that an integration event is being published.
- The following integration event middleware are deprecated and will be removed in 2.0:
LogInboundIntegrationEvent: useLogInboundEventinstead.LogOutboundIntegrationEvent: useLogOutboundEventinstead.
- Allow an outbound integration event handler to implement a
publish()method. Thehandle()method is still supported, butpublish()makes more sense to describe what the handler does with the event it has been given.
- Added missing UUID 7 and 8 methods to the UUID factory interface.
- The
Result::error()method now correctly returns the first error message even if it is not on the first error in the list.
- BREAKING The following deprecated interfaces have been removed:
Bus\CommandInterfaceuseToolkit\Messages\CommandInterfaceinstead.Bus\QueryInterfaceuseToolkit\Messages\QueryInterfaceinstead.Bus\DispatchThroughMiddlewareuseToolkit\Messages\DispatchThroughMiddlewareinstead.Infrastructure\Log\ContextProviderInterfaceuseToolkit\Loggable\ContextProviderInterfaceinstead.
- New
FailedResultExceptionfor throwing result objects that have not succeeded.
- BREAKING: The
UnitOfWorkAwareDispatchernow queues deferred events to be dispatched before the unit of work commits. Previously it queued them for after the commit. This changes allows communication between different domain entities to occur within the unit of work, which is the correct pattern. For example, if an entity or aggregate root needs to be updated as a result of another entity or aggregate dispatching a domain event. It also allows an outbox pattern to be used for the publishing of integration events. This is a breaking change because it changes the order in which events and listeners are executed. Listeners that need to be dispatched after the commit should now implement theDispatchAfterCommitinterface.
- The
ExecuteInUnitOfWorkmiddleware now correctly prevents the unit of work committing if the inner handler returns a failed result. Previously the unit of work would have committed, which was incorrect for a failed result.
- New event bus notifier implementation that was previously missing. This completes the event bus implementation.
- New message interfaces (command, query, integration event) added to the toolkit.
- New loggable context provider interface added to the toolkit.
- Module basename now supports namespaces where an application only has a single bounded context.
- BREAKING Moved the following interfaces to the
Toolkit\Messagesnamespace:MessageInterfaceIntegrationEventInterface
- BREAKING Interfaces that type-hinted
Bus\CommandInterface,Bus\QueryInterfaceorBus\MessageInterfacenow type-hint the new interfaces in theToolkit\Messagesnamespace. - BREAKING Moved the
EventBusimplementation fromInfrastructure\EventBustoEventBus. In Deptrac, this namespace is now part of the Application Bus layer. Renamed the publisher handler and publisher handler containers to integration event handler and container - so that they can be used for both the publisher and notifier implementations. - BREAKING Removed the
EventBus\PublishThroughMiddlewareinterface. Use theToolkit\Messages\DispatchThroughMiddlewareinterface instead.
- BREAKING removed the
deptrac-layers.yamlfile, in favour of applications including the classes in their own Deptrac configuration.
- The
Bus\CommandInterface,Bus\QueryInterfaceandBus\DispatchThroughMiddlewareinterfaces have been deprecated in favour of the new interfaces in theToolkit\Messagesnamespace. - The
Infrastructure\Log\ContextProviderInterfaceis deprecated in favour of the newToolkit\Loggable\ContextProviderInterfaceinterface.
- Removed
finalfrom theDeferredDispatcherandUnitOfWorkAwareDispatcherclasses so that they can be extended.
- New
DeferredDispatcherclass for dispatching domain events when not using a unit of work. - New UUID factory interface and class, that wraps the
ramsey/uuidfactory to return UUID identifiers. - GUIDs that wrap UUIDs can now be created via the static
Guid::fromUuid()method. - New
SetupBeforeDispatchandTearDownAfterDispatchbus middleware, that can be used either to set up (and optionally tear down) application state around the dispatching of a message, or to just do tear down work. - The
EventBusnamespace now has a working implementation for publishing integration events. - Can now provide a closure to the
ListOfErrorsInterface::first()method to find the first matching error. - Added the following methods to the
ListOfErrorsInterface:contains()- determines whether the list contains a matching error.codes()- returns an array containing the unique error codes in the list.
- Added an
ErrorInterface::is()method to determine whether an error matches a given code.
- BREAKING - renamed the domain event
Dispatcherclass toUnitOfWorkAwareDispatcher. - BREAKING - removed the
IntegrationEventsnamespace and moved to theInfrastructure\EventBusnamespace. - BREAKING - the
IntegrationEventInterfacenow expects the UUID to be an identifier UUID, not a Ramsey UUID. - The UUID factory from the
ramsey/uuidpackage is now used when creating new UUID identifiers.
- The unit of work manager now correctly handles re-attempts so that deferred events are not dispatched multiple times.
- New
LazyListOfIdentifiersclass for lazy iteration over a list of identifiers. - Log context for a result now includes the value if it is a scalar value (string, integer, float, or boolean).
- BREAKING: add the
$stackproperty to theListTraitandKeyedSetTraittraits, and use generics to indicate the value they hold. This is breaking because it will cause PHPStan to fail for existing classes the use these traits. - BREAKING: renamed the
LazyIteratorTraittoLazyListTraitand defined its values using generics.
- Log context for a result now includes the value if it implements
ContextProviderInterfaceorIdentifierInterface. - BREAKING: added a
safemethod to theResultInterface, that gives access to the result value without throwing an exception if the result is an error.
- Remove
EntityTrait::getId()nullable return type as it is always set. - Fix generic return type on
Result::ok()method.
- BREAKING: moved the
Bus\Resultsnamespace toToolkit\Result. As part of this move, the interfaces and classes in this namespace no longer implement the logContextProviderInterface, as this is an infrastructure dependency. Instead, the newInfrastructure\Log\ObjectContextandInfrastructure\Log\ResultContextclass can be used to create context for either a result or an object. - All constructor arguments for the
Toolkit\Result\Errorobject are now optional. This allows named arguments to be used when creating an error object. - The
Toolkit\Result\Errorobject can now accept only a code, previously it had to have a message. - The following interfaces no longer extend the log
ContextProviderInterface. Instead, classes only need to implement that log interface if they need to customise how that class is logged.Bus\MessageInterfaceInfrastructure\Queue\QueueableInterface
- The command and query validators now allow rules to return
nullto indicate no errors. - The following dispatchers now accept pipeline builder factories or pipeline containers into their constructor. This
simplifies creating them, as in most cases a pipeline container can be provided from a dependency helper.
CommandDispatcherQueryDispatcherDomainEventDispatching\DispatcherQueue
- BREAKING: changed the
ErrorIterableInterfacetoListOfErrorsInterface. Result objects now only accept list of errors. TheKeyedSetOfErrorsclass can be used to convert a list into a keyed set if needed. This helps simplify the error handling logic, which was overly complex by having a generic error iterable interface that could either be a list or a keyed set. - BREAKING: The error interface no longer extends
Stringable. Use$error->message()instead, or compose a string from multiple properties of the error object. - BREAKING: The code on an error object is now type-hinted as a
BackedEnumornull- previously it wasmixed. Error codes should be from a defined list, therefore an enum is the correctly defined type. - BREAKING: The
PipelineBuilderFactory::cast()method has been renamedmake().
- BREAKING: removed the
IterableInterfaceas there is no need for a list and a keyed set to inherit from the same interface.
Initial release.