geekcell/ddd-bundle
GeekCell DDD Bundle is a Laravel/PHP package for applying Domain-Driven Design with reusable building blocks and conventions that help organize domain, application, and infrastructure layers into cleaner, more maintainable code.
Premise correction (blocking): geekcell/ddd-bundle is a Symfony bundle, not a Laravel package. The repository name (ddd-symfony-bundle) confirms it. It ships a Bundle subclass, a DI extension, and compiler passes — none of which Laravel boots. There is no composer require path that yields working behavior in a Laravel app; at best the install is inert, at worst it drags symfony/framework-bundle into the dependency graph. Every "leverage" option below is therefore port-and-vendor, not adopt.
Architecture fit: Tactical DDD primitives (aggregate roots, value objects, domain events, repository interfaces, UUIDs) are conceptually framework-agnostic and map cleanly onto Laravel. The packaging is the mismatch. Fit is therefore conditional on how the package is internally split: if src/Domain is free of Symfony imports, we can lift it; if the primitives themselves extend or type-hint Symfony contracts, the extraction cost rises sharply and the value proposition collapses toward "read it as a reference implementation."
Integration feasibility: Low to moderate.
console, http-foundation, routing, error-handler, var-dumper), so some overlap may resolve — but a framework-bundle or http-kernel requirement is the dealbreaker, since it implies the full kernel.ServiceProvider with contextual binding.Technical risk: High.
Key questions for the spike (timebox: 2–3 days):
composer.json require symfony/framework-bundle or symfony/http-kernel? (Determines whether install is possible at all.)src/ is Symfony-coupled vs pure PHP domain code? Produce a file-level dependency map.illuminate/* set?Recommendation: Do not adopt as a dependency. Evaluate as a pattern reference and a candidate source of vendored primitives, benchmarked against framework-native and cross-framework alternatives before any code is written.
Stack fit. The bundle's four integration surfaces have direct Laravel counterparts, each requiring a rewrite rather than an adapter:
| Bundle mechanism (Symfony) | Laravel equivalent we must build |
|---|---|
Bundle class + Extension config tree |
ServiceProvider + config/ddd.php |
| Compiler passes for tagging/auto-wiring | Explicit bindings, ContextualBindingBuilder, or #[Singleton]-style attributes |
Symfony EventDispatcher |
Laravel dispatcher, with DB::afterCommit() for domain events |
| Bundle console commands | Artisan commands |
| Attribute-based repository resolution | Interface binding in the provider; no scanning |
Migration path (fork-and-port, gated):
internal/ddd (private VCS or path repository), preserving the license file and attribution. Never depend on the archived upstream.symfony/* runtime imports. Target: a src/Domain tree with zero non-stdlib dependencies.DomainServiceProvider: bind repository interfaces to Eloquent implementations, register command/query handlers, map domain events to Laravel listeners, and add an afterCommit dispatch decorator.Model events double-fire alongside domain events.Compatibility. Three independent version axes must be pinned and re-verified each quarter: our PHP version, our illuminate/* major, and whatever Symfony components remain in the dependency graph (Laravel's own transitive Symfony pins mean a forced major bump can ripple into the framework). If step 1–2 succeed, the Symfony axis disappears entirely — that is the primary argument for porting over adapting.
Sequencing and alternatives. Run the spike as a bake-off, not a single-track evaluation. In parallel, cost out three comparators: (a) hand-rolled primitives (~1 sprint for a small base library), (b) Ecotone, which is deliberately framework-agnostic and supports both Laravel and Symfony, and (c) spatie/laravel-event-sourcing if event sourcing is actually in scope. If (a) lands within roughly the same effort as the port, prefer (a): owning 300 lines of our own primitives beats owning a forked abandoned package. If (b) fits, prefer it: it buys the DDD messaging layer without the fork liability.
Explicit non-goal. Do not attempt to run the Symfony bundle inside Laravel via any shim. The failure mode is a half-booted kernel with silently unbound interfaces — the worst possible outcome, because it breaks at runtime rather than at install.
Maintenance. We become the sole maintainer of any forked code, permanently. Assign a named owner and add the fork to the same dependency-review cadence as first-party packages. Budget roughly 5–10% of one engineer's time per quarter for PHP/Laravel compatibility upkeep on the extracted primitives — small, but non-zero and permanent. The archived upstream means no rebase path; divergence is one-way.
Support. There is no vendor, no community to escalate to (13 stars, 0 dependents, archived), and likely no responsive issue tracker. All support is internal: a runbook covering the aggregate persistence pattern, the event dispatch lifecycle, and the repository binding convention, plus onboarding docs that explain why the code is forked so no one re-introduces the bundle. Factor documentation into the estimate — it is not optional here.
Scaling. The primitives themselves are negligible overhead; risk lives in the wiring we build around them.
Failure modes and mitigations:
| Failure mode | Impact | Mitigation |
|---|---|---|
| Composer resolves but bundle never boots | Silent no-op; interfaces unbound at runtime | Never install as a dependency; port only |
| Interface binding missed in the provider | BindingResolutionException in prod, not tests |
Bind in one provider; add a container-compile smoke test |
| Double event dispatch (Eloquent model events + domain events) | Duplicate side effects, duplicate notifications | Disable model events in repositories; single dispatch point via afterCommit |
| In-process dispatch loses events on transaction rollback or crash | Data inconsistency | Transactional outbox; afterCommit minimum |
| Symfony major drift via Laravel's transitive pins | Broken install mid-quarter | Zero Symfony imports in the extracted code; CI matrix across PHP versions |
| Fork diverges with no upstream | Stale security posture over time | Owner + quarterly review; treat as first-party code |
| Team applies DDD vocabulary without boundaries | Anemic or over-engineered domain layer | Pilot scoped to one context with a real invariant |
Ramp-up. Double learning curve: tactical DDD (aggregates, invariants, bounded contexts, domain events) and Symfony idioms for anyone reading the original source. For a Laravel-only team, expect 2–3 weeks before a mid-level engineer is productive in the ported code, and longer before they can make good boundary decisions — the framework part is teachable; the modeling part is not. Budget one senior engineer as the pattern owner for the first two quarters, and expect the pilot to surface at least one boundary that needs redrawing.
Net TPM read: low strategic value, non-trivial integration cost, permanent (if small) maintenance tail, and a hard architectural mismatch. Sanction the timeboxed spike only to harvest patterns and to make the build-vs-alternatives decision on evidence; do not plan a delivery milestone that depends on this package shipping into a Laravel service.
How can I help you explore Laravel packages today?