NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages
Connections for Laravel

Bulk operations & sync

Stage several connectables with toMany() and run one bulk verb over all of them:

use RoundlyConsulting\Connections\Facades\Connections;

// Touch many targets in one call (each still emits its own event).
Connections::from($user)->toMany([$teamA, $teamB])->withPermissions('view')->connectAll();
Connections::from($user)->toMany($teams)->disconnectAll();
Connections::from($user)->toMany($teams)->grantAll('publish');
Connections::from($user)->toMany($teams)->revokeAll('publish'); // any status: pending, blocked, expired
  • connectAll() runs in one database transaction and returns the connections; every item still goes through the regular create path, so events and cache invalidation stay correct.
  • disconnectAll() runs in one transaction and skips connectables with no connection.
  • grantAll() creates missing connections, like grant(); called without arguments it uses the staged permissions.
  • revokeAll() revokes on every target that has a connection in any status — pending, blocked and expired included, so a revoked permission can’t come back on accept() or extend(). Targets with no connection, or only a soft-deleted one, are skipped.
  • toMany() accepts any iterable — an array or an Eloquent collection. A bulk verb without toMany() throws MissingConnectable.

Reconciling an exact set

Connections::from($user)->sync() — or the trait’s syncConnections() — works like Eloquent’s sync(): it connects what is missing, disconnects extras and updates the overlap, all in one transaction — a failure rolls the whole reconcile back. Status rules apply as for connect(). It returns a SyncResult:

use RoundlyConsulting\Connections\DataTransferObjects\SyncTarget;
use RoundlyConsulting\Connections\Facades\Connections;

// Reconcile to exactly this set: connect missing, disconnect extras, update overlap.
// Safe to repeat — a target detached earlier is revived, not re-inserted.
$result = Connections::from($user)->sync([$teamA, $teamC]);   // or $user->syncConnections([...])
$result->attached;  // list of newly connected (or revived) ids
$result->detached;  // list of disconnected ids
$result->updated;   // list of ids that already existed
$result->isEmpty(); // true when nothing was attached, detached or updated

// Per-target permissions, expiry or meta through SyncTarget DTOs. On an overlap, only what the
// target states changes: a plain model (or a null field) keeps the stored value, and the
// status is never touched.
Connections::from($user)->sync([
    new SyncTarget($teamA, permissions: ['view', 'edit']),
    new SyncTarget($teamB, ['publish'], now()->addDays(30)),
]);

Targets are plain connectables or SyncTarget DTOs — use a SyncTarget to give one target its own permissions, expiry or meta:

use RoundlyConsulting\Connections\DataTransferObjects\SyncTarget;

new SyncTarget(
    model: $teamB,                       // required — a Connectable
    permissions: ['publish'],            // optional
    expiresAt: now()->addDays(30),       // optional CarbonInterface
    meta: ['source' => 'directory'],     // optional
);

Things to know about sync

  • Sync reconciles every connection the connector initiated, across all connectable types — connections to models missing from the list are disconnected, whatever their class. Pass the complete desired set.
  • On an overlap, only what the target states changes: a plain model — or a SyncTarget field left null — keeps the stored permissions, expiry and meta, and the status is never touched.
  • Detached connections are soft-deleted and fire ConnectionRemoved. Syncing one back in later revives it as a fresh connection and lists it under attached, so a sync is safe to repeat.
  • An empty list disconnects everything the connector initiated.

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

By donating, you agree to our donation terms.

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.