The rule builder
The rule language is one way to author a rule; the
fluent rule builder is the other. 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 —
WarrantRule::build() is often clearer than assembling DSL text.
It produces the same AST the parser does, so a built rule flows through identical validation and compilation. 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') ->theyCannot('delete') ->toRule();That builds the same rule as:
if is_self or (is_manager and in_region)they can view, updatethey cannot deleteConnectives
Section titled “Connectives”Each connective has a plain and a negated form, mirroring Laravel’s
where/orWhere/whereNot:
| Method | DSL equivalent |
|---|---|
if / andIf |
and (both are aliases; the first term’s connective is ignored) |
orIf |
or |
ifNot / andIfNot |
and not |
orIfNot |
or not |
Each takes a condition name (with optional parameters) or a closure:
->if('in_team', ['sales', 'eng']) // condition with parameters->orIf(fn ($c) => $c->if('a')->orIf('b')) // closure = a parenthesized groupA closure is a parenthesized group. It receives a bare condition builder — it
has if/orIf/… but no theyCan/theyCannot, because a group is only ever a
condition, never a whole rule.
Precedence is identical to the DSL
Section titled “Precedence is identical to the DSL”not > and > or, so the two front-ends produce byte-for-byte identical trees.
->if('a')->andIf('b')->orIf('c') is (a and b) or c, not a and (b or c). See
operator precedence in the rule
language.
Composing dynamically
Section titled “Composing dynamically”Fold a list inside a group, or branch with when():
$rule = WarrantRule::build() ->if('is_self') ->orIf(function ($c) use ($teamIds) { foreach ($teamIds as $id) { $c->orIf('in_team', [$id]); } }) ->when($includeManagers, fn ($c) => $c->orIf('is_manager')) ->theyCan('view') ->toRule();An empty group folds to false, so it contributes nothing to an or and
vetoes an and — folding an empty list is a safe no-op.
Splicing in DSL text
Section titled “Splicing in DSL text”ifRaw() / orIfRaw() parse a DSL fragment and splice it in as one group —
author the readable part as text, compose the rest structurally:
->ifRaw('is_admin or is_owner', $bindings = [])->andIf('in_region')A rule needs a clause
Section titled “A rule needs a clause”toRule() throws a LogicException if you call neither theyCan nor
theyCannot — exactly as the DSL rejects a bare if with no clause.
Attaching a denial message
Section titled “Attaching a denial message”withDenialMessage() is available mid-chain, to explain a cannot when it fires:
WarrantRule::build() ->if('is_locked')->theyCannot('update') ->withDenialMessage('This document is locked and can no longer be edited.') ->toRule();See Denial messages for the full behaviour.
Built rules go into a WarrantRuleSet just like parsed ones — see
Providing rules for the rule-set
constructors, and the Rule-building API for every
method signature.
