Skip to content

Conditions

Conditions are the predicates a rule’s if may test. Each is a public method on the schema, marked #[TargetedCondition] or #[GlobalCondition]. A condition’s one job is to emit SQL — there is no in-memory evaluation path, so a condition behaves identically whether you’re filtering a list or checking one row.

The name a rule uses is the method name snake-cased, with no prefix added or stripped: isSelfis_self, managesTeammanages_team. Override it by passing a key to the attribute:

#[TargetedCondition('is_owner')]
public function isSelf(TargetedConditionContext $c): Builder { /* ... */ }

The distinction is: does this predicate talk about a specific row?

#[TargetedCondition] — constrains which rows match

Section titled “#[TargetedCondition] — constrains which rows match”

Its context is a TargetedConditionContext carrying targetSqlId — the qualified primary-key SQL id of the row under test (documents.id). Mutate $c->query to add the WHERE fragment and return the builder:

use Illuminate\Contracts\Database\Query\Builder;
use Warrant\Schema\Conditions\TargetedConditionContext;
use Warrant\TargetedCondition;
#[TargetedCondition]
public function isSelf(TargetedConditionContext $c): Builder
{
// $c->targetSqlId === "documents.id" (the correlated row under test)
return $c->query->whereRaw(
'documents.user_id = ?',
[$c->user->getAuthIdentifier()],
);
}

Your predicate may reference any column of the entity’s table; it’s evaluated correlated to the row under test.

#[GlobalCondition] — about the user or the world

Section titled “#[GlobalCondition] — about the user or the world”

Its context is a GlobalConditionContext (no targetSqlId). It may mutate $c->query like a targeted condition, or simply return a bool:

use Warrant\GlobalCondition;
use Warrant\Schema\Conditions\GlobalConditionContext;
#[GlobalCondition]
public function isAdmin(GlobalConditionContext $c): bool
{
return (bool) $c->user->is_admin; // true = holds for this user
}

Some checks run with no rowcapability checks and getUserAbilities() with no target. In that context a targeted condition can’t be evaluated, so Warrant treats it as false (and therefore not <targeted> as true). Global conditions still evaluate normally. This is why a capability schema should only use global conditions.

Every condition method takes a single context object and returns Builder (mutated) or, for a global condition, a bool. The object carries:

Property Type Present on
$c->user Authenticatable both
$c->query Builder (query builder) both
$c->arguments array both
$c->context array both
$c->targetSqlId string targeted only

A condition can take arguments from the rule (in_team('sales')). The resolved arguments arrive on $c->arguments, in order:

#[TargetedCondition]
public function inTeam(TargetedConditionContext $c): Builder
{
// in_team('sales', 'eng') -> $c->arguments === ['sales', 'eng']
return $c->query->whereIn('documents.team_id', $c->arguments);
}

A condition that ignores arguments simply never reads $c->arguments. Arguments come from inline literals, bindings, or @context; a value passed via a binding reaches you verbatim — any PHP type, including arrays and objects.

Every condition also receives the full effective context on $c->context, whether or not the rule passed a value via @context. Reach into it directly when a condition is inherently tied to the frame — then the rule needn’t mention the key at all:

#[TargetedCondition]
public function inCurrentWorkspace(TargetedConditionContext $c): Builder
{
// Rule is just `if in_current_workspace they can view` — no @context needed.
return $c->query->where('documents.workspace_id', $c->context['workspace_id']);
}

The difference from @context: a condition reading $c->context itself always runs and decides for itself, whereas a missing optional @context key soft-falses the condition automatically. See Check-time context for that mechanism.

Every condition leaf is wrapped as an EXISTS subquery, which makes it a strict boolean: a condition touching a NULL column yields false, not SQL’s “unknown,” and negation via NOT EXISTS is exact. That’s why not / cannot behave predictably. See How it compiles to SQL.