Skip to main content

Custom Policies

Policies enforce business rules at ExtensionStage::POLICY (priority 300) — after validation and context resolution, before canonicalization. They either allow an entry to proceed (return normally) or reject it (throw PolicyViolationException).

The pattern

Extend AbstractPolicy and implement enforce():

use Chronicle\Entry\PendingEntry;
use Chronicle\Exceptions\PolicyViolationException;
use Chronicle\Policy\AbstractPolicy;

class RequiresBusinessHoursPolicy extends AbstractPolicy
{
public function enforce(PendingEntry $entry): void
{
$hour = now()->hour;

if ($hour < 8 || $hour >= 18) {
throw new PolicyViolationException(
'Chronicle entries are only permitted during business hours (08:00–18:00).'
);
}
}
}

AbstractPolicy fixes stage() to ExtensionStage::POLICY and wires process() to call enforce() — both methods are final so you cannot accidentally change the stage or break the pipeline contract.

Accessing entry data

Read any entry attribute via PendingEntry::attribute():

public function enforce(PendingEntry $entry): void
{
$action = $entry->attribute('action');

if (str_starts_with((string) $action, 'admin.')) {
if (! Auth::user()?->isAdmin()) {
throw new PolicyViolationException(
"Action [$action] requires admin privileges."
);
}
}
}

Custom exception subclasses

PolicyViolationException extends ChronicleException. Subclass it for specific rejection types so callers can catch selectively:

use Chronicle\Exceptions\PolicyViolationException;

class BusinessHoursViolationException extends PolicyViolationException {}

Registration

Add to config/chronicle.php:

'extensions' => [
// built-in validators...
RequiresBusinessHoursPolicy::class,
],

Or at runtime from a service provider:

Chronicle::extendEntry(RequiresBusinessHoursPolicy::class);

Priority within the POLICY stage

Policies run at stage value 300. If you register multiple policies and need a specific order, implement PrioritizedEntryExtension:

use Chronicle\Contracts\PrioritizedEntryExtension;

class RequiresBusinessHoursPolicy extends AbstractPolicy implements PrioritizedEntryExtension
{
public function priority(): int
{
return -10; // runs before other policies at default priority 0
}

public function enforce(PendingEntry $entry): void { ... }
}

See also