wip: major refactoring
This commit is contained in:
@@ -6,98 +6,42 @@ namespace App\Form\Service;
|
||||
|
||||
use App\Form\Model\BookingCreateDto;
|
||||
use App\Form\Model\BookingDtoInterface;
|
||||
use App\Form\ParticipantFieldHandler\Condition\FieldConditionInterface;
|
||||
use App\Form\Service\Abstract\AbstractFieldOptionsProvider;
|
||||
use App\Form\Service\Factory\ParticipantRoomChoiceLoaderFactory;
|
||||
|
||||
/**
|
||||
* Provides dynamic field options and state for participant form fields.
|
||||
* Provides dynamic field options for participant form fields.
|
||||
*
|
||||
* 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.
|
||||
* This service generates context-aware Symfony form field options for
|
||||
* dynamic fields in the participant form system. It handles fields that
|
||||
* require options to be calculated based on the current booking context,
|
||||
* participant data, and business 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 and state generation
|
||||
* - Supports extensible field option generation
|
||||
*
|
||||
* The provider uses a callable pattern for field options and a condition-based
|
||||
* system for field states, enabling complex conditional field behavior.
|
||||
* The provider uses a callable pattern for field options, enabling
|
||||
* lazy evaluation and complex conditional field behavior.
|
||||
*/
|
||||
class ParticipantFieldOptionsProvider implements FieldStateProviderInterface
|
||||
class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
|
||||
{
|
||||
use FormTraversalTrait;
|
||||
|
||||
/** @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 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.
|
||||
* 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.
|
||||
*
|
||||
* @param ParticipantRoomChoiceLoaderFactory $roomChoiceLoaderFactory Factory for creating room choice loaders
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly ParticipantRoomChoiceLoaderFactory $roomChoiceLoaderFactory,
|
||||
) {
|
||||
$this->registerFieldOptionProviders();
|
||||
$this->registerFieldStateConditions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves form field options for a specified dynamic field.
|
||||
*
|
||||
* This is the main entry point for getting field configurations. It looks up
|
||||
* the appropriate option provider for the field and executes it with the
|
||||
* current booking and participant context to generate dynamic field options.
|
||||
*
|
||||
* The returned array contains Symfony form field options such as:
|
||||
* - 'label' - The field label
|
||||
* - 'placeholder' - Placeholder text
|
||||
* - 'choices' - Available choices for choice fields
|
||||
* - 'choice_loader' - Dynamic choice loader for complex choices
|
||||
* - 'disabled' - Whether the field should be disabled
|
||||
* - 'required' - Whether the field is required
|
||||
*
|
||||
* @param string $fieldName The name of the field to configure
|
||||
* @param BookingDtoInterface $bookingDto The current booking data for context (create or edit)
|
||||
* @param int $participantIndex The index of the participant being configured
|
||||
*
|
||||
* @return array<string, mixed> Symfony form field options, or empty array if field not supported
|
||||
*/
|
||||
public function getFieldOptions(string $fieldName, BookingDtoInterface $bookingDto, int $participantIndex): array
|
||||
{
|
||||
// Check if we have a provider for this field
|
||||
if (!isset($this->fieldOptionProviders[$fieldName])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Execute the provider with current context to generate dynamic options
|
||||
return $this->fieldOptionProviders[$fieldName]($bookingDto, $participantIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a field has option provider support.
|
||||
*
|
||||
* This method allows form builders to determine if a field can be
|
||||
* dynamically configured by this service. It's useful for deciding
|
||||
* whether to use static field options or dynamic configuration.
|
||||
*
|
||||
* @param string $fieldName The name of the field to check
|
||||
*
|
||||
* @return bool True if the field has registered option providers, false otherwise
|
||||
*/
|
||||
public function hasFieldOptions(string $fieldName): bool
|
||||
{
|
||||
return isset($this->fieldOptionProviders[$fieldName]);
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -121,7 +65,7 @@ class ParticipantFieldOptionsProvider implements FieldStateProviderInterface
|
||||
* - Easy to test individual field logic
|
||||
* - Supports complex interdependencies
|
||||
*/
|
||||
private function registerFieldOptionProviders(): void
|
||||
protected function registerFieldOptionProviders(): void
|
||||
{
|
||||
// Room assignment field provider (only available for create workflow)
|
||||
$this->fieldOptionProviders['assignedRoomId'] = fn (BookingDtoInterface $bookingDto, int $participantIndex) => [
|
||||
@@ -151,158 +95,4 @@ class ParticipantFieldOptionsProvider implements FieldStateProviderInterface
|
||||
// ],
|
||||
// ];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 BookingDtoInterface $bookingDto The current booking data for context (create or edit)
|
||||
* @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, BookingDtoInterface $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 BookingDtoInterface $bookingDto The current booking data for context (create or edit)
|
||||
* @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(BookingDtoInterface $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