Conditions

Alpha notice: Expression syntax and condition evaluation follow Symfony Expression Language rules. Edge-case behaviour may change between Core tags.

Condition nodes route workflow execution through outgoing transitions. The runtime does not branch on the condition node object itself — it evaluates transitions[].condition against variables from your workflowable model.

Routing authority

When the runtime reaches a type: condition node, it inspects each outgoing transition in definition order:

  1. Evaluate transitions[].condition when present (Symfony Expression Language).
  2. Take the first transition whose condition is truthy.
  3. If none match, follow the transition marked is_default: true.

The config.expression field on a condition node is optional metadata for editors and documentation. It is not the routing authority. Keep transition conditions aligned with any display expression you store on the node.

// Node — documentation / editor metadata only
[
    'key' => 'amount_gate',
    'type' => 'condition',
    'name' => 'Amount Gate',
    'config' => ['expression' => 'refund_amount >= 500'],
],

// Transitions — runtime routing authority
['from' => 'amount_gate', 'to' => 'risk_reviewer_review', 'condition' => 'refund_amount >= 500'],
['from' => 'amount_gate', 'to' => 'end_approved', 'is_default' => true],

Variable source

Expressions read from WorkflowContextInterface::getWorkflowVariables() on the workflowable model:

use DbflowLabs\Core\Contracts\WorkflowContextInterface;
use DbflowLabs\Core\Traits\HasWorkflow;

class RefundDispute extends Model implements WorkflowContextInterface
{
    use HasWorkflow;

    public function getWorkflowVariables(): array
    {
        return [
            'refund_amount' => (float) $this->refund_amount,
            'risk_score' => (int) $this->risk_score,
        ];
    }
}

The engine evaluates conditions immediately when traversing the graph — there is no pending task at a condition node.

Expression engine

DBFlow alpha uses Symfony Expression Language for transitions[].condition strings.

Common patterns that work well in demo workflows:

Expression Demo workflow
refund_amount >= 500 refund_dispute_approval
amount >= 10000 procurement_request_approval

Prefer comparisons on scalars (float, int, string, bool) that you control explicitly.

Safe variable design

Keep condition inputs predictable:

  • Expose explicit scalars — return amount, department, risk_score; do not pass entire Eloquent models into the variable bag.
  • Cast numeric fields — use (float) $this->amount so string columns do not surprise comparisons.
  • Keep conditions deterministic — avoid time-dependent values, randomness, or external API calls inside getWorkflowVariables().
  • Name transitions after business rules — when a threshold changes, update both the transition condition and any optional config.expression on the node.
  • Validate definitions in CI — run WorkflowDefinitionValidator::validateOrFail($definition) so malformed graphs fail in PHPUnit, not in production.
// Avoid — opaque object references inside expressions
public function getWorkflowVariables(): array
{
    return ['request' => $this]; // do not do this
}

// Prefer — explicit, typed scalars
public function getWorkflowVariables(): array
{
    return [
        'amount' => (float) $this->amount,
        'department' => (string) $this->department,
    ];
}

Default transitions

Every condition node should expose exactly one default outgoing transition (is_default: true). The default path runs when no conditional transition matches.

In the refund demo, amounts below the threshold skip risk_reviewer_review and flow directly to end_approved.

Testing conditions

Branch coverage belongs in integration tests. The dbflow-demo project includes:

  • tests/Feature/RefundDisputeAmountBranchingTest.php — high vs low refund_amount paths
  • tests/Feature/ProcurementRequestDbflowIntegrationTest.php — procurement amount gate

See Testing Workflows for PHPUnit patterns.

What's next

Something wrong? Open an issue on GitHub