›
byrcsc/laravel-approval · 1.x
Skip a stage when the proposed model values do not meet its condition.
A condition decides whether a stage applies to a submission. You can require board approval above ₱100,000 or legal review only for external suppliers.
use ByRcsc\LaravelApproval\Contracts\StageCondition;
use ByRcsc\LaravelApproval\Data\StageSnapshot;
use Illuminate\Database\Eloquent\Model;
final class AmountExceeds implements StageCondition
{
public function passes(
StageSnapshot $stage,
Model $approvable,
Model $requester,
array $config,
): bool {
return $approvable->amount > ($config['threshold'] ?? 0);
}
}Attach it to the stage:
$workflow->stages()->create([
'sequence' => 2,
'name' => 'Board',
'required_approvals' => 2,
'condition' => AmountExceeds::class,
'condition_config' => ['threshold' => 100_000],
]);Conditions are resolved from the container, so constructor injection works.
The $approvable a condition receives is the proposed record, not the
persisted one. When a request holds an attribute draft, the drafted values are
filled onto a clone before the condition runs:
// Persisted amount is 4_000; the draft proposes 150_000.
$order->submitForApproval('purchase-order', ['amount' => 150_000]);
// AmountExceeds sees 150_000 and the board stage applies.The clone is discarded immediately. The real model is never mutated, and approver resolvers still receive the persisted record.
Conditions run during submission, before the request is written. Each stage
stores its result in condition_result, and a stage whose condition did not
pass is created directly in the skipped state.
Nothing re-evaluates a condition afterwards. A record edited mid-flight does not switch a skipped stage back on, for the same reason approver snapshots do not update: the request runs the rules it was submitted under.
A skipped stage is recorded in the audit trail as a stage_skipped action
naming the condition class, so the history explains why a stage never ran.
A missing condition class, or one that does not implement StageCondition,
raises InvalidWorkflowException before submission writes any rows. Workflow
definitions perform the same check when the definition is loaded.
A workflow whose stages all skip has nothing to wait on. The request is
submitted, ApprovalCompleted fires immediately, the draft is applied through
the usual stale check, and the request reaches approved without any human
decision.
This result means the workflow decided that no human approval was required. Add an unconditional first stage when every request must receive at least one decision.
A skipped stage produces no assignments, but Laravel Approval still validates
its configuration. Resolvers run for every stage. If required_approvals
exceeds the number of resolved approvers, submission throws
InvalidWorkflowException::approvalRuleUnreachable(). This validation also
applies when the stage is skipped.
One check is condition-aware: only a stage whose condition passes must
resolve at least one approver. A stage that does not apply is allowed to have
nobody; a stage that does apply and has nobody is a workflow that would stall,
and submission fails with InvalidWorkflowException::stageHasNoApprovers().
foreach ($request->timeline() as $entry) {
$entry->state; // TimelineState::ConditionSkipped for condition skips
}TimelineState distinguishes condition_skipped from an ordinary skipped,
which is what a stage becomes when the request ended before reaching it. The
underlying StageStatus is skipped in both cases; the timeline separates
them by reading condition_result.