Programmatic API

Beyond Artisan commands, AuditChain provides a programmatic API for chain verification, batch grouping, context metadata, and logging control. This lets you build custom integrations, admin dashboards, health checks, and automated workflows.

Chain Verification

Use AuditChainService to verify the hash chain from application code:

use GrayMatter\AuditChain\Services\AuditChainService;

$service = app(AuditChainService::class);
$result = $service->verifyChain();

The return value is an array with three keys:

[
    'valid' => true,      // bool -- whether the chain is intact
    'checked' => 150,     // int -- number of chained logs verified
    'errors' => [],       // array -- list of error details
]

Filtering by Model

Pass a model type and optional ID to verify a subset of the chain:

// Verify all audit logs for a specific model type
$result = $service->verifyChain(type: 'App\Models\Order');

// Verify logs for a specific model instance
$result = $service->verifyChain(type: 'App\Models\Order', id: '42');

When filters are applied, only hash integrity is checked (each log's stored hash matches its recomputed hash). Chain continuity (prev_hash linking) is skipped because the global chain spans all model types — filtering removes entries from the middle of the chain.

Health Check Endpoint

Build a health check route that exposes chain status:

// routes/api.php
use GrayMatter\AuditChain\Services\AuditChainService;

Route::get('/health/audit-chain', function (AuditChainService $service) {
    $result = $service->verifyChain();

    return response()->json([
        'status' => $result['valid'] ? 'ok' : 'compromised',
        'checked' => $result['checked'],
        'errors' => count($result['errors']),
    ], $result['valid'] ? 200 : 500);
})->middleware('auth:sanctum');

Important: Protect this endpoint with authentication. Chain verification status should not be publicly accessible.

The AuditChain Facade

The AuditChain facade provides runtime control over audit logging through the AuditChainManager singleton.

use GrayMatter\AuditChain\Facades\AuditChain;

batch()

Group related audit log entries under a single UUID:

AuditChain::batch(function () {
    $order->audit('shipped');
    $order->update(['status' => 'shipped']);
    $inventory->update(['quantity' => $inventory->quantity - 1]);
});
// All 3 audit logs share the same batch_uuid

You can supply your own batch ID:

AuditChain::batch(function () {
    // ...
}, batchId: 'import-2024-01-15');

Batches nest safely. An inner batch uses its own UUID; the outer batch resumes when the inner one completes:

AuditChain::batch(function () {
    $order->update(['status' => 'processing']);

    AuditChain::batch(function () {
        $payment->audit('captured');
        $payment->update(['captured_at' => now()]);
    });

    $order->update(['status' => 'shipped']);
});

context()

Attach free-form metadata to all subsequent audit logs:

AuditChain::context(['source' => 'csv_import', 'file' => 'users.csv']);

User::create([...]); // context is attached to this audit log
User::create([...]); // and this one too

Context is additive — calling context() multiple times merges the values:

AuditChain::context(['source' => 'api']);
AuditChain::context(['request_id' => 'abc-123']);
// Context is now: ['source' => 'api', 'request_id' => 'abc-123']

Note: Context is not included in the hash chain computation. It is operational metadata — changing context does not invalidate the chain.

clearContext()

Reset the context to an empty array:

AuditChain::clearContext();

Use this after an import or batch operation to stop attaching the context to subsequent logs.

withoutAudit()

Suppress all audit logging within a callback:

AuditChain::withoutAudit(function () {
    User::factory()->count(1000)->create();
    // No audit logs created
});

Common use cases:

  • Database seeding — Avoid flooding the audit table with seed data
  • Data migrations — Bulk updates that should not be tracked
  • Imports — CSV or API imports where you log the import itself but not each individual record

withoutAudit() nests safely. If an outer call disables logging, an inner call does not re-enable it.

Building Custom Integrations

Middleware for Request Context

Automatically attach request metadata to all audit logs in a request:

// app/Http/Middleware/AuditContext.php
namespace App\Http\Middleware;

use Closure;
use GrayMatter\AuditChain\Facades\AuditChain;
use Illuminate\Http\Request;

class AuditContext
{
    public function handle(Request $request, Closure $next)
    {
        AuditChain::context([
            'request_id' => $request->header('X-Request-ID', (string) str()->uuid()),
            'route' => $request->route()?->getName(),
        ]);

        return $next($request);
    }

    public function terminate(Request $request, $response): void
    {
        AuditChain::clearContext();
    }
}

Admin Dashboard Widget

Query verification results for a dashboard:

use GrayMatter\AuditChain\Services\AuditChainService;
use GrayMatter\AuditChain\Models\AuditLog;

$service = app(AuditChainService::class);
$result = $service->verifyChain();

$stats = [
    'chain_valid' => $result['valid'],
    'chain_checked' => $result['checked'],
    'total_logs' => AuditLog::count(),
    'today_logs' => AuditLog::where('created_at', '>=', now()->startOfDay())->count(),
];

Conditional Auditing

Combine withoutAudit() with business logic:

use GrayMatter\AuditChain\Facades\AuditChain;

public function importUsers(array $records): void
{
    AuditChain::context(['source' => 'user_import', 'count' => count($records)]);

    AuditChain::batch(function () use ($records) {
        // Log the import event on a tracking model
        $this->importLog->audit('started');

        // Suppress individual record auditing
        AuditChain::withoutAudit(function () use ($records) {
            foreach ($records as $record) {
                User::create($record);
            }
        });

        $this->importLog->audit('completed');
    });

    AuditChain::clearContext();
}

This records the import start and completion in the audit trail (with batch and context), but skips auditing each individual user creation.