Skip to content

Conditions Branching

Jean-Marc Strauven edited this page Aug 6, 2025 · 1 revision

πŸ”€ Conditions & Branching

← Error Handling | Home | Queue Integration β†’

Laravel Flowpipe supports powerful conditional logic to create dynamic workflows that adapt based on data and business rules.

🧠 Core Concept

Conditional execution allows your workflows to make decisions and follow different paths based on the payload data. This enables dynamic, intelligent workflows that respond to real-world scenarios.

# Simple example: Different paths for different user types
steps:
  - condition:
      field: user_type
      operator: equals
      value: premium
    then:
      - type: action
        class: PremiumUserOnboardingStep
    else:
      - type: action
        class: BasicUserOnboardingStep

πŸ“‹ Basic Conditional Syntax

Simple Condition

steps:
  - condition:
      field: payment_required
      operator: equals
      value: true
    then:
      - type: action
        class: ProcessPaymentStep
    else:
      - type: action
        class: MarkAsFreeStep

Inline Step Conditions

steps:
  - type: action
    name: send-premium-features-email
    class: SendPremiumFeaturesEmailStep
    when:
      field: user_type
      operator: equals
      value: premium

Nested Field Access

steps:
  - condition:
      field: user.profile.country
      operator: equals
      value: "US"
    then:
      - type: action
        class: ApplyUSTaxesStep

πŸŽ›οΈ Available Operators

Equality Operators

# Exact match
operator: equals
operator: not_equals

# Examples
condition:
  field: status
  operator: equals
  value: "active"
  
condition:
  field: verified
  operator: not_equals
  value: false

Comparison Operators

# Numeric comparisons
operator: greater_than
operator: less_than
operator: greater_than_or_equal
operator: less_than_or_equal

# Examples
condition:
  field: order_total
  operator: greater_than
  value: 100

condition:
  field: user_age
  operator: greater_than_or_equal
  value: 18

String Operators

# String operations
operator: contains
operator: not_contains
operator: starts_with
operator: ends_with

# Examples
condition:
  field: email
  operator: ends_with
  value: "@company.com"
  
condition:
  field: description
  operator: contains
  value: "urgent"

Array Operators

# Array membership
operator: in
operator: not_in

# Examples
condition:
  field: country
  operator: in
  value: ["US", "CA", "GB"]
  
condition:
  field: user_role
  operator: not_in
  value: ["banned", "suspended"]

Existence Operators

# Field existence
operator: exists
operator: not_exists
operator: is_null
operator: is_not_null
operator: is_empty
operator: is_not_empty

# Examples
condition:
  field: profile_picture
  operator: exists
  
condition:
  field: middle_name
  operator: is_not_null

πŸ”„ Complex Conditions

Multiple Conditions (AND)

condition:
  all:
    - field: user_type
      operator: equals
      value: premium
    - field: subscription_active
      operator: equals
      value: true
    - field: account_balance
      operator: greater_than
      value: 0
  then:
    - type: action
      class: GrantPremiumAccessStep

Multiple Conditions (OR)

condition:
  any:
    - field: user_role
      operator: equals
      value: admin
    - field: user_role
      operator: equals
      value: moderator
    - field: has_special_permission
      operator: equals
      value: true
  then:
    - type: action
      class: GrantAdminAccessStep

Nested Conditions

condition:
  all:
    - field: order_type
      operator: equals
      value: "subscription"
    - any:
        - field: payment_method
          operator: equals
          value: "credit_card"
        - all:
            - field: payment_method
              operator: equals
              value: "bank_transfer"
            - field: account_verified
              operator: equals
              value: true
  then:
    - type: action
      class: ProcessSubscriptionStep

🌊 Advanced Branching Patterns

Multi-Way Branching

steps:
  # User type routing
  - condition:
      field: user_type
      operator: equals
      value: enterprise
    then:
      - type: group
        name: enterprise-onboarding
        
  - condition:
      field: user_type
      operator: equals
      value: premium
    then:
      - type: group
        name: premium-onboarding
        
  - condition:
      field: user_type
      operator: equals
      value: basic
    then:
      - type: group
        name: basic-onboarding
    else:
      # Default case
      - type: action
        class: DefaultOnboardingStep

Switch-Style Branching

steps:
  - type: switch
    field: payment_method
    cases:
      credit_card:
        - type: action
          class: ProcessCreditCardStep
        - type: action
          class: UpdateCreditCardRewardsStep
          
      paypal:
        - type: action
          class: ProcessPaypalStep
        - type: action
          class: SyncPaypalAccountStep
          
      bank_transfer:
        - type: action
          class: ProcessBankTransferStep
        - type: action
          class: ScheduleBankTransferStep
          
      default:
        - type: action
          class: ProcessGenericPaymentStep

Progressive Enhancement

steps:
  # Base functionality (always executed)
  - type: action
    name: create-basic-profile
    class: CreateBasicProfileStep
    
  # Enhanced features based on user type
  - type: action
    name: add-premium-features
    class: AddPremiumFeaturesStep
    when:
      field: user_type
      operator: in
      value: ["premium", "enterprise"]
      
  - type: action
    name: setup-enterprise-integration
    class: SetupEnterpriseIntegrationStep
    when:
      field: user_type
      operator: equals
      value: enterprise
      
  - type: action
    name: assign-account-manager
    class: AssignAccountManagerStep
    when:
      all:
        - field: user_type
          operator: equals
          value: enterprise
        - field: annual_revenue
          operator: greater_than
          value: 1000000

🎯 Real-World Examples

E-commerce Order Processing

flow: smart-order-processing
description: Intelligent order processing with dynamic routing

send:
  order_id: "ORD-12345"
  customer_type: "premium"
  order_total: 250.00
  items_count: 3
  shipping_country: "US"

steps:
  # Basic validation (always runs)
  - type: action
    name: validate-order
    class: ValidateOrderStep
    
  # Premium customer benefits
  - condition:
      field: customer_type
      operator: equals
      value: premium
    then:
      - type: action
        name: apply-premium-discount
        class: ApplyPremiumDiscountStep
      - type: action
        name: upgrade-shipping
        class: UpgradeToFreeShippingStep
        
  # Large order handling
  - condition:
      field: order_total
      operator: greater_than
      value: 200
    then:
      - type: action
        name: fraud-check
        class: EnhancedFraudCheckStep
      - type: action
        name: manager-approval
        class: RequireManagerApprovalStep
        when:
          field: order_total
          operator: greater_than
          value: 500
          
  # International shipping
  - condition:
      field: shipping_country
      operator: not_equals
      value: "US"
    then:
      - type: action
        name: calculate-duties
        class: CalculateCustomsDutiesStep
      - type: action
        name: verify-shipping-restrictions
        class: VerifyShippingRestrictionsStep
        
  # Payment processing (different methods)
  - condition:
      field: payment_method
      operator: equals
      value: "credit_card"
    then:
      - type: action
        class: ProcessCreditCardPaymentStep
    else:
      - condition:
          field: payment_method
          operator: equals
          value: "paypal"
        then:
          - type: action
            class: ProcessPaypalPaymentStep
        else:
          - type: action
            class: ProcessAlternativePaymentStep

User Content Moderation

flow: content-moderation
description: Smart content moderation with escalation

send:
  content_id: "POST-456"
  user_reputation: 85
  content_type: "image"
  reported_count: 2

steps:
  # Automated checks
  - type: action
    name: spam-detection
    class: SpamDetectionStep
    
  - type: action
    name: profanity-filter
    class: ProfanityFilterStep
    
  # Image-specific checks
  - condition:
      field: content_type
      operator: equals
      value: "image"
    then:
      - type: action
        name: image-content-scan
        class: ImageContentScanStep
      - type: action
        name: nsfw-detection
        class: NsfwDetectionStep
        
  # User reputation-based processing
  - condition:
      field: user_reputation
      operator: greater_than
      value: 90
    then:
      # High reputation users get auto-approved
      - type: action
        name: auto-approve
        class: AutoApproveContentStep
    else:
      - condition:
          any:
            - field: user_reputation
              operator: less_than
              value: 50
            - field: reported_count
              operator: greater_than
              value: 1
        then:
          # Low reputation or reported content goes to human review
          - type: action
            name: queue-human-review
            class: QueueForHumanReviewStep
        else:
          # Medium reputation gets AI review
          - type: action
            name: ai-content-analysis
            class: AiContentAnalysisStep
          - condition:
              field: ai_confidence
              operator: greater_than
              value: 0.8
            then:
              - type: action
                name: auto-approve
                class: AutoApproveContentStep
            else:
              - type: action
                name: queue-human-review
                class: QueueForHumanReviewStep

Dynamic Notification Routing

flow: notification-routing
description: Smart notification delivery based on user preferences

send:
  user_id: 123
  notification_type: "order_update"
  urgency: "medium"
  user_timezone: "America/New_York"

steps:
  # Get user preferences
  - type: action
    name: fetch-preferences
    class: FetchUserPreferencesStep
    
  # Check if notifications are enabled
  - condition:
      field: preferences.notifications_enabled
      operator: equals
      value: false
    then:
      - type: action
        name: log-notification-skipped
        class: LogNotificationSkippedStep
      # Exit early
    else:
      # Determine delivery method based on urgency and preferences
      - condition:
          field: urgency
          operator: equals
          value: "high"
        then:
          # High urgency: multiple channels
          - type: action
            name: send-push-notification
            class: SendPushNotificationStep
          - type: action
            name: send-sms
            class: SendSmsStep
            when:
              field: preferences.sms_enabled
              operator: equals
              value: true
          - type: action
            name: send-email
            class: SendEmailStep
        else:
          # Medium/Low urgency: preferred channel only
          - condition:
              field: preferences.preferred_channel
              operator: equals
              value: "push"
            then:
              - type: action
                name: send-push-notification
                class: SendPushNotificationStep
          - condition:
              field: preferences.preferred_channel
              operator: equals
              value: "email"
            then:
              - type: action
                name: send-email
                class: SendEmailStep
          - condition:
              field: preferences.preferred_channel
              operator: equals
              value: "sms"
            then:
              - type: action
                name: send-sms
                class: SendSmsStep
                
      # Respect quiet hours
      - condition:
          all:
            - field: current_hour_in_user_tz
              operator: greater_than_or_equal
              value: 22
            - field: urgency
              operator: not_equals
              value: "high"
        then:
          - type: action
            name: schedule-for-later
            class: ScheduleNotificationStep
            config:
              delay_until_hour: 9

πŸ”§ PHP-Based Conditions

Custom Condition Classes

<?php

namespace App\Flowpipe\Conditions;

use Grazulex\LaravelFlowpipe\Contracts\Condition;

class BusinessHoursCondition implements Condition
{
    public function evaluate(array $payload, array $context = []): bool
    {
        $timezone = $payload['user_timezone'] ?? 'UTC';
        $now = now($timezone);
        
        // Business hours: 9 AM to 6 PM, Monday to Friday
        return $now->isWeekday() && 
               $now->hour >= 9 && 
               $now->hour < 18;
    }
}

Using Custom Conditions

steps:
  - condition:
      custom: App\Flowpipe\Conditions\BusinessHoursCondition
    then:
      - type: action
        name: process-immediately
        class: ProcessImmediatelyStep
    else:
      - type: action
        name: queue-for-business-hours
        class: QueueForBusinessHoursStep

πŸ§ͺ Testing Conditional Logic

Unit Testing Conditions

<?php

namespace Tests\Unit\Flowpipe;

use Tests\TestCase;
use Grazulex\LaravelFlowpipe\Flowpipe;

class ConditionalLogicTest extends TestCase
{
    public function test_premium_user_gets_special_treatment()
    {
        $result = Flowpipe::fromYaml('user-onboarding')
            ->send([
                'user_type' => 'premium',
                'email' => '[email protected]'
            ])
            ->execute();

        $this->assertTrue($result->isSuccessful());
        $this->assertTrue($result->data['premium_features_enabled']);
        $this->assertEquals('premium', $result->data['onboarding_track']);
    }
    
    public function test_basic_user_gets_standard_treatment()
    {
        $result = Flowpipe::fromYaml('user-onboarding')
            ->send([
                'user_type' => 'basic',
                'email' => '[email protected]'
            ])
            ->execute();

        $this->assertTrue($result->isSuccessful());
        $this->assertFalse($result->data['premium_features_enabled']);
        $this->assertEquals('basic', $result->data['onboarding_track']);
    }
}

Integration Testing Multiple Paths

public function test_order_processing_handles_all_scenarios()
{
    // Test premium customer with large order
    $premiumResult = Flowpipe::fromYaml('smart-order-processing')
        ->send([
            'customer_type' => 'premium',
            'order_total' => 300.00,
            'shipping_country' => 'CA'
        ])
        ->execute();
        
    $this->assertTrue($premiumResult->data['premium_discount_applied']);
    $this->assertTrue($premiumResult->data['enhanced_fraud_check_performed']);
    $this->assertTrue($premiumResult->data['customs_duties_calculated']);
    
    // Test basic customer with small order
    $basicResult = Flowpipe::fromYaml('smart-order-processing')
        ->send([
            'customer_type' => 'basic',
            'order_total' => 50.00,
            'shipping_country' => 'US'
        ])
        ->execute();
        
    $this->assertFalse($basicResult->data['premium_discount_applied']);
    $this->assertFalse($basicResult->data['enhanced_fraud_check_performed']);
}

πŸ’‘ Best Practices

1. Keep Conditions Simple

# Good: Simple, readable condition
condition:
  field: user_type
  operator: equals
  value: premium

# Avoid: Overly complex nested conditions
condition:
  all:
    - any:
        - all: [...]
        - field: complex_field
          operator: contains
          value: "something"
    - any: [...]

2. Use Meaningful Field Names

# Good: Descriptive field names
condition:
  field: subscription_active
  operator: equals
  value: true

# Avoid: Cryptic field names
condition:
  field: flag_1
  operator: equals
  value: true

3. Document Complex Logic

# Document complex business rules
- condition:
    # European users need GDPR consent for marketing emails
    all:
      - field: user_location.region
        operator: equals
        value: "EU"
      - field: gdpr_consent_marketing
        operator: not_equals
        value: true
  then:
    - type: action
      name: skip-marketing-email
      class: SkipMarketingEmailStep

4. Provide Fallbacks

# Always provide an else case for critical paths
- condition:
    field: payment_processor_available
    operator: equals
    value: true
  then:
    - type: action
      class: ProcessPaymentStep
  else:
    # Fallback to queue processing
    - type: action
      class: QueuePaymentForLaterStep

🎯 What's Next?

πŸš€ Laravel Flowpipe

🏠 Home

🏁 Getting Started

πŸ“š Core Concepts

πŸš€ Advanced Features

πŸ› οΈ Tools & Configuration

πŸ“– Examples


πŸ”— GitHub Repository

Clone this wiki locally