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

Unit Of Work Laravel Package

isolate/unit-of-work

A Laravel package that brings the Unit of Work pattern to your application. Group related operations into a single atomic unit, manage database transactions, and defer side effects such as events or jobs until the unit completes successfully.

View on GitHub
Deep Wiki
Context7

Getting Started

isolate/unit-of-work is a standalone implementation of the Unit of Work pattern — an identity map plus change tracking with a commit() that flushes accumulated changes through a mapper you supply. It is not a Laravel package: there is no service provider, no facade, no config file, and no Eloquent integration.

Minimal steps:

  1. Test installability before anything else. The last tagged release is September 2015 and was written for PHP 5.x. On a modern Laravel app (PHP 8.1+) composer require isolate/unit-of-work will very likely fail platform or dependency resolution (the illuminate/* constraints are pinned to the 5.0–5.1 era). Create a throwaway directory, try the install there, and if it fails, clone the repository and read the source instead of fighting the constraint solver.
  2. Read in this order: README.md → composer.json (namespace and autoload style) → the top-level classes in src/ → tests/. In a package this old with this few stars, the test suite is the only reliable documentation of the actual API. Look specifically for the unit-of-work entry class, the identity map, and whatever "entity definition"/registration object is used to teach it about your domain objects.
  3. Identify the mapper contract. The package deliberately does not know how to persist anything. Find the interface your data mapper must implement (insert, update, delete-style methods) — that interface is the integration point with your application.
  4. First use case: a multi-step domain operation that touches more than one aggregate — moving stock between two warehouses, transferring funds between two accounts, promoting an order and its line items. Load both sides through the identity map, mutate them, and let a single commit() produce one insert/update/delete set. That is the whole value proposition: writes are derived from the object graph rather than hand-written at each call site.

Illustrative shape (verify names against the source — this package predates any convention you may assume):

$uow = new UnitOfWork($mapper, $entityDefinitions);

$from = $uow->getRepository(Stock::class)->find($fromId);
$to   = $uow->getRepository(Stock::class)->find($toId);

$from->decrease($qty);
$to->increase($qty);

$uow->commit(); // single insert/update set

Implementation Patterns

Wrap it behind your own interface. Nothing in controllers, jobs, or domain services should reference the package namespace directly. Define a UnitOfWorkInterface in your domain layer and make the package one adapter:

namespace App\Domain\Support;

interface UnitOfWorkInterface
{
    public function register(Runnable $work): void;
    public function commit(): void;
    public function rollback(): void;
}

This keeps the exit ramp cheap — when the package becomes untenable (or never installs), you swap in an implementation over DB::transaction() without touching call sites.

Envelope each commit in a database transaction. The package's commit() orchestrates mapper calls; it does not by itself open a PDO transaction. Wrap it:

DB::transaction(function () use ($uow) {
    $uow->commit();
});

That is what turns ordered inserts/updates/deletes into an actual atomic boundary. Without it you get exactly the partial-write failure mode the pattern exists to prevent.

Defer side effects until after commit. Do not queue jobs, send mail, or dispatch domain events inside the unit-of-work scope. Buffer them on the unit and flush after the transaction returns:

$uow->afterCommit(fn () => event(new StockTransferred($from, $to)));

If the package does not offer a hook (check the source), keep a small closure array alongside the mapper and drain it once DB::transaction returns. This avoids the classic "job fired for a row that was rolled back" bug.

One unit per application service call, not per request. A request-boundary unit holds the DB connection for the whole HTTP cycle, which grows lock duration and collapses throughput under concurrency. Open the unit at the top of a command/handler, commit at the end of it, then release.

Use the identity map to share aggregate instances. The point of loading through getRepository(...)->find() instead of direct hydration is that repeated lookups return the same object. Mutate in one place and the change is tracked once — no double-update when the repository is hit twice.

Test the boundary, not the package. Write behavioral tests that assert: (a) an exception inside the scope leaves no rows changed, (b) the side-effect closure does not fire on rollback, (c) mutating two aggregates produces exactly one update each. These tests are what protect you when you eventually replace the package.

Gotchas and Tips

  • No Laravel integration. No service provider, no auto-discovery (this predates 5.5), no facade alias, no config publish. You bind the unit yourself in a service provider and construct it manually. Assume everything is a plain PHP object.
  • PHP 8 will not "just work." Removed functions, return-type changes, and stricter typing are fatal in PHP 8, not warnings. If you adopt it, budget for a fork-and-patch and run PHPStan/Psalm at your project's level immediately to surface breakage.
  • commit() is not transactional by itself. The most common mistake is calling commit() outside DB::transaction() and assuming atomicity. Verify against the source that the mapper calls are all inside one enclosing transaction, or you will ship partial commits while believing you have a UoW.
  • Nested units of work. Check whether nesting uses savepoints or silently flattens. If it flattens, a nested rollback does nothing and a caller may think it rolled back. Either forbid nesting (assert single-entry) or implement savepoints in your adapter.
  • Identity map staleness. If another process writes to the same rows between find() and commit(), the unit will not notice. Treat long-lived units as unsafe; open-commit-close.
  • Flush ordering. The mapper interface flushes inserts/updates/deletes as a batch, not necessarily in the order you mutated. If a child entity references a parent that was inserted in the same batch, verify the mapper resolves ordering — or split into two units.
  • No error surface. The package predates modern exception hierarchies and PSR logging. Add instrumentation from day one: increment a commit counter and a rollback counter on each request, tagged with the operation name. When the throughput issue or partial-write issue appears, you want a dashboard, not a post-mortem.
  • No community corpus. 23 stars, zero dependents, ten years dormant — you will not find a Stack Overflow answer for behavior you do not understand. Read the tests; they are the documentation.
  • Extension points to lean on: custom mapper (implement the mapper contract yourself; do not extend a base class if none exists), entity definitions (these are how you tell the unit about properties and change detection — keep them declarative and near the domain object), and the repository accessor (the identity map hangs off this). Keep all three behind your own interfaces so they do not leak into domain code.
  • If any of the above costs more than a day, stop. A ~200-line UnitOfWork over DB::transaction() with an afterCommit array, explicit aggregate registration, and after-commit event dispatch covers 90% of Laravel codebases and eliminates the ownership burden. The value of this package is the pattern, demonstrated in the source; the artifact itself is a reference implementation, not a dependency you should ship.
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.
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle