Laravel collector

seamward/laravel-collector adds Laravel package discovery, named connections, webhook middleware, explicit HTTP, queue, and scheduled-feed observation, lifecycle flushing, and safe diagnostics on top of the framework-neutral PHP core.

Status

The Laravel collector is in alpha and is available from Packagist as 0.1.0-alpha.1. The one-prompt setup supports Laravel 12 and 13 on PHP 8.3 or later. During the reviewed local apply, Seamward installs the exact supported PHP and Laravel collector pair. Unsupported versions and unsafe source shapes stop before any project write.

Install

Install both alpha packages explicitly, then publish the Laravel configuration:

Code example

composer require seamward/php-collector:^0.1@alpha seamward/laravel-collector:^0.1@alphaphp artisan vendor:publish --tag=seamward-config

Both constraints are explicit so Composer accepts the prerelease dependency without changing the project's global minimum stability. Laravel discovers SeamwardServiceProvider automatically. The adapter supports Laravel 12 and 13 on PHP 8.3 or later.

Automatic setup preview

Run seamward setup, restart the coding agent, and ask:

Code example

Set up Seamward in this project.

The CLI still runs on Node.js 22 or later. It detects Composer and Laravel internally, installs the exact supported collector versions from Packagist during the approved apply, generates credential-free named connection files under config/, wraps supported Http facade calls, adds seamward.webhook middleware to literal webhook routes, runs PHP syntax checks and an approved Composer verification script, and rolls back the complete change if verification fails. It never reads or edits .env.

Automatic rewriting is intentionally narrow. The request target must be a recoverable string route, and webhook routes must use a literal route template. Dynamic URL variables, custom request objects, queues, scheduled feeds, and other PHP frameworks return manual_required; use the explicit examples below for those boundaries.

Configure

Define one named connection per Seamward Integration boundary in config/seamward.php:

Code example

'connections' => [    'candidate_webhook' => [        'connection_key' => env('SEAMWARD_CANDIDATE_CONNECTION_KEY'),        'ingest_token' => env('SEAMWARD_CANDIDATE_INGEST_TOKEN'),        'endpoint' => env('SEAMWARD_INGEST_URL', 'https://api.seamward.com/ingest'),        'redaction_policy_version' => 'shape-only-v1',        'deployment' => [            'service' => env('SEAMWARD_SERVICE'),            'release' => env('SEAMWARD_RELEASE'),            'commitSha' => env('SEAMWARD_COMMIT_SHA'),        ],    ],],

Keep ingest tokens in protected runtime configuration. Never put them in source, browser code, logs, or support messages.

The default connection uses SEAMWARD_CONNECTION_KEY, SEAMWARD_INGEST_TOKEN, and the optional SEAMWARD_INGEST_URL. Named connections can use names chosen by the application, as shown above.

Observe an inbound webhook

Attach the terminable middleware to the route and pass the named connection plus a stable route template:

Code example

use Seamward\LaravelCollector\Middleware\ObserveWebhook;
Route::post('/webhooks/candidates', CandidateWebhookController::class)    ->middleware(ObserveWebhook::class.':candidate_webhook,/webhooks/candidates');

The middleware returns the original response and rethrows application exceptions unchanged. Its terminate phase flushes the observation outside the controller's business logic.

Observe outbound HTTP

Wrap only the provider call. The returned Laravel HTTP response and original exceptions remain authoritative:

Code example

use Illuminate\Support\Facades\Http;use Seamward\LaravelCollector\SeamwardManager;
$response = app(SeamwardManager::class)->observeHttp(    connection: 'candidate_api',    method: 'POST',    routeTemplate: '/v1/candidates',    callback: fn () => Http::post($providerUrl, $requestData),);

Response values are inspected locally for their JSON structure. Request and response headers are never added to the observation envelope.

To observe the request contract instead, append payloadLocation and the local request value. Existing positional and named calls remain compatible:

Code example

$response = app(SeamwardManager::class)->observeHttp(    connection: 'candidate_api',    method: 'POST',    routeTemplate: '/v1/candidates',    callback: fn () => Http::post($providerUrl, $requestData),    payloadLocation: 'request',    requestPayload: $requestData,);

requestPayload never enters the envelope. Only its structural shape and fingerprint are buffered. If the provider call throws, the envelope records status 0 and an unaccepted outcome, then the original exception is rethrown unchanged.

Observe a queue

Use explicit wrappers around the broker operation. The package does not infer an Integration from arbitrary Laravel jobs:

Code example

$published = $manager->observeQueuePublish(    connection: 'candidate_events',    queueName: 'candidate-events',    message: $message,    publisher: fn ($message) => $broker->publish('candidate-events', $message),);
$handled = $manager->observeQueueConsumer(    connection: 'candidate_events',    queueName: 'candidate-events',    message: $message,    consumer: fn ($message) => $handler->handle($message),);

The wrappers record queue evidence but do not acknowledge, retry, release, or dead-letter the job. Laravel queue completion and failure events remain flush boundaries only.

Observe a scheduled feed

Wrap one scheduled import or export callback:

Code example

$result = $manager->observeScheduledFeed(    connection: 'candidate_import',    feedName: 'nightly-candidate-import',    direction: 'inbound',    payload: $rows,    handler: fn ($rows) => $importer->import($rows),);

Use a stable feed or job label, never a run id or timestamp. The wrapper does not schedule, retry, or otherwise control the job.

Lifecycle and diagnostics

The package flushes resolved collectors after terminable webhook middleware, completed console commands, processed or failed queue jobs, and application termination. Long-running workers and custom loops should call SeamwardManager::flush() at their own safe boundary. There is no background timer or destructor network call.

Check configuration presence without printing credentials:

Code example

php artisan seamward:doctor

Next steps