wip: major refactoring
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form\Service\Abstract;
|
||||
|
||||
use App\Form\Model\BookingDtoInterface;
|
||||
use App\Form\Service\Contract\FieldOptionsProviderInterface;
|
||||
|
||||
/**
|
||||
* Abstract base class for field options providers.
|
||||
*
|
||||
* This class contains common field options generation logic shared between
|
||||
* different field options provider implementations. It provides the core
|
||||
* functionality for managing field option providers and generating dynamic
|
||||
* field configurations.
|
||||
*/
|
||||
abstract class AbstractFieldOptionsProvider implements FieldOptionsProviderInterface
|
||||
{
|
||||
/** @var array<string, callable> Field option providers indexed by field name */
|
||||
protected array $fieldOptionProviders = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->registerFieldOptionProviders();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves form field options for a specified dynamic field.
|
||||
*
|
||||
* This method 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 that will be
|
||||
* used when building the form field. These options are merged with any
|
||||
* static options defined in the form type.
|
||||
*
|
||||
* @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]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers field option providers during service initialization.
|
||||
*
|
||||
* This method must be implemented by concrete classes to define their
|
||||
* specific field option providers. Each provider is a callable that
|
||||
* receives the booking DTO and participant index and returns appropriate
|
||||
* Symfony form field options.
|
||||
*
|
||||
* Example implementation:
|
||||
*
|
||||
* protected function registerFieldOptionProviders(): void
|
||||
* {
|
||||
* $this->fieldOptionProviders['fieldName'] = fn($bookingDto, $participantIndex) => [
|
||||
* 'label' => 'Field Label',
|
||||
* 'choices' => $this->generateChoicesFor($bookingDto, $participantIndex),
|
||||
* ];
|
||||
* }
|
||||
*/
|
||||
abstract protected function registerFieldOptionProviders(): void;
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form\Service\Abstract;
|
||||
|
||||
use App\Form\Model\BookingDtoInterface;
|
||||
use App\Form\Service\Contract\FieldConditionInterface;
|
||||
use App\Form\Service\Contract\FieldStateProviderInterface;
|
||||
use App\Form\Service\Trait\FormTraversalTrait;
|
||||
|
||||
/**
|
||||
* Abstract base class for field state providers.
|
||||
*
|
||||
* This class contains common field state evaluation logic shared between
|
||||
* different field state provider implementations. It provides the core
|
||||
* functionality for evaluating field conditions and applying state modifications.
|
||||
*/
|
||||
abstract class AbstractFieldStateProvider implements FieldStateProviderInterface
|
||||
{
|
||||
use FormTraversalTrait;
|
||||
|
||||
/** @var array<string, array<string, FieldConditionInterface>> Field state conditions indexed by field name and state type */
|
||||
protected array $fieldStateConditions = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->registerFieldStateConditions();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 must be implemented by concrete classes to define their
|
||||
* specific field state conditions.
|
||||
*/
|
||||
abstract protected function registerFieldStateConditions(): void;
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form\Service\Abstract;
|
||||
|
||||
use App\Form\Model\BookingDtoInterface;
|
||||
use App\Form\Service\Contract\ParticipantFieldHandlerInterface;
|
||||
|
||||
/**
|
||||
* Abstract base class providing common functionality for participant field handlers.
|
||||
*
|
||||
* This class implements common patterns used across participant field handlers,
|
||||
* including default implementations for field state modification methods.
|
||||
* Reduces boilerplate code in concrete implementations.
|
||||
*/
|
||||
abstract class AbstractParticipantFieldHandler implements ParticipantFieldHandlerInterface
|
||||
{
|
||||
/**
|
||||
* Returns the field names this handler depends on.
|
||||
*
|
||||
* Default implementation returns an empty array, meaning no dependencies.
|
||||
* Override this method in concrete handlers that depend on other fields
|
||||
* being processed first (e.g., a meal preference handler might depend on
|
||||
* the room assignment being processed first).
|
||||
*
|
||||
* @return string[] Array of field names that must be processed before this handler
|
||||
*/
|
||||
public function getDependencies(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if this handler should process the field based on submitted data.
|
||||
*
|
||||
* Default implementation checks if the field exists in the submitted data.
|
||||
* Override this method for more complex processing conditions (e.g., only
|
||||
* process if certain other conditions are met).
|
||||
*
|
||||
* @param array<string, mixed> $submittedData The submitted participant form data
|
||||
* @param int $participantIndex The index of the participant being processed
|
||||
*
|
||||
* @return bool True if the handler should process this field, false otherwise
|
||||
*/
|
||||
public function shouldProcess(array $submittedData, int $participantIndex): bool
|
||||
{
|
||||
return isset($submittedData[$this->getFieldName()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely retrieves a participant object from the booking DTO.
|
||||
*
|
||||
* This helper method provides safe access to participant data by checking
|
||||
* if the participant exists at the given index. Returns null if the
|
||||
* participant doesn't exist, preventing array access errors.
|
||||
*
|
||||
* @param BookingDtoInterface $bookingDto The booking DTO containing participants (create or edit)
|
||||
* @param int $participantIndex The index of the participant to retrieve
|
||||
*
|
||||
* @return object|null The participant object, or null if not found
|
||||
*/
|
||||
protected function getParticipant(BookingDtoInterface $bookingDto, int $participantIndex): ?object
|
||||
{
|
||||
$participants = $bookingDto->getParticipants();
|
||||
return $participants[$participantIndex] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely extracts a field value from submitted participant data.
|
||||
*
|
||||
* This helper method provides safe access to form field values using
|
||||
* the null coalescing operator. Useful for extracting field values
|
||||
* without worrying about undefined array keys.
|
||||
*
|
||||
* @param array<string, mixed> $submittedData The submitted participant form data
|
||||
* @param string $fieldName The name of the field to retrieve
|
||||
* @param mixed $default The default value to return if field is not set
|
||||
*
|
||||
* @return mixed The field value, or the default value if not found
|
||||
*/
|
||||
protected function getFieldValue(array $submittedData, string $fieldName, mixed $default = null): mixed
|
||||
{
|
||||
return $submittedData[$fieldName] ?? $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes empty string values to null.
|
||||
*
|
||||
* Form inputs often submit empty strings for unselected/empty fields.
|
||||
* This helper converts those empty strings to null values, which is
|
||||
* typically more appropriate for database storage and business logic.
|
||||
*
|
||||
* @param mixed $value The value to normalize
|
||||
*
|
||||
* @return mixed The normalized value (null if empty, original value otherwise)
|
||||
*/
|
||||
protected function normalizeEmptyValue(mixed $value): mixed
|
||||
{
|
||||
return empty($value) ? null : $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts and normalizes string values to integers.
|
||||
*
|
||||
* Form inputs submit all values as strings. This helper safely converts
|
||||
* string values to integers while handling empty strings and non-numeric
|
||||
* values gracefully by returning null.
|
||||
*
|
||||
* @param mixed $value The value to convert to integer
|
||||
*
|
||||
* @return int|null The integer value, or null if empty/non-numeric
|
||||
*/
|
||||
protected function normalizeIntValue(mixed $value): ?int
|
||||
{
|
||||
if (empty($value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return is_numeric($value) ? (int) $value : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns field state modifications that should be applied after processing.
|
||||
*
|
||||
* Default implementation returns no state modifications. Override this method
|
||||
* in concrete handlers that need to modify field states based on their
|
||||
* processing results.
|
||||
*
|
||||
* @param array<string, mixed> $submittedData The submitted participant form data
|
||||
* @param BookingDtoInterface $bookingDto The booking DTO (potentially modified by processing)
|
||||
* @param int $participantIndex The participant index being processed
|
||||
*
|
||||
* @return array<string, array<string, mixed>> Empty array (no state modifications by default)
|
||||
*/
|
||||
public function getFieldStateModifications(array $submittedData, BookingDtoInterface $bookingDto, int $participantIndex): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns field names whose state is affected by this handler's processing.
|
||||
*
|
||||
* Default implementation returns an empty array, meaning this handler doesn't
|
||||
* affect the state of any fields. Override this method in concrete handlers
|
||||
* that modify field states.
|
||||
*
|
||||
* @return string[] Empty array (no affected fields by default)
|
||||
*/
|
||||
public function getAffectedFieldNames(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user