validateOperator($operator); $this->validateExpectedValue($operator, $expectedValue); } /** * Evaluates the field value condition against current form data. * * Retrieves the current value of the specified field and compares it * against the expected value using the configured operator. Supports * both participant-level fields and booking-level fields. * * @param BookingDtoInterface $bookingDto The current booking data (create or edit) * @param int $participantIndex The index of the participant being evaluated * @param array $formData Current form data for condition evaluation * * @return bool True if the field value meets the condition criteria, false otherwise */ public function evaluate(BookingDtoInterface $bookingDto, int $participantIndex, array $formData): bool { $fieldValue = $this->getFieldValue($formData, $participantIndex, $bookingDto); return match ($this->operator) { self::OPERATOR_EQUALS => $this->compareEquals($fieldValue, $this->expectedValue), self::OPERATOR_NOT_EQUALS => !$this->compareEquals($fieldValue, $this->expectedValue), self::OPERATOR_IN => $this->compareIn($fieldValue, $this->expectedValue), self::OPERATOR_NOT_IN => !$this->compareIn($fieldValue, $this->expectedValue), self::OPERATOR_EMPTY => $this->isEmpty($fieldValue), self::OPERATOR_NOT_EMPTY => !$this->isEmpty($fieldValue), default => false, }; } /** * Returns the field names that this condition depends on. * * This condition depends on the field being evaluated, so any changes * to that field should trigger re-evaluation of dependent field states. * * @return string[] Array containing the field name being evaluated */ public function getDependentFields(): array { return [$this->fieldName]; } /** * Returns a human-readable description of the field value condition. * * Generates a descriptive string explaining what field value is being * checked and what the expected criteria are. * * @return string Description of the field value condition */ public function getDescription(): string { $valueDescription = is_array($this->expectedValue) ? '['.implode(', ', $this->expectedValue).']' : (string) $this->expectedValue; return match ($this->operator) { self::OPERATOR_EQUALS => sprintf('Field "%s" equals %s', $this->fieldName, $valueDescription), self::OPERATOR_NOT_EQUALS => sprintf('Field "%s" does not equal %s', $this->fieldName, $valueDescription), self::OPERATOR_IN => sprintf('Field "%s" is in %s', $this->fieldName, $valueDescription), self::OPERATOR_NOT_IN => sprintf('Field "%s" is not in %s', $this->fieldName, $valueDescription), self::OPERATOR_EMPTY => sprintf('Field "%s" is empty', $this->fieldName), self::OPERATOR_NOT_EMPTY => sprintf('Field "%s" is not empty', $this->fieldName), default => sprintf('Field "%s" %s %s', $this->fieldName, $this->operator, $valueDescription), }; } /** * Creates a condition for field equality. * * @param string $fieldName The field to check * @param mixed $expectedValue The expected value */ public static function equals(string $fieldName, mixed $expectedValue): self { return new self($fieldName, self::OPERATOR_EQUALS, $expectedValue); } /** * Creates a condition for field non-equality. * * @param string $fieldName The field to check * @param mixed $expectedValue The value that should not match */ public static function notEquals(string $fieldName, mixed $expectedValue): self { return new self($fieldName, self::OPERATOR_NOT_EQUALS, $expectedValue); } /** * Creates a condition for field value inclusion. * * @param string $fieldName The field to check * @param array $allowedValues The allowed values */ public static function in(string $fieldName, array $allowedValues): self { return new self($fieldName, self::OPERATOR_IN, $allowedValues); } /** * Creates a condition for field emptiness. * * @param string $fieldName The field to check */ public static function empty(string $fieldName): self { return new self($fieldName, self::OPERATOR_EMPTY); } /** * Creates a condition for field non-emptiness. * * @param string $fieldName The field to check */ public static function isNotEmpty(string $fieldName): self { return new self($fieldName, self::OPERATOR_NOT_EMPTY); } /** * Retrieves field value from form data or participant data. */ private function getFieldValue(array $formData, int $participantIndex, BookingDtoInterface $bookingDto): mixed { // First check participant-specific form data if (isset($formData['participants'][$participantIndex][$this->fieldName])) { return $formData['participants'][$participantIndex][$this->fieldName]; } // Then check participant DTO data $participant = $bookingDto->getParticipant($participantIndex); if (null !== $participant && property_exists($participant, $this->fieldName)) { return $participant->{$this->fieldName}; } // Finally check booking-level form data return $formData[$this->fieldName] ?? null; } /** * Compares two values for equality, handling type coercion. */ private function compareEquals(mixed $fieldValue, mixed $expectedValue): bool { // Handle string/number coercion common in form data if (is_string($fieldValue) && is_numeric($expectedValue)) { return $fieldValue === (string) $expectedValue; } if (is_numeric($fieldValue) && is_string($expectedValue)) { return (string) $fieldValue === $expectedValue; } return $fieldValue === $expectedValue; } /** * Checks if field value is in array of expected values. */ private function compareIn(mixed $fieldValue, array $expectedValues): bool { foreach ($expectedValues as $expectedValue) { if ($this->compareEquals($fieldValue, $expectedValue)) { return true; } } return false; } /** * Checks if a value is considered empty. */ private function isEmpty(mixed $value): bool { return null === $value || '' === $value || (is_array($value) && empty($value)); } /** * Validates that the operator is supported. */ private function validateOperator(string $operator): void { $validOperators = [ self::OPERATOR_EQUALS, self::OPERATOR_NOT_EQUALS, self::OPERATOR_IN, self::OPERATOR_NOT_IN, self::OPERATOR_EMPTY, self::OPERATOR_NOT_EMPTY, ]; if (!in_array($operator, $validOperators, true)) { throw new \InvalidArgumentException(sprintf('Invalid operator "%s". Valid operators: %s', $operator, implode(', ', $validOperators))); } } /** * Validates expected value based on operator requirements. */ private function validateExpectedValue(string $operator, mixed $expectedValue): void { if (in_array($operator, [self::OPERATOR_IN, self::OPERATOR_NOT_IN], true) && !is_array($expectedValue)) { throw new \InvalidArgumentException(sprintf('Operator "%s" requires expectedValue to be an array', $operator)); } if (in_array($operator, [self::OPERATOR_EMPTY, self::OPERATOR_NOT_EMPTY], true) && null !== $expectedValue) { throw new \InvalidArgumentException(sprintf('Operator "%s" does not accept expectedValue parameter', $operator)); } } }