PHPackages                             jeromejhipolito/laravel-eloquent-atomic - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Database &amp; ORM](/categories/database)
4. /
5. jeromejhipolito/laravel-eloquent-atomic

ActiveLibrary[Database &amp; ORM](/categories/database)

jeromejhipolito/laravel-eloquent-atomic
=======================================

Atomic upsert operations with soft-delete awareness and pessimistic locking for Laravel Eloquent models.

v2.0.0(4mo ago)08MITPHPPHP ^8.2

Since Feb 10Pushed 4mo agoCompare

[ Source](https://github.com/jeromejhipolito/laravel-eloquent-atomic)[ Packagist](https://packagist.org/packages/jeromejhipolito/laravel-eloquent-atomic)[ RSS](/packages/jeromejhipolito-laravel-eloquent-atomic/feed)WikiDiscussions main Synced today

READMEChangelogDependencies (5)Versions (3)Used By (0)

Laravel Eloquent Atomic
=======================

[](#laravel-eloquent-atomic)

Atomic upsert operations with soft-delete awareness and pessimistic locking for Laravel Eloquent models.

Replaces Laravel's `updateOrCreate()` with a race-condition-safe alternative that uses `SELECT ... FOR UPDATE` with automatic retry on unique constraint violations and deadlocks. Soft-deleted records are automatically detected and restored.

Installation
------------

[](#installation)

```
composer require jeromejhipolito/laravel-eloquent-atomic
```

Requires PHP 8.2+ and Laravel 11 or 12.

Usage
-----

[](#usage)

Add the `AtomicUpsert` trait to any service class that needs race-condition-safe upserts:

```
use JeromeJHipolito\EloquentAtomic\Traits\AtomicUpsert;

class SubscriptionService
{
    use AtomicUpsert;

    public function syncSubscription(array $data): Subscription
    {
        return $this->atomicUpdateOrCreate(
            Subscription::class,
            ['external_id' => $data['id']],       // lookup attributes (must have unique index)
            ['status' => $data['status'], 'name' => $data['name']]  // values to set
        );
    }
}
```

### How It Works

[](#how-it-works)

1. Starts a database transaction
2. Attempts `SELECT ... FOR UPDATE` to find an existing record (including soft-deleted)
3. If found: restores (if soft-deleted) and updates in a single query
4. If not found: creates the record
5. On `UniqueConstraintViolationException` or `DeadlockException`: rolls back and retries (up to 3 attempts)

Each retry gets a fresh transaction -- locks are released between attempts to prevent cascading lock waits.

### Requirements

[](#requirements)

The lookup `$attributes` columns **must** have a UNIQUE constraint at the database level. Without it, the retry mechanism cannot catch race conditions and duplicate records may be created.

```
Schema::table('subscriptions', function (Blueprint $table) {
    $table->unique('external_id');
});
```

### Soft-Delete Awareness

[](#soft-delete-awareness)

If the target model uses Laravel's `SoftDeletes` trait, `AtomicUpsert` automatically:

- Queries `withTrashed()` to find soft-deleted records
- Restores and updates in a single query (no intermediate visible state)
- Sets `wasRecentlyCreated = false` on the returned model

No configuration needed -- soft-delete detection is automatic via reflection (cached per model class).

Configuration
-------------

[](#configuration)

### Custom Primary Key Column

[](#custom-primary-key-column)

Override `getModelKeyColumn()` if your models use a non-standard primary key:

```
use JeromeJHipolito\EloquentAtomic\Traits\AtomicUpsert;

class MyService
{
    use AtomicUpsert;

    protected function getModelKeyColumn(): string
    {
        return 'uuid';
    }
}
```

### DetectsSoftDeletes Trait

[](#detectssoftdeletes-trait)

Use `DetectsSoftDeletes` standalone for soft-delete-aware logic in your own code:

```
use JeromeJHipolito\EloquentAtomic\Traits\DetectsSoftDeletes;

class MyService
{
    use DetectsSoftDeletes;

    public function findRecord(string $modelClass, int $id)
    {
        $query = $modelClass::query();

        if (static::modelUsesSoftDeletes($modelClass)) {
            $query->withTrashed();
        }

        return $query->find($id);
    }
}
```

API
---

[](#api)

### `atomicUpdateOrCreate(string $modelClass, array $attributes, array $values): Model`

[](#atomicupdateorcreatestring-modelclass-array-attributes-array-values-model)

ParameterTypeDescription`$modelClass``class-string`Eloquent model class`$attributes``array`Lookup columns (must match a unique index)`$values``array`Columns to create/update**Returns**`T`The created or updated model instance### `modelUsesSoftDeletes(string $modelClass): bool`

[](#modelusessoftdeletesstring-modelclass-bool)

Returns `true` if the model class uses Laravel's `SoftDeletes` trait. Results are cached per class.

### `getModelKeyColumn(): string`

[](#getmodelkeycolumn-string)

Returns the primary key column name. Defaults to `'id'`. Override for UUID or custom primary keys.

Database Compatibility
----------------------

[](#database-compatibility)

FeatureMySQL (InnoDB)PostgreSQLSQLite`lockForUpdate()`Full support + gap locksFull supportIgnored (serialized writes)Deadlock recoverySupportedSupportedN/AUnique constraint retrySupportedSupportedSupportedLicense
-------

[](#license)

MIT

###  Health Score

34

—

LowBetter than 75% of packages

Maintenance74

Regular maintenance activity

Popularity4

Limited adoption so far

Community2

Small or concentrated contributor base

Maturity48

Maturing project, gaining track record

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

2

Last Release

145d ago

Major Versions

v1.0.0 → v2.0.02026-02-10

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/180266815?v=4)[JeromeHipolito](/maintainers/jeromejhipolito)[@jeromejhipolito](https://github.com/jeromejhipolito)

---

Tags

concurrencylaraveleloquentpessimistic-lockingupsertatomicsoft-delete

###  Code Quality

TestsPest

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/jeromejhipolito-laravel-eloquent-atomic/health.svg)

```
[![Health](https://phpackages.com/badges/jeromejhipolito-laravel-eloquent-atomic/health.svg)](https://phpackages.com/packages/jeromejhipolito-laravel-eloquent-atomic)
```

###  Alternatives

[mongodb/laravel-mongodb

A MongoDB based Eloquent model and Query builder for Laravel

7.1k8.4M96](/packages/mongodb-laravel-mongodb)[kirschbaum-development/eloquent-power-joins

The Laravel magic applied to joins.

1.6k32.6M46](/packages/kirschbaum-development-eloquent-power-joins)[psalm/plugin-laravel

Psalm plugin for Laravel

3355.3M346](/packages/psalm-plugin-laravel)[yajra/laravel-oci8

Oracle DB driver for Laravel via OCI8

8793.2M25](/packages/yajra-laravel-oci8)[glushkovds/phpclickhouse-laravel

Adapter of the most popular library https://github.com/smi2/phpClickHouse to Laravel

2051.5M2](/packages/glushkovds-phpclickhouse-laravel)[cybercog/laravel-love

Make Laravel Eloquent models reactable with any type of emotions in a minutes!

1.2k332.0k1](/packages/cybercog-laravel-love)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
