Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/HypathStack/model-scribe/llms.txt

Use this file to discover all available pages before exploring further.

All ModelScribe drivers — built-in or custom — must implement HypathBel\ModelScribe\Contracts\DriverInterface. This two-method contract keeps the core package decoupled from any particular storage or transport layer, and makes custom drivers straightforward to write and test.
namespace HypathBel\ModelScribe\Contracts;

use HypathBel\ModelScribe\DTOs\LogEntry;

interface DriverInterface
{
    public function log(LogEntry $entry): void;
    public function prune(): int;
}

Methods

log(LogEntry $entry): void

Persist or dispatch a single audit log entry to the driver’s target destination.
$entry
LogEntry
required
An immutable LogEntry DTO (value object) carrying all audit data for this event. The DTO exposes the following readonly properties:
PropertyTypeDescription
$eventScribeEventThe event type enum case
$logNamestringRouting / grouping key
$description?stringHuman-readable description
$subject?ModelThe audited Eloquent model
$causer?ModelThe actor Eloquent model
$propertiesarray['old' => [...], 'attributes' => [...]]
$tagsarrayString tags
$url?stringRequest URL
$ipAddress?stringClient IP address
$userAgent?stringClient User-Agent string
$batchUuidstringAuto-generated UUID grouping related entries
The LogEntry also exposes a withLogName(string $logName): self method that returns a new instance with a different log name — used internally by the StackDriver to fan entries out to sub-drivers.
A LogEntry is constructed once by ModelScribe::log() or the observer and then handed to the driver. Because it is readonly, drivers must not attempt to modify it. If a driver needs a modified copy (e.g. a different logName), use $entry->withLogName('other').

prune(): int

Delete stale records from the driver’s backing store according to its configured retention policy. Returns the count of records removed as an int. Drivers that do not manage persistent state (for example, a webhook or queue driver that dispatches and forgets) should return 0.
prune() is called by ModelScribe::prune(?string $driver = null): int. The DriverInterface does not receive the driver name or any configuration at prune-time — each driver should read its own retention configuration (e.g. config('model-scribe.drivers.database.prune_after_days')) in its constructor or in the prune() implementation itself.

Built-in Drivers

Three driver classes ship with ModelScribe, all in the HypathBel\ModelScribe\Drivers namespace:
ClassDriver keyDescription
DatabaseDriver'database'Writes entries to an Eloquent-managed SQL table
FileDriver'file'Appends JSON-encoded entries to a log file
StackDriver'stack'Fan-out wrapper; delegates to multiple sub-drivers

DriverManager

The DriverManager class resolves, caches, and creates driver instances. It is accessed via ModelScribe::getDriverManager().

driver(?string $name = null): DriverInterface

Resolves a driver by name, returning a cached instance on repeat calls. When $name is null, the global default from config('model-scribe.default') is used.
$driver = ModelScribe::getDriverManager()->driver('database');
$driver->log($entry);

extend(string driver,Closuredriver, Closure callback): static

Register a factory closure for a custom driver name. The closure receives the driver’s configuration array and the DriverManager instance, and must return a DriverInterface implementation. Call this in a ServiceProvider::boot() method before any logging occurs.
use HypathBel\ModelScribe\Facades\ModelScribe;
use HypathBel\ModelScribe\DriverManager;
use HypathBel\ModelScribe\Contracts\DriverInterface;
use App\Audit\WebhookDriver;

ModelScribe::getDriverManager()->extend(
    'webhook',
    function (array $config, DriverManager $manager): DriverInterface {
        return new WebhookDriver($config);
    }
);

flush(): void

Clears the internal cache of resolved driver instances. All subsequent calls to driver() will re-instantiate from configuration. Primarily useful in test suites where you need a clean driver state between test cases.
ModelScribe::getDriverManager()->flush();

Writing a Custom Driver

Implement DriverInterface and register the class via extend(). The full pattern from contract to configuration looks like this: 1. Create the driver class
<?php

namespace App\Audit;

use HypathBel\ModelScribe\Contracts\DriverInterface;
use HypathBel\ModelScribe\DTOs\LogEntry;
use Illuminate\Support\Facades\Http;

class WebhookDriver implements DriverInterface
{
    public function __construct(protected array $config) {}

    public function log(LogEntry $entry): void
    {
        Http::post($this->config['url'], [
            'event'       => $entry->event->value,
            'log_name'    => $entry->logName,
            'description' => $entry->description,
            'batch_uuid'  => $entry->batchUuid,
        ]);
    }

    public function prune(): int
    {
        // Webhook driver has no persistent store to prune
        return 0;
    }
}
2. Register in a ServiceProvider
<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use HypathBel\ModelScribe\Facades\ModelScribe;
use HypathBel\ModelScribe\DriverManager;
use HypathBel\ModelScribe\Contracts\DriverInterface;
use App\Audit\WebhookDriver;

class AuditServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        ModelScribe::getDriverManager()->extend(
            'webhook',
            function (array $config, DriverManager $manager): DriverInterface {
                return new WebhookDriver($config);
            }
        );
    }
}
3. Configure in config/model-scribe.php
'drivers' => [
    'webhook' => [
        'driver' => 'webhook',
        'url'    => env('AUDIT_WEBHOOK_URL'),
    ],
],
If your custom driver wraps a third-party service that may be unavailable (network timeouts, API rate limits), handle exceptions internally within log() and fail silently or push to a fallback — callers of ModelScribe::log() do not expect audit logging to throw.
Drivers are cached after the first resolution. If you call extend() after a driver of the same name has already been resolved, the cached instance will remain in use until flush() is called.

Build docs developers (and LLMs) love