---
title: "Commit and progress — Imports for Laravel | Roundly"
description: "The commit as one Laravel job batch of idempotent chunks — progress, summary, callback exceptions, cancel and resume, the lock and the ledger."
url: https://roundly-consulting.com/open-source/docs/imports-for-laravel/commit
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Imports for Laravel](https://roundly-consulting.com/open-source/docs/imports-for-laravel.md)

# Commit and progress

```php
use RoundlyConsulting\Imports\Enums\RecordAction;

$batch = $session->commit();     // BatchHandle
$batch->status(); $batch->progress(); $batch->summary(); $batch->records(RecordAction::Created);
$batch->isRunning(); $batch->isFinished(); $batch->isRolledBack();   // each re-reads the row
$batch->cancel(); $batch->resume();
```

## How a commit runs

- authorize(Commit); the session must be previewed with the caller’s preview version and not expired (409 otherwise).
- The lock moves the session to committing; an ImportBatch row records the committer.
- One Laravel job batch holding one sequential chain of chunk jobs on imports.queue.\* (sync with the queue off). Chunk size = max(commit.chunk\_size, ceil(rows / commit.max\_chunks)), fixed at preview. ImportStarted fires just before the dispatch; if the queue refuses it, the commit fails at once (dispatch\_failed), the lock is released and the exception rethrown.
- Each chunk runs inside the owner’s context. The first chunk of each run re-asks authorize(Commit) for the committer — a user who lost access fails the batch (unauthorized).
- One transaction per chunk. It locks the batch row and checks that the batch is running and the chunk is next, so a duplicate delivery, a retry of a committed chunk or any job of a stopped batch does nothing.
- Invalid and duplicate rows are skipped; every other row is re-validated and re-matched against current data (reclassified when that changed). Each row runs in a savepoint: your callback, then checks — the record is saved, on the import connection and inside the owner.
- Your writes, the ledger, the row results and the chunk counter commit or roll back together. About one query per row: ledger rows are bulk-inserted per chunk, so the ledger model’s own Eloquent events do not fire — listen to ImportChunkProcessed or ImportCompleted.
- Finished → completed (ImportCompleted); failed → failed (ImportFailed, resumable). Both run inside the owner’s context, set finished\_at and undo\_until, and release the lock.

## Exceptions in your callbacks

- ImportRowException and ValidationException fail the row with their message.
- A unique index violation in your table fails the row as duplicate\_created\_meanwhile.
- Any other exception fails the row with a generic message and is reported.
- A database concurrency or lost-connection error, and any non-Exception throwable, is rethrown: the chunk rolls back and the job retries (commit.tries, commit.backoff, commit.timeout).

## Side effects

Mail, jobs and files written by callbacks are outside the transaction. Dispatch them after commit, so a rolled-back chunk never sends anything:

```php
public function create(ImportRow $row, ImportContext $context): Model
{
    $member = Member::create([...$row->attributes(), ...$context->ownerAttributes()]);

    SendWelcome::dispatch($member)->afterCommit();   // never sent for a chunk that rolls back

    return $member;
}
```

## Cancel and resume

cancel() stops the chain — rows already written stay and can be undone. resume() starts a new batch from the next chunk for a failed or cancelled run, refused once the file or working copy was pruned. ImportSummary: created, linked, updated, skipped, failed, reclassified, duplicates, invalid and durationSeconds.

## One running import per owner and importer

A unique lock key on the session allows one running commit or rollback per owner and importer, set by one conditional update that also moves the status. A double-clicked commit gets ImportStateException; another session of the same owner and importer gets ImportAlreadyRunningException (409, naming it). imports:sweep releases a run with no progress for sessions.stale\_after whose job batch is finished, cancelled, failing or gone, and any run silent for sessions.abandon\_after — a run that is merely slow is left alone. A run whose owner was deleted is still failed and its lock released, but no event fires — there is no owner to name. An error on one session is reported and the sweep carries on with the others.

## The ledger: “imported” on every record

imported\_records holds one row per record a commit created, linked or updated — the polymorphic “imported from” pivot. Add HasImportSource to a model to read it:

```php
use RoundlyConsulting\Imports\Concerns\HasImportSource;

final class Asset extends Model
{
    use HasImportSource;
}

$asset->importRecords;              // MorphMany<ImportedRecord>
$asset->importBatch();              // ?ImportBatch — the latest batch that wrote it whose undo did not remove it
$asset->wasImported();              // bool
Asset::importedIn($batch->model())->get();
```

The trait is read-only. A record an undo kept still counts as imported.

## Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

[More ways to support, including crypto](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/imports-for-laravel.md)
