Attribute-driven transactions and row-level locks for Laravel: retries, backoff, HTTP-aware rollbacks, and FOR UPDATE/SHARE on route model binding.
meius/laravel-transaction-orchestrator is a Laravel package for attribute-driven transactions and row-level locks for laravel: retries, backoff, http-aware rollbacks, and for update/share on route model binding..
It currently has 5 GitHub stars and 1 downloads on Packagist (latest version v0.1.0).
Install it with composer require meius/laravel-transaction-orchestrator.
Discover more Laravel packages by meius
or browse all Laravel packages to compare alternatives.
Last updated
Declarative transactions and row-level locks for Laravel 11+ via PHP attributes.
Annotate controller methods with #[Transactional] — get transactions with retries/backoff and HTTP-aware rollback.
Annotate parameters with #[LockForUpdate] / #[SharedLock] — get row-locks directly during route model binding. Zero boilerplate.
Internally it uses the standard Laravel/Eloquent API:
lockForUpdate()andsharedLock(). Support = whatever Laravel and your DB driver support.
#[Transactional] — wrap controller actions in a transaction, optionally with retries and backoff.noRollbackOn).#[LockForUpdate] / #[SharedLock] on action parameters — row-lock during route model binding.Row lock support is fully delegated to Laravel/Eloquent:
FOR UPDATE / FOR SHARE (or LOCK IN SHARE MODE on older versions).FOR UPDATE / FOR SHARE (or FOR KEY SHARE depending on context).UPDLOCK, ROWLOCK), same as Laravel does.SELECT (effectively no-op).If your driver/version doesn’t support the mode, behavior matches Laravel.
Composer Installation:
Install the package using Composer:
composer require meius/laravel-transaction-orchestrator
Register the Service Provider:
Manually register the service provider by adding it to your bootstrap/providers.php file:
return [
// Other service providers...
Meius\LaravelTransactionOrchestrator\Providers\TransactionOrchestratorServiceProvider::class,
];
use App\Models\Order;
use App\Repositories\OrderRepository;
use Meius\LaravelTransactionOrchestrator\Attributes\Locks\LockForUpdate;
use Meius\LaravelTransactionOrchestrator\Attributes\Transactional;
use Meius\LaravelTransactionOrchestrator\Enums\HttpRollbackPolicy;
use Symfony\Component\HttpFoundation\Response;
class OrderController extends Controller
{
public function __construct(
private readonly OrderRepository $orderRepository,
) {
//
}
#[Transactional(
connection: 'mysql',
retries: 3,
backoff: [50, 100, 200], // milliseconds
noRollbackOn: [QueryException::class],
rollbackOnHttpError: HttpRollbackPolicy::ROLLBACK_ON_5XX,
)]
public function destroy(#[LockForUpdate] Order $order): Response
{
try {
$this->orderRepository->delete($order);
} catch (\Throwable) {
return response()->json([
'message' => 'Unable to delete the order.',
], Response::HTTP_INTERNAL_SERVER_ERROR); // 5xx → rollback (per policy)
}
return response()->noContent(); // 204 → commit
}
}
TransactionalPurpose: run the controller method inside transaction(s).
Parameters & behavior:
connection: null|string|string[].
null → default from config/database.php.$connections.retries: how many times to retry on transient DB errors (deadlock, lock timeout, disconnect, etc.).backoff: delay in ms before retry.
noRollbackOn: list of exception FQCNs that do not trigger rollback.rollbackOnHttpError: rollback policy based on HTTP response:
ROLLBACK_NONE, ROLLBACK_ON_4XX, ROLLBACK_ON_5XX, ROLLBACK_ON_4XX_5XX(default), or list of codes ([409, 422]).Validation (constructor enforces):
backoff as array → must be list<int>.rollbackOnHttpError as array → must be list<int>.noRollbackOn → must be Throwable subclasses.Transaction outcome:
noRollbackOn).LockForUpdate and SharedLockPurpose: apply row-lock to action parameter during route model binding.
How it works:
lockForUpdate() or sharedLock()) just once.Example:
use App\Exceptions\Products\CannotRemoveProductException;
use App\Exceptions\Products\ProductNotInOrderException;
use App\Http\Resources\OrderResource;
use App\Models\Order;
use App\Models\Product;
use App\Services\OrderService;
use Meius\LaravelTransactionOrchestrator\Attributes\Locks\LockForUpdate;
use Meius\LaravelTransactionOrchestrator\Attributes\Locks\SharedLock;
use Meius\LaravelTransactionOrchestrator\Attributes\Transactional;
use Symfony\Component\HttpFoundation\Response;
class OrderProductController extends Controller
{
public function __construct(
private readonly OrderService $orderService,
) {
//
}
/**
* Removes a product from the order.
*/
#[Transactional]
public function destroy(
#[LockForUpdate] Order $order,
#[SharedLock] Product $product
): Response {
try {
$order = $this->orderService->recalculate($order, $product);
} catch (ProductNotInOrderException|CannotRemoveProductException $exception) {
return response()->json([
'error' => $exception->getMessage(),
], Response::HTTP_UNPROCESSABLE_ENTITY);
} catch (\Throwable) {
return response()->json([
'error' => 'Unable to remove product from order.',
], Response::HTTP_INTERNAL_SERVER_ERROR);
}
return OrderResource::make($order)->response();
}
}
retries > 0.backoff in ms. Example [10, 30, 70] → values applied per attempt, last repeated.With retries enabled, make operations idempotent (or dedupe-safe). The package does not enforce idempotency.
Rollback can be triggered by response status without exceptions:
422 → rollback.409 → rollback.5xx → rollback (default).Customize via policy or code list:
#[Transactional(rollbackOnHttpError: [409, 422])]
Decision is made after action returns a Response, before sending body.
connection accepts an array:
#[Transactional(connection: ['mysql', 'pgsql'])]
DB::transaction() inside, Laravel uses savepoints (if supported).#[Transactional] decides final commit/rollback.commit()/rollBack() with orchestrator — use DB::transaction().This package is open-sourced software licensed under the MIT license.