darangonaut/laravel-doctrine-projections
A Laravel package for Doctrine projections that helps you build and maintain read models from domain events. It integrates projection classes with Laravel’s container and console, making event-sourced read-model setup and rebuilding straightforward.
Baseline: no prior assessment existed; this establishes the first baseline at v0.7.0. Prior versions should be considered unfit for use — v0.7.0 fixes two silent-failure classes that would have shipped broken systems.
Architecture fit. The package is a build-time generator: it reads Doctrine ORM mapping metadata and emits Eloquent model classes ("projections") plus migrations. That is a good architectural shape for our context if — and only if — we have a Doctrine-mapped domain layer and want an Eloquent query surface over it without hand-maintaining a parallel set of models. The strongest signal in the release notes is that generation reads mapping, not schema, and needs no database (pinned by test; 200 entities in 0.03s). That makes generation deterministic, cheap, and runnable in any CI job without a service container. It also means generated output can be diffed and reviewed like source.
Integration feasibility. Low technical effort, moderate verification effort. Constraints are explicit and checkable: PHP 8.3/8.4, Laravel 12/13, SQLite/MySQL 8.4/PostgreSQL 16. The Eloquent surface we would actually depend on — chunk, cursor, lazy, paginate, route model binding, replicate, toArray, toJson — is verified under Model::shouldBeStrict(), which is the right test posture and materially raises confidence in the generated runtime behavior.
Technical risk. This is a minor version with three deliberate refusals (namespace collision with entity namespace, case-only-differing short names, migration overwrite) — each documented in UPGRADING.md, each turning a previously silent mis-generation into a hard failure. For a 0.x package this is legitimate, but it means pinning is mandatory and CI pipelines that previously passed may now fail.
The two substantive fixes define the real risk picture:
| Fixed defect | Failure mode | Exposure if we ran < 0.7.0 |
|---|---|---|
| Abstract STI class had no scope | Returned every row in the table (folder appeared as a file) | Wrong query results, potential cross-type data exposure |
Failed write reported as success (File::put() false / raw ErrorException) |
Printed "3 projection(s) generated", exit 0 | Green deploy with no models; total app breakage post-deploy |
Both are the "silent" category, and both are exactly the kind of failure a TPM must assume exists in any code-generation tool until proven otherwise. The remaining risk is the three mapping patterns documented rather than guarded (non-backed enum, enumType on a json column, discriminator column mapped as a field). These will produce broken projections at runtime with no generator complaint — so they must be covered by our own mapping lint, not by trust.
Key questions for the maintainer/team:
doctrine_diff default?Stack fit. Clean, provided we are on Laravel 12/13 and PHP 8.3+. The verified database matrix matches ours. The dependency on Doctrine mapping metadata is the gating prerequisite — if the entity layer is not Doctrine, the package is not applicable.
Migration path. Pin 0.7.x (not a floating ^0.7) and upgrade on a branch. The critical step is regenerate everything and diff the artifacts. Two categories of diff will appear:
Compatibility. No API surface is documented as removed; the breakage is in generator acceptance criteria, not in a public PHP API. That is the cheapest kind of break — it fails at build time, not at runtime. Still treat it as breaking for our pipeline contract.
Sequencing.
Model::shouldBeStrict() enabled in staging; add targeted query-count/row-count assertions for each STI hierarchy.Maintenance. Generated artifacts become a maintained surface. Every Doctrine mapping change requires regeneration and PR review of the diff. Keep projections in a dedicated directory and namespace, treat the output as vendored code, and never hand-edit it.
Support. 0.x with a single maintainer — the classic bus-factor exposure. Mitigating evidence is strong: 211 tests, PHPStan at max with no baseline, and a real OS/DB/PHP matrix. That is well above median for a 0.x package, but it does not remove the need for a pilot and an exit plan (worst case: freeze the generator, commit the output, and own the generated models).
Scaling. Generation cost is negligible (200 entities / 0.03s) and does not grow with data volume. Runtime scaling risk sits in the generated Eloquent models: STI scopes add a discriminator predicate to every query, so ensure the discriminator column is indexed on single-table hierarchies. chunk/cursor/lazy being verified means large-table iteration paths are usable.
Failure modes to instrument.
enumType on json, and discriminator-as-field is mandatory before onboarding new entities.Ramp-up. Low for anyone fluent in Laravel and Doctrine — roughly a day to first generated projections and half a day of team documentation on the projection namespace convention, migration generation, and STI scoping rules. Budget an extra half day for the case-sensitivity divergence between macOS/Windows developer machines and Linux CI, which is the least intuitive of the new refusals.
Recommendation. Adopt at exactly 0.7.x for a scoped pilot on one or two bounded domains, with the generation CI gate and mapping lint in place from day one. Do not adopt below 0.7.0 under any circumstance, and do not adopt 1.0+ without a stated support policy.
How can I help you explore Laravel packages today?