league/tactician-doctrine
Integrates the Tactician command bus with Doctrine via middleware that wraps command handlers in database transactions. It manages EntityManager/DBAL connections, helping keep command execution atomic and simplifying transactional command handling in PHP applications.
Architecture fit. league/tactician-doctrine is a thin integration layer that wraps command handlers in Doctrine ORM transactions. Operationally, it is a middleware plugin for Tactician: when a command is dispatched, the plugin opens a transaction before the handler runs and commits (or rolls back) afterward, with an option to flush the EntityManager on success. For a Laravel/PHP product this is architecturally a "command bus + unit-of-work" pattern bolted onto Laravel's own request lifecycle. Laravel already ships DB::transaction() and Illuminate\Bus\Dispatcher, so the package only makes sense if the team has consciously chosen Tactician as its command bus and wants transaction boundaries to live in bus middleware rather than in controllers or actions. It is not a general-purpose Doctrine integration and gives you nothing if you are on Eloquent.
Integration feasibility. Low-to-moderate effort, but entirely conditional on two preconditions being true: (1) Doctrine ORM is the persistence layer, and (2) Tactician is the command bus. Both are unusual in a Laravel codebase — Doctrine in Laravel typically arrives via laravel-doctrine/orm, and Tactician has largely lost mindshare to Laravel's native bus and to Symfony Messenger. Wiring is a handful of lines: register the plugin(s) on the command bus and provide an EntityManager (or a manager-name resolver). There is no service provider in the package, so a TPM should budget for a small adapter/provider plus configuration and a health check. This is glue code, not a platform.
Technical risk. The dominant risk is abandonment, not defects. Last release was January 2022; 58 stars and zero dependents mean effectively no community backstop. It is pinned to an older Tactician major, and Tactician itself has been quiet, so PHP 8.2/8.3 compatibility and Doctrine 3.x support are unverified — expect to fork or patch. Security risk is negligible (small surface, no I/O), but bus middleware that controls transaction boundaries is high-blast-radius: a subtle bug silently commits partial work or rolls back a handler that had already emitted a domain event. The "flush inside the transaction" option is the classic footgun — a failed flush surfaces after side effects have fired.
Key questions. Is Doctrine ORM genuinely in the stack, or is this a hypothetical? Is Tactician already the bus, or would this package be the reason to adopt it? What is the exit cost if the package is abandoned — can we inline the ~200 lines of transaction middleware ourselves? Do our handlers emit events or touch external systems before commit, and if so how do we defer them? What is the PHP/Doctrine version ceiling we must support, and has anyone validated it against our matrix? Is there a business reason to prefer this over DB::transaction() and Laravel's native bus?
Stack fit. Poor-to-conditional for a standard Laravel application. The native equivalents — DB::transaction(), DB::afterCommit(), Illuminate\Bus\Dispatcher middleware, and ShouldQueueAfterCommit — cover the same ground with first-party support, documentation, and a decade of forward compatibility. The package only fits if the organization has intentionally adopted Doctrine as its persistence abstraction (common in DDD-leaning shops migrating from Symfony) and Tactician as its bus. If either is Eloquent or the native bus, stop here; the integration cost is pure downside.
Migration path. There is no meaningful migration path offered by the package itself — it is not a bridge away from anything. Realistically the decision is between two states: adopt Tactician + this plugin as a deliberate architectural commitment, or skip the package and implement the equivalent transaction middleware in-house (roughly a day of work for a senior engineer, fully owned and testable). For most Laravel teams the correct migration is to not introduce this dependency and instead wrap command handlers in DB::transaction() at the dispatch site, or write a single custom bus middleware. If a legacy Symfony/Doctrine service must be folded into a Laravel monolith, the package could serve as a transitional shim — but even then, a local TransactionalMiddleware class is easier to reason about and to retire.
Compatibility. This is the gating item. Verify against the target PHP version (8.2+ in most shops now), Doctrine ORM 2.x vs 3.x, and the Tactician version the package pins. A 2022 release almost certainly predates Doctrine 3's stricter typing. Because there are zero dependents, no one else is validating this matrix for you — treat compatibility as an unproven assumption requiring a spike, not a documented fact.
Sequencing. If pursued: (1) spike — prove the plugin works on the target PHP/Doctrine/Tactician matrix with a trivial handler and an intentional rollback; (2) build the missing service provider and configuration, including which EntityManager and whether flush is enabled; (3) implement handlers with explicit awareness of commit boundaries — defer events and external calls to after-commit; (4) add failure-injection tests (handler throws, flush fails, nested transactions) before enabling on any revenue path; (5) plan the fork-and-vendor option up front. Sequence the spike before any architectural commitment to Tactician, not after.
Maintenance. The real cost. With no releases since early 2022, 58 stars, and zero dependents, the de facto maintainer is your team. Budget for an internal fork, a vendored copy, or periodic patches for PHP/Doctrine upgrades. That is a recurring tax on a component that provides perhaps 200 lines of behavior — a poor maintenance-to-value ratio when the first-party alternative is free. Any TPM should treat "we now own this" as the default assumption.
Support. Effectively none. No vendor, no active community, no security response channel. Escalation is your own engineers reading source. For a well-understood middleware class this is survivable; for anything touching transaction semantics on critical paths it means every incident starts from zero shared knowledge. Document the middleware's behavior internally as if it were custom code, because operationally it is.
Scaling. Neutral. Transaction middleware adds negligible overhead; throughput is governed by Doctrine's unit of work and the database, not this package. The scaling concern is organizational: as headcount grows, fewer engineers will know Tactician's dispatch model, and new hires arrive knowing Laravel's native bus. The pattern becomes a knowledge bottleneck that compounds with team size.
Failure modes. Enumerate them explicitly: handler succeeds but flush fails after side effects (the sharpest one — mitigate by deferring events/external calls to after-commit); silent partial commit if transaction boundaries are mismatched with nested dispatches; EntityManager closed or in an inconsistent state after a rollback, poisoning subsequent dispatches in long-running workers/queues; stale identity map across jobs. Each needs a test and a runbook entry, and each is a mode the team owns rather than inherits from a supported library.
Ramp-up. Two learning curves stack: Tactician's command bus and middleware model, plus Doctrine's unit of work and identity map. Laravel developers typically know neither. Estimate one to two weeks before a mid-level engineer is productive and considerably longer before they can safely reason about transaction boundaries and flush semantics. That ramp-up cost, multiplied across the team and by turnover, frequently exceeds the value of the pattern — and it is the strongest argument for using Laravel's native bus and DB::transaction() unless Doctrine and Tactician are already load-bearing commitments.
TPM recommendation. Do not adopt for a greenfield or predominantly Eloquent Laravel codebase — the score of 1.1 is consistent with a stale, unmaintained glue package whose functionality the framework already provides. Approve only as a deliberate consequence of an existing Doctrine + Tactician architecture, and only after a compatibility spike and with an explicit, funded plan to vendor or fork. If the underlying pattern is genuinely wanted, write the transaction middleware in-house: same behavior, full ownership, no abandonment risk.
How can I help you explore Laravel packages today?