›
›
›
  1. docs
  2. ›
  3. byrcsc/laravel-data-sync
2.x
Browse documentationOpenClose

Getting started

  • Introduction
  • Installation and setup
  • Quick start
  • Upgrading from 1.0

Core concepts

  • File identity and runs
  • Sync definitions
  • Sources and connections
  • File formats and readers
  • Field mapping
  • Matching and writing
  • Validation and hooks

Running syncs

  • Running a sync
  • Queues and workers
  • Scheduling

APIs and databases

  • Syncing from an API
  • Syncing from a database
  • Definition providers

Transfers

  • File transfers

Operations

  • Run history and status
  • Events and listeners
  • Failures and retries
  • Sensitive data
  • Health checks
  • Retention and pruning

Reference

  • Configuration
  • Console commands
  • Testing
  • Troubleshooting

Getting started

  • Introduction
  • Installation and setup
  • Quick start
  • Upgrading from 1.0

Core concepts

  • File identity and runs
  • Sync definitions
  • Sources and connections
  • File formats and readers
  • Field mapping
  • Matching and writing
  • Validation and hooks

Running syncs

  • Running a sync
  • Queues and workers
  • Scheduling

APIs and databases

  • Syncing from an API
  • Syncing from a database
  • Definition providers

Transfers

  • File transfers

Operations

  • Run history and status
  • Events and listeners
  • Failures and retries
  • Sensitive data
  • Health checks
  • Retention and pruning

Reference

  • Configuration
  • Console commands
  • Testing
  • Troubleshooting

byrcsc/laravel-data-sync · 2.x

Sync definitions.

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.

The required surface

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:

MethodDefaultDocumented in
name()kebab-case class basename minus Syncbelow
rules()[]Validation and hooks
writeMode()WriteMode::UpsertMatching and writing
onDuplicate()DuplicatePolicy::LastWinsMatching and writing
onUnknownColumns()UnknownColumnPolicy::IgnoreField mapping
captureUnmappedInto()nullField mapping
chunkSize()1000Matching and writing
maxErrors()nullMatching and writing
atomic()falseMatching and writing
captureFailedPayloads()trueSensitive data
prepare()returns the row unchangedValidation and hooks
withValidator()no-opValidation and hooks
beforeSave()not declaredValidation and hooks
afterSave()not declaredValidation and hooks
queueConnection()nullQueues and workers
queue()nullQueues and workers
schedule()not declaredScheduling

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:

ClassName
ProductsSyncproducts
SupplierPricingSyncsupplier-pricing
Inventoryinventory

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 withoutFailedPayloadCapture().

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 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.
  • 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() 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.

API and database syncs

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.

Transfers

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.

What to read next

  • Sources and connections for where files come from and what happens to them afterwards.
  • Field mapping for the Field DSL.
  • Matching and writing for write policies and the two write strategies.
PreviousFile identity and runsNextSources and connections
View source

On this page

  1. The required surface
  2. Names
  3. Registration
  4. Definitions are resolved from the container
  5. Validation happens before anything runs
  6. API and database syncs
  7. Transfers
  8. What to read next