Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Domain Driven Design Laravel Package

php-ddd/domain-driven-design

php-ddd/domain-driven-design helps Laravel/PHP developers structure applications around bounded contexts and domain logic, with reusable DDD building blocks and conventions for entities, value objects, aggregates, repositories, services, and domain events.

Deep Wiki
Context7

Getting Started

This is a building-blocks library, not a framework — it gives you base classes/interfaces for Entities, Value Objects, Aggregates, Domain Events, Repositories, and Specifications, and you wire them into your own app. Treat it as reference material more than a dependency you build on.

Minimal steps:

  1. Check installability first. The package is archived and last released 2015-08-15, so its composer.json almost certainly pins PHP 5.x-era constraints. Try composer require php-ddd/domain-driven-design in a scratch project; if the constraint solver refuses, fork it, relax the php and phpunit constraints, and require it as a VCS/path repository. Do this before designing anything around it.
  2. Read src/ before the README. Docs from that era are thin. The namespace layout is the real documentation: look for Entity, ValueObject, Aggregate/AggregateRoot, DomainEvent, Repository, Specification, and any Exception folders. Each base class tells you what the author considered mandatory (usually identity comparison and equality semantics).
  3. Read the tests. For an abandoned package the test suite is the only reliable spec — especially for equals() behavior on value objects and event collection on aggregates.

First use case: model a single aggregate end-to-end rather than adopting the whole package at once.

final class Money implements ValueObject
{
    private function __construct(
        private int $amount,
        private string $currency
    ) {}

    public static function fromInt(int $amount, string $currency): self
    {
        return new self($amount, $currency);
    }

    public function equals($other): bool
    {
        return $other instanceof self
            && $this->amount === $other->amount
            && $this->currency === $other->currency;
    }
}

Then an aggregate root that records a domain event, and a repository interface in your domain layer. That triangle (Value Object → Aggregate + Event → Repository interface) is the fastest way to judge whether the package's opinions match yours. If they don't, extract the ideas and drop the dependency — you'll lose very little.

Implementation Patterns

Value Objects for money, IDs, and email. Extend the package's value object base (or follow its contract) so equals() is centralized. Prefer named constructors (fromString, fromInt) over public constructors, and keep them immutable — no setters, no public properties.

Entities compared by identity, not attributes. The package's entity base typically exposes identity-based equality. Keep your equals()/sameIdentityAs() on the base class semantics and never override it with attribute comparison for persisted entities.

Aggregate roots as the only mutation entry point. Children (order lines, etc.) get mutated only through root methods. The root validates invariants and records domain events; children never dispatch their own events. This is where a building-blocks library earns its keep — it's a checklist, not machinery.

Collect domain events on the aggregate, dispatch them at the boundary. The library gives you the event objects and probably a recorder on the aggregate root; it does not give you a dispatcher. In Laravel:

DB::transaction(function () use ($order) {
    $this->orders->save($order);          // repository impl
    $events = $order->releaseEvents();    // drain the recorder
});

foreach ($events as $event) {
    event($event);                        // or dispatch to a bus
}

Draining after commit is the single most important integration decision. Dispatching inside the transaction means listeners can observe state that later rolls back.

Repository interface in the domain, Eloquent implementation in infrastructure. Bind in a service provider:

$this->app->bind(OrderRepository::class, EloquentOrderRepository::class);

The implementation maps between your aggregate and Eloquent models. If your aggregate is an Eloquent model, you've collapsed the layers and the package buys you nothing — pick one or the other.

Specifications for query composition. Use them for reusable domain rules (ActiveCustomerSpecification, OverdueInvoiceSpecification). In practice, translate a specification into a query builder in the repository implementation rather than filtering in memory; keep the specification object as the named, testable rule.

Factories when construction is non-trivial. If an aggregate needs several collaborators or a multi-step invariant check, put a named factory in the domain layer rather than a fat static constructor on the entity.

Gotchas and Tips

  • PHP version is the real blocker, not the API. 2015-era code predates scalar type hints, return types, strict_types, and nullable syntax; it may also use each(), create_function(), or other removed functions. Expect deprecation noise and possible fatals on PHP 8. Run the suite on your target version before committing to it.
  • No composer.lock safety net. Unmaintained packages drift against your other dependencies. Pin an exact version (or a commit hash from your fork) and record why you're carrying an abandoned dependency.
  • Domain events are in-memory only. There is no outbox, no retry, no delivery guarantee. If event loss matters, implement a transactional outbox table yourself; the package won't help.
  • Serialization surprises. Value objects with private constructors and no __serialize/__unserialize can break queue payloads and cache round-trips. Test serialization explicitly before pushing an aggregate onto a queue.
  • Don't let the base classes dictate your public API. Extending a third-party Entity couples your domain to the package forever. If you only need equals() semantics, copy the ten lines into your own base class — an archived dependency is not worth a permanent inheritance chain.
  • Bidirectional mapping is where bugs hide. Every repository implementation needs round-trip tests: save aggregate → reload → assert all value objects compare equal and no events fire spuriously on hydration.
  • Watch for accidental rehydration events. If your aggregate records events in the constructor or in setters used during hydration, reloading an entity will emit phantom events. Hydrate via a distinct, event-suppressing path.
  • Debugging tip: when equals() returns false unexpectedly, dump both sides' raw properties. Era-appropriate implementations often use loose == or serialize() comparison, which quietly treats 1 and "1" — or two objects with different class order — inconsistently.
  • Extension points are sparse. Where the library lacks something (specification-to-query translation, event dispatching, unit-of-work), build a thin adapter in your infrastructure layer rather than patching a fork you'll have to maintain.
  • Have an exit plan. Because the package is archived, wrap your usage behind your own small set of domain base classes from day one. That way replacing php-ddd/domain-driven-design later is a mechanical change in one directory instead of a rewrite across every aggregate.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle