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

DI and actions

The facade is the recommended default, not the only way in. Three entry points run the same code: the Sentinel facade (shortest); the manager RoundlyConsulting\Sentinel\SentinelManager — the facade’s root, a container singleton with every facade method under the same signature — injected through the constructor; and the actions in RoundlyConsulting\Sentinel\Actions\…, final readonly classes with one execute() each.

Inject the manager

use RoundlyConsulting\Sentinel\SentinelManager;

final class FixInvoice
{
    public function __construct(private SentinelManager $sentinel) {}

    public function __invoke(Invoice $invoice, User $admin): void
    {
        $this->sentinel->for($invoice)->by($admin)->because('Ticket #412: corrected VAT via SQL')->acknowledge();
    }
}

Call an action

Twenty-one of the 28 host-facing actions take one public request or option object — build them with named arguments. Seven take plain arguments: CheckInstallationAction (none), RetireKeyAction($ring, $keyId), ForgetIdempotencyKeyAction($key, $scope), ReadLedgerHistoryAction($model, $seal, $limit), SignRequestAction($request, $keyId, SigningOptions $options), VerifyRequestSignatureAction($request, ?$profile) and VerifyResponseSignatureAction($response, ?$profile). Where the manager fills in the model’s default seal, AcknowledgeRequest, UnsealRequest and ReadLedgerHistoryAction want the seal name explicitly.

use RoundlyConsulting\Sentinel\Actions\Keys\ImportKeyAction;
use RoundlyConsulting\Sentinel\Actions\Keys\RetireKeyAction;
use RoundlyConsulting\Sentinel\Actions\Seals\AcknowledgeTamperingAction;
use RoundlyConsulting\Sentinel\Actions\Seals\ReadLedgerHistoryAction;
use RoundlyConsulting\Sentinel\Actions\Seals\SealModelAction;
use RoundlyConsulting\Sentinel\DataTransferObjects\AcknowledgeRequest;
use RoundlyConsulting\Sentinel\DataTransferObjects\ImportKeyRequest;
use RoundlyConsulting\Sentinel\DataTransferObjects\SealRequest;
use RoundlyConsulting\Sentinel\Enums\Algorithm;

// The raw actions — a request object…
app(ImportKeyAction::class)->execute(new ImportKeyRequest('http', 'acme-2026-10', Algorithm::Ed25519, $pem, owner: $partner));
app(SealModelAction::class)->execute(new SealRequest($invoice, reason: 'INC-1'));   // null seal = the default one
app(AcknowledgeTamperingAction::class)->execute(new AcknowledgeRequest(
    model: $invoice, seal: 'financial', reason: 'Ticket #412: corrected VAT via SQL', actor: $admin,
));

// …or plain arguments
app(ReadLedgerHistoryAction::class)->execute($invoice, 'financial', 20);   // list<LedgerRecord>, limit 1–1000
app(RetireKeyAction::class)->execute('http', 'acme-2025-10');

Facade method → action

Facade methodAction (input)
check()CheckInstallationAction (no input)
seal()Seals\SealModelAction (SealRequest)
verify()Seals\VerifyModelAction (VerifyRequest)
verifyOrFail()Seals\VerifyModelAction (VerifyRequest)
verifyAll()Seals\VerifyModelsAction (VerifyManyRequest)
verifyMany()Seals\VerifyModelsAction (VerifyManyRequest)
isIntact()Seals\VerifyModelsAction (VerifyManyRequest)
acknowledge()Seals\AcknowledgeTamperingAction (AcknowledgeRequest)
unseal()Seals\UnsealModelAction (UnsealRequest)
ledgerHistory()Seals\ReadLedgerHistoryAction ($model, $seal, $limit)
scan()Seals\ScanSealsAction (ScanOptions)
reseal()Seals\ResealModelsAction (ResealOptions)
resealWhere()Seals\ResealWhereAction (ResealWhereRequest)
updateAndReseal()Seals\UpdateAndResealAction (UpdateAndResealRequest)
sealMissing()Seals\SealMissingAction (BaselineOptions)
generateKey()Keys\GenerateKeyAction (GenerateKeyRequest)
importKey()Keys\ImportKeyAction (ImportKeyRequest)
rotateKey()Keys\RotateKeyAction (RotateKeyRequest)
revokeKey()Keys\RevokeKeyAction (RevokeKeyRequest)
retireKey()Keys\RetireKeyAction ($ring, $keyId)
checkpoint()Ledger\CreateCheckpointAction (CheckpointOptions)
verifyLedger()Ledger\VerifyLedgerAction (LedgerVerifyOptions)
runIdempotent()Idempotency\RunIdempotentAction (IdempotentCall)
forgetIdempotencyKey()Idempotency\ForgetIdempotencyKeyAction ($key, $scope)
issueNonce()Nonces\IssueNonceAction (IssueNonceRequest)
consumeNonce()Nonces\ConsumeNonceAction (ConsumeNonceRequest)
signedRoute()Nonces\IssueSingleUseUrlAction (SignedRouteRequest)
prune()PruneAction (PruneOptions)
signRequest()Signatures\SignRequestAction ($request, $keyId, SigningOptions)
verifyRequestSignature()Signatures\VerifyRequestSignatureAction ($request, $profile)
verifyResponseSignature()Signatures\VerifyResponseSignatureAction ($response, $profile)
  • Each manager method that seals, verifies or changes state resolves its action from the container on every call, so a container binding of an action class replaces it everywhere — facade, handles, model trait and commands. Actions are final; a replacement must offer the same execute() signature, for example a wrapper that logs and delegates to the original.
  • The handles, sub-accessors, model trait, middleware, rule, macros and job middleware all call the manager — never an action — which is why the fake sees every call.
  • Sentinel::fake() (facade only) swaps the manager in the facade and in the container, so an injected SentinelManager is faked too. Calling an action directly bypasses the fake.
  • Data objects carrying secrets (ImportKeyRequest, ConsumeNonceRequest, IssuedNonce, GeneratedKey, RotationResult) print [redacted] in var_dump(), json_encode() and log contexts, and all except ConsumeNonceRequest refuse serialize().
  • Manager methods such as verifyIn(), persist(), retrieved(), beginIdempotentRequest() and rememberNonce() are public only for the package’s own wiring and marked @internal — don’t call them.

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.