byrcsc/laravel-data-sync · 1.x
Sync definitions.
The anatomy of a SyncDefinition class, how its name and registration work, what the container resolves, and the validation applied before a definition ever runs.
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.
The required surface
use ByRcsc\LaravelDataSync\Contracts\Reader as ReaderContract;
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(): ReaderContract; // 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 |
Names
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.
Registration
// 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.
Definitions are resolved from the container
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 withoutPayloadCapture().
Validation happens before anything runs
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 onlyFieldinstances, with no duplicate attribute names.model()names an existing class extendingIlluminate\Database\Eloquent\Model.matchOn()is non-empty, and every entry names a mapped field.chunkSize()is at least 1, andmaxErrors(), when set, is not negative.- Every mapped attribute is a real column on the model's table, or has a set mutator. This check is skipped when the table does not exist yet.
- When
onUnknownColumns()isCapture,captureUnmappedInto()names an attribute cast toarray,json,object,collection,encrypted:array, orencrypted:collection.
Failures throw an InvalidDefinitionException naming the class and the problem.
Transfers
A TransferDefinition shares source(), name(), the queue methods, and
schedule(), and replaces the row-mapping surface with matchFilename() and
destination(). Both kinds go in the same syncs array and run through the
same commands. See file transfers.
What to read next
- Sources and connections for where files come from and what happens to them afterwards.
- Field mapping for the
FieldDSL. - Matching and writing for write policies and the two write paths.