Browse documentationOpen

byrcsc/laravel-data-sync · 1.x

Sources and connections.

Point a definition at a disk or an inline Flysystem connection, defer files that are still uploading, and decide what happens to the supplier's copy afterwards.

source() returns a Source describing where files arrive and what to do with them once they have been processed.

use ByRcsc\LaravelDataSync\Definitions\Source;

public function source(): Source
{
    return Source::connection('supplier-sftp')
        ->path('products')
        ->minFileAge(minutes: 2)
        ->archiveTo('products/processed');
}

Disks and connections

Source::disk() names a filesystem disk from config/filesystems.php:

Source::disk('incoming')->path('products');

Source::connection() names an entry in data-sync.connections, an inline Flysystem configuration array built on demand. Use it for a supplier server that does not deserve a permanent disk in the application's filesystem config:

// config/data-sync.php
'connections' => [
    'supplier-sftp' => [
        'driver' => 'sftp',
        'host' => env('SUPPLIER_SFTP_HOST'),
        'username' => env('SUPPLIER_SFTP_USERNAME'),
        'privateKey' => env('SUPPLIER_SFTP_KEY_PATH'),
        'root' => '/outbound',
    ],
],

Connection arrays take the same keys the driver takes in config/filesystems.php. They are validated at boot: a non-array entry, or one with no driver, throws. An ftp or sftp driver whose Flysystem adapter is not installed throws too, naming the package to require.

Paths

path() names a directory relative to the disk root:

Source::disk('incoming')->path('products');

Every file under that directory is a candidate, including files in subdirectories. Discovery is not filtered by extension or glob — if a definition should only see CSVs, give it a directory that only receives CSVs.

path() may also name a single file, which is what the make:sync stub generates:

Source::disk('local')->path('imports/products.csv');

An empty or missing directory yields no candidates and the run reports zero discovered files. That is a success, not an error.

Deferring files that are still arriving

Source::disk('incoming')->path('products')->minFileAge(minutes: 2);

minFileAge() skips files whose modification time is more recent than the given age, which is how you avoid reading a file the supplier is still uploading. Both arguments are optional and additive:

->minFileAge(minutes: 1, seconds: 30);

Deferred files are counted separately in the command output and picked up on a later run. Nothing is recorded in the ledger for them.

There is no lock coordination with the sender: minFileAge() is a heuristic based on modification time. For a supplier that can write slowly for longer than your window, ask for an upload-then-rename or a marker file instead.

What happens to the supplier's copy

By default, nothing. The source file is left exactly where it was, and the package relies on the ledger to avoid reprocessing it.

Three actions change that:

MethodEffect
archiveTo($path)Copies the source file to another path on the source disk
moveSourceTo($path)Moves it to another path on the source disk
deleteFromSource()Deletes it
Source::connection('supplier-sftp')
    ->path('outbound')
    ->moveSourceTo('outbound/processed');

Copies and moves land at <path>/<checksum>-<filename>, so two files with the same name never collide in the destination.

Three rules govern them:

  • They run only after a verified archive. The staged file is copied to the archive disk and its checksum re-verified first. If that fails, the run fails and the source is untouched.
  • They run only on a real run. Dry runs never touch the source.
  • moveSourceTo() wins over deleteFromSource(). A move already removes the original, so the delete is not attempted.

A failure while applying them — a read-only supplier directory, a dropped SFTP connection — does not fail the run. The file is already imported and archived; undoing that would be worse. The message is recorded in the run's warnings column instead.

Staging

Whatever the source, discovery streams each candidate to the staging disk before anything reads it. Processing, archiving, and transfers all read from staging, so a slow or flaky SFTP server is contacted once per file rather than once per chunk.

That also means staging must be reachable by whichever process eventually reads the file. On a single machine, the default local disk is fine. Across machines, see queues and workers.

Checking a source before you trust it

php artisan sync:doctor

sync:doctor lists every configured connection and every definition's source, and reports a failure when one cannot be reached. It is the cheapest way to catch a rotated SFTP key before a scheduled run does.