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(3mo ago)08MITPHPPHP ^8.2

Since Feb 10Pushed 3mo 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 1mo ago

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

36

—

LowBetter than 82% of packages

Maintenance82

Actively maintained with recent releases

Popularity5

Limited adoption so far

Community2

Small or concentrated contributor base

Maturity47

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

92d ago

Major Versions

v1.0.0 → v2.0.02026-02-10

### Community

Maintainers

![](https://www.gravatar.com/avatar/0306a31e9423235a83c0093c7caaa9725e76e8796396ed3bc51225dd0cdaa4f1?d=identicon)[jeromejhipolito](/maintainers/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

[cviebrock/eloquent-sluggable

Easy creation of slugs for your Eloquent models in Laravel

4.0k13.6M253](/packages/cviebrock-eloquent-sluggable)[tucker-eric/eloquentfilter

An Eloquent way to filter Eloquent Models

1.8k4.8M26](/packages/tucker-eric-eloquentfilter)[watson/validating

Eloquent model validating trait.

9723.3M47](/packages/watson-validating)[cybercog/laravel-ban

Laravel Ban simplify blocking and banning Eloquent models.

1.1k651.8k11](/packages/cybercog-laravel-ban)[cybercog/laravel-love

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

1.2k302.7k1](/packages/cybercog-laravel-love)[cviebrock/eloquent-taggable

Easy ability to tag your Eloquent models in Laravel.

567694.8k3](/packages/cviebrock-eloquent-taggable)

PHPackages © 2026

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