wip: field states and dependencies
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form\Service;
|
||||
|
||||
use App\Form\Model\BookingCreateDto;
|
||||
|
||||
/**
|
||||
* Interface for providing dynamic field state based on conditions.
|
||||
*
|
||||
* Field state providers determine the runtime state of form fields (readonly,
|
||||
* disabled, hidden) based on participant data, other field values, and business
|
||||
* logic. This interface enables centralized field state management with support
|
||||
* for complex conditional logic.
|
||||
*
|
||||
* Key Responsibilities:
|
||||
* - Calculate field states based on current booking and form context
|
||||
* - Support multiple state types (readonly, disabled, hidden, etc.)
|
||||
* - Handle field interdependencies and conditional logic
|
||||
* - Provide efficient state calculation with caching support
|
||||
* - Enable dynamic field state updates during form processing
|
||||
*
|
||||
* State Types:
|
||||
* - readonly: Field is visible but not editable
|
||||
* - disabled: Field is visible but interaction is disabled
|
||||
* - hidden: Field is not displayed in the form
|
||||
* - required: Field becomes mandatory based on conditions
|
||||
* - attr: Custom HTML attributes for advanced styling/behavior
|
||||
*/
|
||||
interface FieldStateProviderInterface
|
||||
{
|
||||
/**
|
||||
* Calculates the dynamic state for a specified field.
|
||||
*
|
||||
* This method evaluates all configured conditions for a field and returns
|
||||
* the appropriate state modifications that should be applied to the field.
|
||||
* The returned array contains Symfony form field attributes that control
|
||||
* field behavior and appearance.
|
||||
*
|
||||
* State attributes may include:
|
||||
* - 'attr' => ['readonly' => true] - Make field readonly
|
||||
* - 'disabled' => true - Disable field interaction
|
||||
* - 'required' => false - Override field requirement
|
||||
* - 'attr' => ['style' => 'display: none'] - Hide field
|
||||
* - 'attr' => ['class' => 'conditional-field'] - Add CSS classes
|
||||
*
|
||||
* @param string $fieldName The name of the field to evaluate
|
||||
* @param BookingCreateDto $bookingDto The current booking data for context
|
||||
* @param int $participantIndex The index of the participant being evaluated
|
||||
* @param array<string, mixed> $formData Current form data (may include partial submissions)
|
||||
*
|
||||
* @return array<string, mixed> Symfony form field options for state modifications, empty if no changes needed
|
||||
*/
|
||||
public function getFieldState(string $fieldName, BookingCreateDto $bookingDto, int $participantIndex, array $formData = []): array;
|
||||
|
||||
/**
|
||||
* Checks whether a field has state conditions configured.
|
||||
*
|
||||
* This method allows form builders to determine if a field has dynamic
|
||||
* state behavior configured. Fields without state conditions use their
|
||||
* default static configuration, while fields with conditions require
|
||||
* runtime state evaluation.
|
||||
*
|
||||
* @param string $fieldName The name of the field to check
|
||||
*
|
||||
* @return bool True if the field has state conditions configured, false otherwise
|
||||
*/
|
||||
public function hasStateConditions(string $fieldName): bool;
|
||||
|
||||
/**
|
||||
* Returns field names that trigger state re-evaluation for a given field.
|
||||
*
|
||||
* This method identifies which form fields, when changed, should trigger
|
||||
* re-evaluation of the specified field's state. This information is used
|
||||
* for dependency tracking and efficient state updates during form processing.
|
||||
*
|
||||
* For example, if field 'serviceSelection' affects the state of field 'ageRestriction',
|
||||
* then 'serviceSelection' should be returned as a dependency for 'ageRestriction'.
|
||||
*
|
||||
* @param string $fieldName The name of the field to get dependencies for
|
||||
*
|
||||
* @return string[] Array of field names that affect the specified field's state
|
||||
*/
|
||||
public function getFieldStateDependencies(string $fieldName): array;
|
||||
|
||||
/**
|
||||
* Calculates field states for all configured fields at once.
|
||||
*
|
||||
* This method provides bulk state calculation for performance optimization
|
||||
* when multiple field states need to be determined simultaneously. It's
|
||||
* particularly useful during form building and bulk state updates.
|
||||
*
|
||||
* @param BookingCreateDto $bookingDto The current booking data for context
|
||||
* @param int $participantIndex The index of the participant being evaluated
|
||||
* @param array<string, mixed> $formData Current form data for condition evaluation
|
||||
*
|
||||
* @return array<string, array<string, mixed>> Field states indexed by field name
|
||||
*/
|
||||
public function getAllFieldStates(BookingCreateDto $bookingDto, int $participantIndex, array $formData = []): array;
|
||||
}
|
||||
@@ -5,36 +5,40 @@ declare(strict_types=1);
|
||||
namespace App\Form\Service;
|
||||
|
||||
use App\Form\Model\BookingCreateDto;
|
||||
use App\Form\ParticipantFieldHandler\Condition\FieldConditionInterface;
|
||||
|
||||
/**
|
||||
* Provides dynamic field options for participant form fields.
|
||||
* Provides dynamic field options and state for participant form fields.
|
||||
*
|
||||
* This service acts as a field option provider registry for the participant form
|
||||
* system. It generates context-aware Symfony form field options based on the
|
||||
* overall booking context and individual participant data.
|
||||
* This service acts as both a field option provider and field state provider
|
||||
* for the participant form system. It generates context-aware Symfony form
|
||||
* field options and calculates dynamic field states based on conditional logic.
|
||||
*
|
||||
* Key Responsibilities:
|
||||
* - Manages field option providers for dynamic fields
|
||||
* - Calculates field states based on conditional logic
|
||||
* - Provides context-aware field configurations
|
||||
* - Handles interdependent field relationships
|
||||
* - Supports extensible field option generation
|
||||
* - Supports extensible field option and state generation
|
||||
*
|
||||
* The provider uses a callable pattern where each field has a function
|
||||
* that generates the appropriate Symfony form options based on the current
|
||||
* booking and participant state.
|
||||
* The provider uses a callable pattern for field options and a condition-based
|
||||
* system for field states, enabling complex conditional field behavior.
|
||||
*/
|
||||
class ParticipantFieldOptionsProvider
|
||||
class ParticipantFieldOptionsProvider implements FieldStateProviderInterface
|
||||
{
|
||||
/** @var array<string, callable> Field option providers indexed by field name */
|
||||
private array $fieldOptionProviders = [];
|
||||
|
||||
/** @var array<string, array<string, FieldConditionInterface>> Field state conditions indexed by field name and state type */
|
||||
private array $fieldStateConditions = [];
|
||||
|
||||
/**
|
||||
* Initializes the provider with required dependencies.
|
||||
*
|
||||
* The provider automatically registers all field option providers
|
||||
* during construction to ensure they're available for form building.
|
||||
* This approach keeps all field configuration logic centralized and
|
||||
* makes it easy to add new dynamic fields.
|
||||
* The provider automatically registers all field option providers and
|
||||
* field state conditions during construction to ensure they're available
|
||||
* for form building. This approach keeps all field configuration logic
|
||||
* centralized and makes it easy to add new dynamic fields.
|
||||
*
|
||||
* @param ParticipantRoomChoiceLoaderFactory $roomChoiceLoaderFactory Factory for creating room choice loaders
|
||||
*/
|
||||
@@ -42,6 +46,7 @@ class ParticipantFieldOptionsProvider
|
||||
private readonly ParticipantRoomChoiceLoaderFactory $roomChoiceLoaderFactory,
|
||||
) {
|
||||
$this->registerFieldOptionProviders();
|
||||
$this->registerFieldStateConditions();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -139,8 +144,160 @@ class ParticipantFieldOptionsProvider
|
||||
// 'Vegetarian' => 'vegetarian',
|
||||
// 'Vegan' => 'vegan',
|
||||
// ],
|
||||
// // Could depend on room assignment or other factors
|
||||
// 'disabled' => !$this->hasMealOptions($bookingDto, $participantIndex),
|
||||
// ];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the dynamic state for a specified field.
|
||||
*
|
||||
* Evaluates all configured conditions for a field and returns the appropriate
|
||||
* state modifications. State conditions are organized by state type (readonly,
|
||||
* disabled, etc.) and evaluated independently.
|
||||
*
|
||||
* @param string $fieldName The name of the field to evaluate
|
||||
* @param BookingCreateDto $bookingDto The current booking data for context
|
||||
* @param int $participantIndex The index of the participant being evaluated
|
||||
* @param array<string, mixed> $formData Current form data for condition evaluation
|
||||
*
|
||||
* @return array<string, mixed> Symfony form field options for state modifications
|
||||
*/
|
||||
public function getFieldState(string $fieldName, BookingCreateDto $bookingDto, int $participantIndex, array $formData = []): array
|
||||
{
|
||||
if (!isset($this->fieldStateConditions[$fieldName])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$stateModifications = [];
|
||||
$attributes = [];
|
||||
|
||||
foreach ($this->fieldStateConditions[$fieldName] as $stateType => $condition) {
|
||||
if ($condition->evaluate($bookingDto, $participantIndex, $formData)) {
|
||||
switch ($stateType) {
|
||||
case 'readonly':
|
||||
$attributes['readonly'] = true;
|
||||
break;
|
||||
case 'disabled':
|
||||
$stateModifications['disabled'] = true;
|
||||
break;
|
||||
case 'required':
|
||||
$stateModifications['required'] = true;
|
||||
break;
|
||||
case 'hidden':
|
||||
$attributes['style'] = ($attributes['style'] ?? '').' display: none;';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($attributes)) {
|
||||
$stateModifications['attr'] = $attributes;
|
||||
}
|
||||
|
||||
return $stateModifications;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a field has state conditions configured.
|
||||
*
|
||||
* @param string $fieldName The name of the field to check
|
||||
*
|
||||
* @return bool True if the field has state conditions configured
|
||||
*/
|
||||
public function hasStateConditions(string $fieldName): bool
|
||||
{
|
||||
return isset($this->fieldStateConditions[$fieldName]) && !empty($this->fieldStateConditions[$fieldName]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns field names that trigger state re-evaluation for a given field.
|
||||
*
|
||||
* @param string $fieldName The name of the field to get dependencies for
|
||||
*
|
||||
* @return string[] Array of field names that affect the specified field's state
|
||||
*/
|
||||
public function getFieldStateDependencies(string $fieldName): array
|
||||
{
|
||||
if (!isset($this->fieldStateConditions[$fieldName])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$dependencies = [];
|
||||
|
||||
foreach ($this->fieldStateConditions[$fieldName] as $condition) {
|
||||
$dependencies = array_merge($dependencies, $condition->getDependentFields());
|
||||
}
|
||||
|
||||
return array_unique($dependencies);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates field states for all configured fields at once.
|
||||
*
|
||||
* @param BookingCreateDto $bookingDto The current booking data for context
|
||||
* @param int $participantIndex The index of the participant being evaluated
|
||||
* @param array<string, mixed> $formData Current form data for condition evaluation
|
||||
*
|
||||
* @return array<string, array<string, mixed>> Field states indexed by field name
|
||||
*/
|
||||
public function getAllFieldStates(BookingCreateDto $bookingDto, int $participantIndex, array $formData = []): array
|
||||
{
|
||||
$allStates = [];
|
||||
|
||||
foreach (array_keys($this->fieldStateConditions) as $fieldName) {
|
||||
$fieldState = $this->getFieldState($fieldName, $bookingDto, $participantIndex, $formData);
|
||||
if (!empty($fieldState)) {
|
||||
$allStates[$fieldName] = $fieldState;
|
||||
}
|
||||
}
|
||||
|
||||
return $allStates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers field state conditions during service initialization.
|
||||
*
|
||||
* This method defines the conditional logic for field states. Each field
|
||||
* can have multiple state conditions (readonly, disabled, hidden, required)
|
||||
* that are evaluated independently.
|
||||
*
|
||||
* Adding new field state conditions:
|
||||
* To add conditional state logic for a field, register conditions here:
|
||||
*
|
||||
* $this->fieldStateConditions['fieldName'] = [
|
||||
* 'readonly' => new SomeCondition(),
|
||||
* 'disabled' => CompositeCondition::and(
|
||||
* new AgeRangeCondition(null, 17),
|
||||
* FieldValueCondition::equals('someField', 'someValue')
|
||||
* ),
|
||||
* ];
|
||||
*
|
||||
* Condition Types:
|
||||
* - 'readonly': Field is visible but not editable
|
||||
* - 'disabled': Field interaction is disabled
|
||||
* - 'required': Field becomes mandatory
|
||||
* - 'hidden': Field is not displayed
|
||||
*/
|
||||
private function registerFieldStateConditions(): void
|
||||
{
|
||||
// Example field state conditions would be registered here
|
||||
// For demonstration purposes, here are some example patterns:
|
||||
|
||||
// Example 1: Make assignedRoomId readonly for participants under 18
|
||||
// $this->fieldStateConditions['assignedRoomId'] = [
|
||||
// 'readonly' => new AgeRangeCondition(null, 17),
|
||||
// ];
|
||||
|
||||
// Example 2: Disable service selection if no room is assigned
|
||||
// $this->fieldStateConditions['serviceSelection'] = [
|
||||
// 'disabled' => FieldValueCondition::isEmpty('assignedRoomId'),
|
||||
// ];
|
||||
|
||||
// Example 3: Complex condition with multiple criteria
|
||||
// $this->fieldStateConditions['advancedOptions'] = [
|
||||
// 'hidden' => CompositeCondition::or(
|
||||
// new AgeRangeCondition(null, 15),
|
||||
// FieldValueCondition::equals('userType', 'basic')
|
||||
// ),
|
||||
// ];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user