Providing rules
Rules are data. Warrant never invents them — it asks your resolver for them at request time. This is the seam where your access-control model meets Warrant.
The RuleResolver interface
Section titled “The RuleResolver interface”Implement one method. Given a context, return the WarrantRuleSet that governs
this user’s access to that resource:
use Warrant\RuleResolutionContext;use Warrant\RuleResolver;use Warrant\RuleSyntaxTree\WarrantRuleSet;
class DatabaseRuleResolver implements RuleResolver{ public function resolve(RuleResolutionContext $context): WarrantRuleSet { // $context->user — the Authenticatable being checked (nullable) // $context->schemaKey — e.g. 'documents' // $context->schema — the schema class string // $context->model — the model class string, or null (capability schema)
$grants = DB::table('role_permissions') ->where('role_id', $context->user->role_id) ->where('resource', $context->schemaKey) ->pluck('rule'); // ['if is_self they can view', ...]
return WarrantRuleSet::fromSyntax( $context->schemaKey, $grants->implode("\n"), // rules concatenate freely ); }}Store rule strings in a table, compose them from role flags, read them from JWT
claims — whatever fits. Warrant only cares that you return a WarrantRuleSet.
Building a rule set
Section titled “Building a rule set”Three ways to construct a WarrantRuleSet. The first argument is always the
schema (a model instance, a schema instance, or a schema/model class string, or a
plain schema-key string):
From syntax
Section titled “From syntax”Parse a string, resolving bindings inline:
WarrantRuleSet::fromSyntax('documents', 'if is_self they can view', $bindings = []);From already-parsed rules
Section titled “From already-parsed rules”Build individual WarrantRules and compose them. fromRules takes a variadic
list or a single array (it flattens a mix of both), accepts builders directly,
and takes no bindings (the rules are already resolved):
use Warrant\RuleSyntaxTree\WarrantRule;
$own = WarrantRule::fromSyntax('if is_self they can view, update');$noDelete = WarrantRule::fromSyntax('they cannot delete');
WarrantRuleSet::fromRules('documents', $own, $noDelete);WarrantRuleSet::fromRules('documents', [$own, $noDelete]); // equivalentWith a build callback
Section titled “With a build callback”WarrantRuleSet::build hands you a factory; each $rule() call appends a builder:
WarrantRuleSet::build('documents', function ($rule) { $rule()->if('is_self')->theyCan('view', 'update'); $rule()->theyCannot('delete');});Directly with the parser
Section titled “Directly with the parser”If you want the parsed rules without a rule set:
use Warrant\RuleSyntaxTree\Parsing\WarrantParser;
$rules = WarrantParser::parse('if is_self they can view', $bindings = []); // WarrantRule[]$one = WarrantParser::parseSingleRule('they cannot delete'); // WarrantRuleBuilding rules programmatically
Section titled “Building rules programmatically”When a rule’s shape depends on runtime data — a list of team ids, a feature
flag, values that don’t belong in a string — the fluent builder is often clearer
than assembling DSL text. WarrantRule::build() produces the same AST the
parser does, and nothing is serialized to a string, so arbitrary PHP values in
condition parameters survive untouched:
use Warrant\RuleSyntaxTree\WarrantRule;
$rule = WarrantRule::build() ->if('is_self') ->orIf(fn ($c) => $c->if('is_manager')->andIf('in_region')) ->theyCan('view', 'update') ->toRule();The builder is its own topic — connectives, parenthesized groups, dynamic composition, and splicing in DSL text are all covered in The rule builder.
Implicit rules
Section titled “Implicit rules”A schema can declare rules always merged into the rule set, regardless of
what the resolver returns, by overriding implicitRules(). They’re added to
every resolved rule set before compilation, so they’re validated and obey
deny-overrides exactly like resolver rules — and, like every rule, they’re still
evaluated against the current user via their conditions:
use Warrant\RuleSyntaxTree\WarrantRule;
class DocumentSchema extends WarrantSchema{ protected function implicitRules(): array { return [ WarrantRule::fromSyntax('if is_admin they can *'), WarrantRule::fromSyntax('if is_suspended they cannot *'), ]; }}Because deny-overrides is order-independent, an implicit cannot beats any
resolver-supplied can — ideal for baseline guarantees like an admin escape
hatch or a suspension lockout.
Registering the resolver
Section titled “Registering the resolver”Warrant ships no default resolver. Configure one in config/warrant.php,
plus the list of schemas:
return [ 'rule_resolver' => App\Warrant\DatabaseRuleResolver::class,
'schemas' => [ App\Warrant\DocumentSchema::class, App\Warrant\ProjectSchema::class, ],];