Download the PHP package tiny-blocks/building-blocks without Composer

On this page you can find all versions of the php package tiny-blocks/building-blocks. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package building-blocks

Building Blocks

License

Overview

The Building Blocks library provides the tactical design building blocks of Domain-Driven Design: Entity, Identity, AggregateRoot, and the infrastructure required to carry domain events through a transactional outbox or an event-sourced store.

This library implements the tactical patterns from Evans (Entity, Identity, Aggregate Root, Value Object) and Vernon (Domain Event) together with pragmatic extensions that production code needs but the original DDD literature does not address: aggregate versioning for optimistic offline locking (Fowler PEAA), model versioning and rolling snapshots for event-sourced aggregates (Greg Young), event upcasting for schema evolution (Greg Young), and an event envelope decoupling domain events from infrastructure metadata (Hohpe/Woolf EIP). Every extension is annotated in its own PHPDoc with its source.

Domain events defined here are plain PHP objects fully compatible with any PSR-14 dispatcher. The library does not replace PSR-14, it defines what flows through it. Serialization to wire formats is delegated to adapters such as tiny-blocks/outbox.

Installation

How to use

The library exposes three styles of aggregate modeling through sibling interfaces:

Entity

Every entity declares which property holds its Identity. By default, the property is named id, aggregates with a differently named property override identityProperty().

Single-field identity

Compound identity

Identity access on entities

Aggregate

AggregateRoot adds two pragmatic fields to Evans' aggregate: a monotonic AggregateVersion for optimistic concurrency control, and a ModelVersion for schema evolution of the aggregate type.

Domain events with transactional outbox

EventualAggregateRoot records domain events during the unit of work. State is the source of truth, events are emitted as side effects and must be delivered at-least-once.

After persisting the aggregate state, the application service drains the recorded events with pullEvents(), which returns them and clears the buffer, so a second save of the same instance does not re-emit the events already drained. peekEvents() returns a non-destructive copy for inspection without touching the buffer. An instance models a single unit of work: reload from the repository before operating on the same logical aggregate again rather than reusing a drained instance.

Declaring events

Emitting events from the aggregate

Draining events

Restoring aggregate version on reload

Constructing event records directly

Every envelope carries $id, $event, $revision, $eventType, $occurredAt, $aggregateId, $aggregateType, and $aggregateVersion. The aggregate normally builds the record, so consumers read these fields off EventRecord directly without instantiating one.

Integration events and the Anti-Corruption Layer

DomainEvent describes facts that happened inside the bounded context and evolves freely with the internal model. IntegrationEvent describes the stable public contract that flows to external consumers and must remain backward-compatible. The two interfaces are siblings, not parent and child. An IntegrationEvent is produced by an IntegrationEventTranslator, which acts as the Anti-Corruption Layer (Vernon, IDDD Chapter 3) between the internal model and the public contract.

Declaring integration events

Class names for integration events must follow the bounded-context ubiquitous language and must not carry a technical suffix such as IntegrationEvent. The domain event TransactionConfirmed is translated into the integration event PaymentConfirmed, not PaymentConfirmedIntegrationEvent.

Bumping the public schema revision independently of the underlying domain event:

Writing a translator

IntegrationEventTranslator is the Anti-Corruption Layer seam. Each implementation declares which EventRecord it handles via supports() and produces the corresponding IntegrationEvent via translate(). Implementations must be pure functions with no side effects or I/O.

Registering translators

IntegrationEventTranslators is an ordered collection of translators. findFor() returns the first translator whose supports() returns true for a given record, or null when no translator handles it. A null result is the canonical signal that the event is purely internal and must not cross the bounded-context boundary.

Constructing integration event records directly

IntegrationEventRecord::from() envelopes a translated integration event with the transport metadata from the originating EventRecord. The identifier is reused from the originating record so that outbox relay retries remain idempotent. The revision and event type are derived from the integration event, not from the domain event.

Parameter Type Description
eventRecord EventRecord Originating domain event record. Supplies transport metadata.
integrationEvent IntegrationEvent Integration event produced by the translator. Supplies payload and public schema.

Event sourcing

EventSourcingRoot stores no state of its own, state is derived by replaying the event stream.

Applying events to state

Creating a blank aggregate

Replaying an event stream

Snapshots

Snapshots let the event store skip replay of early events when reconstituting a long-lived aggregate. A snapshot captures the aggregate's state at a specific version so that reconstitution can resume from that point instead of replaying the entire history.

Capturing aggregate state

Aggregates control what fields enter the snapshot by overriding snapshotState(). The default captures every declared property except recordedEvents and aggregateVersion (which are tracked separately on the envelope).

Taking a snapshot

Persisting snapshots

Built-in conditions

Upcasting

Upcasters migrate serialized events across schema changes without touching the event classes.

Defining an upcaster

Chaining upcasters

Default values for new fields

FAQ

01. Why is DomainEvent close to a marker interface?

A domain event is a fact about something that happened in the domain. The contract carries only revision() so the library can route schema migrations through upcasters. Everything else (aggregate identity, aggregate version, aggregate type, occurrence timestamp) is envelope metadata that belongs to EventRecord. Keeping the event itself minimal prevents infrastructure concerns from leaking into the domain model.

Vaughn Vernon, Implementing Domain-Driven Design (Addison-Wesley, 2013), Chapter 8, "Domain Events".

02. Why does EventualAggregateRoot store EventRecord instead of DomainEvent?

Only the aggregate has the context needed to build the complete envelope: identity, aggregate version, aggregate type name. Storing raw events and wrapping them later would either duplicate that context or require a second pass. pushEvent() builds the full EventRecord immediately, and the outbox adapter reads them as-is with no translation.

Gregor Hohpe and Bobby Woolf, Enterprise Integration Patterns (Addison-Wesley, 2003), "Envelope Wrapper".

03. Why are EventualAggregateRoot and EventSourcingRoot siblings instead of a hierarchy?

Outbox and event sourcing are mutually exclusive persistence strategies. An aggregate either persists its state and emits events as side effects, or persists only its events as the source of truth. A common base beyond AggregateRoot would imply the two patterns can coexist on the same aggregate, which they cannot.

Martin Fowler, Event Sourcing (martinfowler.com, 2005). Chris Richardson, Microservices Patterns (Manning, 2018), Chapter 3, "Transactional Outbox".

04. Why does Revision live on the DomainEvent instead of the call site?

The revision of an event is a property of the event's schema. Keeping it on the event means the call site (pushEvent, when) does not need to know the schema version, the event class is the single source of truth. Bumping a revision is always paired with a payload change (added field, removed field, renamed field), so creating a new event class to carry the new revision is the natural unit of work.

Greg Young, Versioning in an Event Sourced System (Leanpub, 2017).

05. Why does blank() skip the constructor?

EventSourcingRootBehavior::blank() instantiates the aggregate via reflection without invoking its constructor because all aggregate state in an event-sourced model must come from events or from a snapshot. Any invariants established by the constructor would contradict that principle. Concrete aggregates should treat their constructor as private and reserved for internal use during command handling.

Greg Young, CQRS Documents (2010), "Event Sourcing" section.

06. Why doesn't the library serialize envelopes to JSON or any other wire format?

Serialization is an infrastructure concern. Putting encoding methods on domain value objects mixes that concern into the domain layer, which contradicts the library's persistence-agnostic stance. Adapters such as tiny-blocks/outbox provide dedicated serializer ports. The domain layer exposes EventRecord, Snapshot, and the value objects as pure data, downstream adapters decide how to map them onto bytes.

Alistair Cockburn, Hexagonal Architecture (alistair.cockburn.us, 2005).

07. What is the difference between ModelVersion and AggregateVersion?

AggregateVersion counts events per aggregate instance. It is the basis for optimistic concurrency control: a save fails if the aggregate version in storage differs from the in-memory version the aggregate believed it had.

ModelVersion versions the aggregate type itself. When the aggregate schema changes in a backwards-incompatible way (a property is removed, renamed, or its semantics shift), bumping the model version gives migration code a single source of truth to branch on.

The two are different concepts that happen to share an integer representation. They are typed as separate value objects to prevent accidental comparisons across them at compile time.

Martin Fowler, Patterns of Enterprise Application Architecture (Addison-Wesley, 2002), "Optimistic Offline Lock", source of AggregateVersion semantics. Greg Young, Versioning in an Event Sourced System (Leanpub, 2017), source of ModelVersion semantics.

08. How are recorded events drained from an EventualAggregateRoot?

After the aggregate state has been persisted, the application service calls pullEvents(), which returns the events recorded since the last drain and clears the buffer. Draining through pullEvents() publishes each event once: a second save of the same instance finds an empty buffer and re-emits nothing. peekEvents() is the non-destructive counterpart, returning a fresh copy for inspection (in tests, for example) while leaving the buffer intact.

An instance models a single transactional unit of work. Reload from the repository before operating on the same logical aggregate again rather than reusing a drained instance, so its aggregate version and state reflect what storage holds.

Eric Evans, Domain-Driven Design (Addison-Wesley, 2003), Chapter 6, "Aggregates" (single transactional unit per aggregate per request).

09. Should I add identity(), aggregateType(), or toArray() to my DomainEvent?

No. These three concerns live elsewhere:

A DomainEvent that grows methods like these duplicates envelope data already on the EventRecord and pulls infrastructure into the domain layer.

10. Why does the library include AggregateVersion and ModelVersion if Evans never mentioned them?

Evans defined the tactical patterns of DDD, but optimistic concurrency control and aggregate schema evolution are concerns that emerged later in mainstream production code. AggregateVersion carries the optimistic offline lock formalized by Fowler in PEAA: the value travels with the aggregate, the persistence adapter compares the in-memory value against the stored one, and a mismatch raises a concurrency exception instead of overwriting another process's change. ModelVersion carries Greg Young's schema versioning for aggregate types, so migration code has a single source of truth to branch on when older shapes show up in storage.

Martin Fowler, Patterns of Enterprise Application Architecture (Addison-Wesley, 2002), "Optimistic Offline Lock". Greg Young, Versioning in an Event Sourced System (Leanpub, 2017).

11. Why are reconstitutePartial() and

reconstituteStrict() static on the interface even though PHP's polymorphism for static methods is limited?

The interface declaration documents the contract: every EventualAggregateRoot exposes two static factories with the shape (Identity, array, AggregateVersion): static that repositories can call. PHP does not dispatch static calls through interfaces at runtime, so the consumer always names the concrete class (Order::reconstituteStrict(...), Reservation::reconstitutePartial(...)). The interface still earns its keep: it forces aggregates to expose the factories, the trait default provides both for free (reconstituteStrict delegates to reconstitutePartial), and overrides remain bound to the declared signature. The parameter name is free per LSP, so an override of reconstitutePartial can rename $identity to $orderId for readability, but the type must remain Identity, narrowing to a concrete identity class would break LSP. Concrete types are enforced inside the override with instanceof.

Barbara Liskov and Jeannette Wing, A Behavioral Notion of Subtyping (ACM TOPLAS, 1994).

12. Why was reconstituteAggregateVersion() removed?

It was never part of the external contract. The only caller was the trait's own reconstitute() factory, which needed to set the aggregate version on the instance it had just built. Exposing that internal step as a public instance method invited misuse (repositories calling it on aggregates they had not just reconstituted) without adding any expressiveness over assigning the property directly. The factory now writes $aggregate->aggregateVersion directly inside the trait, which is legal because the assignment happens in the static method of the same class after the trait flattens into the aggregate. Eliminating the public method tightens the surface and removes the documentation burden of explaining when calling it is correct.

13. Why are DomainEvent and IntegrationEvent siblings instead of parent and child?

Domain events evolve freely with the internal model. Integration events are a public contract that must remain backward-compatible across bounded-context consumers. A parent/child relationship would make every domain event eligible to cross the bounded-context boundary by virtue of typing, reintroducing the very coupling the distinction exists to eliminate. Sibling interfaces force the boundary crossing to be an explicit translation step, observable in the type system. There is no accidental publication: the compiler rejects a DomainEvent where an IntegrationEvent is expected, and the IntegrationEventTranslator is the only path between the two.

Vaughn Vernon, Implementing Domain-Driven Design (Addison-Wesley, 2013), Chapter 3, "Context Maps".

14. Why doesn't the library let me publish a DomainEvent directly through the outbox?

The Anti-Corruption Layer exists precisely to keep the public contract isolated from the internal model. A shortcut that lets a domain event become an integration event without an explicit translation step erases that boundary.

Without translation, internal model refactors propagate silently to external consumers. A renamed field or a new value object on a domain event changes the published payload with no compile-time signal. Consumers break at runtime, not at the CI boundary where the change was introduced.

Domain events are versioned by the internal model; integration events are versioned by the public contract. Coupling them forces a single revision counter to serve two evolution speeds, which collapses the ability to evolve each side independently.

Even when a domain event and an integration event happen to share the same shape today, the cost of writing a translator that copies fields is a few seconds per event. In return: static analysis flags drift between the two shapes, refactor pressure surfaces in CI as a compile error inside the translator, and the public contract is locatable as a single namespace in the codebase.

Vaughn Vernon, Implementing Domain-Driven Design (Addison-Wesley, 2013), Chapter 3, "Context Maps", section "Anticorruption Layer".

License

Building Blocks is licensed under MIT.

Contributing

Please follow the contributing guidelines to contribute to the project.


All versions of building-blocks with dependencies

PHP Build Version
Package Version
Requires php Version ^8.5
ramsey/uuid Version ^4.9
tiny-blocks/collection Version ^2.5
tiny-blocks/mapper Version ^3.1
tiny-blocks/time Version ^2.3
tiny-blocks/value-object Version ^5.0
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package tiny-blocks/building-blocks contains the following files

Loading the files please wait ...