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.