Files
myep/docs/FIELD_STATE_SYSTEM.md
T

5.1 KiB

Universal Conditional Field State System

This system provides a flexible architecture for implementing conditional field states (readonly, disabled, hidden) based on participant data and interdependent field values.

Architecture Overview

The system consists of several key components:

  1. FieldConditionInterface - Defines the contract for condition evaluation
  2. Concrete Conditions - Implement specific business logic (age ranges, field values, etc.)
  3. CompositeCondition - Combines conditions with AND/OR/NOT logic
  4. FieldStateProviderInterface - Manages field state calculation
  5. ParticipantFieldOptionsProvider - Enhanced to support state conditions
  6. Form Integration - Applied in BookingCreateParticipantType

Usage Examples

Basic Age-Based Condition

// Make a field readonly for participants under 18
$this->fieldStateConditions['serviceSelection'] = [
    'readonly' => new AgeRangeCondition(null, 17),
];

Field Dependency Condition

// Disable field if room is not assigned
$this->fieldStateConditions['mealPreference'] = [
    'disabled' => FieldValueCondition::empty('assignedRoomId'),
];

Complex Composite Condition

// Hide field for young participants OR if basic service is selected
$this->fieldStateConditions['advancedOptions'] = [
    'hidden' => CompositeCondition::or(
        new AgeRangeCondition(null, 15),
        FieldValueCondition::equals('serviceType', 'basic')
    ),
];

Multiple State Conditions

// Field with multiple conditional states
$this->fieldStateConditions['specialServices'] = [
    'readonly' => new AgeRangeCondition(null, 17),
    'required' => FieldValueCondition::equals('roomType', 'premium'),
    'disabled' => CompositeCondition::and(
        FieldValueCondition::empty('assignedRoomId'),
        FieldValueCondition::notEquals('participantType', 'staff')
    ),
];

Available Conditions

AgeRangeCondition

  • new AgeRangeCondition(18, null) - At least 18 years old
  • new AgeRangeCondition(null, 17) - Under 18 years old
  • new AgeRangeCondition(18, 65) - Between 18 and 65 years old

FieldValueCondition

  • FieldValueCondition::equals('field', 'value') - Field equals specific value
  • FieldValueCondition::notEquals('field', 'value') - Field does not equal value
  • FieldValueCondition::in('field', ['a', 'b']) - Field value is in array
  • FieldValueCondition::empty('field') - Field is empty or null
  • FieldValueCondition::isNotEmpty('field') - Field has a value

CompositeCondition

  • CompositeCondition::and($cond1, $cond2) - All conditions must be true
  • CompositeCondition::or($cond1, $cond2) - At least one condition must be true
  • CompositeCondition::not($condition) - Inverts condition result

State Types

  • readonly - Field is visible but not editable
  • disabled - Field interaction is disabled
  • required - Field becomes mandatory
  • hidden - Field is not displayed (via CSS display: none)

Adding New Conditions

To register field state conditions, add them to the registerFieldStateConditions() method in ParticipantFieldOptionsProvider:

private function registerFieldStateConditions(): void
{
    // Age-based readonly state
    $this->fieldStateConditions['assignedRoomId'] = [
        'readonly' => new AgeRangeCondition(null, 17),
    ];
    
    // Field dependency
    $this->fieldStateConditions['mealPreference'] = [
        'disabled' => FieldValueCondition::empty('assignedRoomId'),
    ];
    
    // Complex business logic
    $this->fieldStateConditions['advancedServices'] = [
        'hidden' => CompositeCondition::or(
            new AgeRangeCondition(null, 15),
            FieldValueCondition::equals('membershipLevel', 'basic')
        ),
        'required' => FieldValueCondition::equals('roomType', 'suite'),
    ];
}

Performance Considerations

  • Conditions use lazy evaluation and short-circuit logic
  • Field state calculations are cached during form processing
  • Dependency tracking prevents unnecessary re-evaluations
  • Bulk state calculation optimizes multiple field updates

Integration with HTMX

The system supports real-time field state updates:

  1. Field changes trigger dependency re-evaluation
  2. State modifications are applied via form rebuilding
  3. HTMX can update field states without full page refresh
  4. Dependency tracking ensures only affected fields are updated

Extending the System

Custom Conditions

Create new condition classes implementing FieldConditionInterface:

class CustomBusinessRuleCondition implements FieldConditionInterface
{
    public function evaluate(BookingCreateDto $bookingDto, int $participantIndex, array $formData): bool
    {
        // Custom business logic here
        return true;
    }
    
    public function getDependentFields(): array
    {
        return ['fieldThatTriggersThisCondition'];
    }
    
    public function getDescription(): string
    {
        return 'Custom business rule description';
    }
}

This system provides a powerful, maintainable foundation for complex conditional field behavior while maintaining clean separation of concerns and extensibility.