›
byrcsc/laravel-data-sync · 2.x
Describe one file feed, its model, mapping, matching, and write behavior in a class.
A sync definition is a class extending SyncDefinition. It answers five
required questions and adjusts a dozen optional ones. Behavior lives here;
config/data-sync.php holds only infrastructure.
use ByRcsc\LaravelDataSync\Contracts\Reader;
use ByRcsc\LaravelDataSync\Definitions\Source;
use ByRcsc\LaravelDataSync\Definitions\SyncDefinition;
final class ProductsSync extends SyncDefinition
{
public function source(): Source; // where files come from
public function format(): Reader; // how to read them
public function model(): string; // the destination Eloquent model
public function fields(): array; // the mapping, as Field objects
public function matchOn(): array; // the columns that identify a row
}Everything else has a default:
| Method | Default | Documented in |
|---|---|---|
name() | kebab-case class basename minus Sync | below |
rules() | [] | Validation and hooks |
writeMode() | WriteMode::Upsert | Matching and writing |
onDuplicate() | DuplicatePolicy::LastWins | Matching and writing |
onUnknownColumns() | UnknownColumnPolicy::Ignore | Field mapping |
captureUnmappedInto() | null | Field mapping |
chunkSize() | 1000 | Matching and writing |
maxErrors() | null | Matching and writing |
atomic() | false | Matching and writing |
captureFailedPayloads() | true | Sensitive data |
prepare() | returns the row unchanged | Validation and hooks |
withValidator() | no-op | Validation and hooks |
beforeSave() | not declared | Validation and hooks |
afterSave() | not declared | Validation and hooks |
queueConnection() | null | Queues and workers |
queue() | null | Queues and workers |
schedule() | not declared | Scheduling |
The name is what you type on the command line, what the ledger and runs record,
and what data-sync.overrides keys on. It is derived from the class basename
with a trailing Sync removed, then kebab-cased:
| Class | Name |
|---|---|
ProductsSync | products |
SupplierPricingSync | supplier-pricing |
Inventory | inventory |
Override it when the class name is wrong for operators:
public function name(): string
{
return 'supplier-catalog';
}Two definitions may not share a name. A collision throws an
InvalidConfigurationException at boot.
// config/data-sync.php
'syncs' => [
App\Syncs\ProductsSync::class,
App\Syncs\StatementsTransfer::class,
],Order does not matter. sync:run with no argument runs every enabled
definition, in registration order.
Disable one without removing it:
'overrides' => [
'products' => ['enabled' => false],
],A disabled definition is skipped by sync:run and is not scheduled. It is still
validated, and still addressable by name if you run it explicitly.
Every definition is built with app()->make(), so constructor injection works:
final class ProductsSync extends SyncDefinition
{
public function __construct(
private readonly CurrencyConverter $currency,
) {}
public function fields(): array
{
return [
Field::make('price_cents', from: 'Price')
->transform(fn (string $value): int => $this->currency->toCents($value)),
];
}
}Definitions are resolved fresh on each use, including inside queued jobs, so
they must not hold per-run state. The one piece of mutable state the base class
carries is the payload-capture flag set by withoutFailedPayloadCapture().
Resolving a definition validates it. sync:run, sync:status, sync:doctor,
and the queued jobs all go through the same path, so a broken definition fails
loudly and early rather than half way through a file.
A SyncDefinition must satisfy all of the following:
fields() is non-empty and contains only Field instances, with no duplicate
attribute names.model() names an existing class extending Illuminate\Database\Eloquent\Model.matchOn() is non-empty, and every entry names a mapped field.chunkSize() is at least 1, and maxErrors(), when set, is not negative.onUnknownColumns() is Capture, captureUnmappedInto() names an
attribute cast to array, json, object, collection, encrypted:array,
or encrypted:collection.Failures throw an InvalidDefinitionException naming the class and the problem.
ApiSyncDefinition and DatabaseSyncDefinition both extend
SnapshotSyncDefinition, which extends SyncDefinition, so everything on this
page applies to them. SnapshotSyncDefinition fixes source() and format(),
because the package fetches the records and writes them to a file itself. It
adds lockTimeout(), the number of seconds a fetch holds its lock, which
defaults to 300.
An API definition adds endpoint() and the paging methods. See syncing from
an API. A database definition adds connection(), query(),
keyColumns(), and watermark(). See syncing from a
database.
A definition class in data-sync.syncs registers one definition. To register
definitions from data at runtime, such as one for each database an account
connects, list a definition provider instead.
A TransferDefinition shares source(), name(), the queue methods, and
schedule(), and replaces the row-mapping methods with filenamePattern() and
destination(). Both kinds go in the same syncs array and run through the
same commands. See file transfers.
Field DSL.