type === self::REMOVE; } /** * Check if this configuration has a regex pattern. */ public function hasRegexPattern(): bool { return $this->type === self::MASK_REGEX; } /** * Get the regex pattern from a regex mask configuration. * * @return string|null The regex pattern or null if not a regex mask */ public function getRegexPattern(): ?string { if ($this->type !== self::MASK_REGEX || $this->replacement === null) { return null; } $parts = explode('::', $this->replacement, 2); return $parts[0] ?? null; } /** * Get the replacement value. * * @return string|null The replacement value */ public function getReplacement(): ?string { if ($this->type === self::MASK_REGEX && $this->replacement !== null) { $parts = explode('::', $this->replacement, 2); return $parts[1] ?? Mask::MASK_MASKED; } return $this->replacement; } /** * Convert to array representation. * * @return (null|string)[] * * @psalm-return array{type: string, replacement: null|string} */ public function toArray(): array { return [ 'type' => $this->type, 'replacement' => $this->replacement, ]; } /** * Create from array representation. * * @param array $data * * @throws InvalidConfigurationException|InvalidRegexPatternException When data contains invalid values */ public static function fromArray(array $data): self { $type = $data['type'] ?? self::REPLACE; $replacement = $data['replacement'] ?? null; // Validate type $validTypes = [self::MASK_REGEX, self::REMOVE, self::REPLACE]; if (!in_array($type, $validTypes, true)) { $validList = implode(', ', $validTypes); throw InvalidConfigurationException::forParameter( 'type', $type, sprintf("Must be one of: %s", $validList) ); } // Validate replacement for REPLACE type - only when explicitly provided if ( $type === self::REPLACE && array_key_exists('replacement', $data) && ($replacement === null || trim($replacement) === '') ) { throw InvalidConfigurationException::forParameter( 'replacement', null, 'Cannot be null or empty for REPLACE type' ); } return new self($type, $replacement); } /** * Validate if a regex pattern is syntactically correct. * * @param string $pattern The regex pattern to validate * @return bool True if valid, false otherwise */ private static function isValidRegexPattern(string $pattern): bool { // Suppress warnings for invalid patterns $previousErrorReporting = error_reporting(E_ERROR); try { // Test the pattern by attempting to use it /** @psalm-suppress ArgumentTypeCoercion - Pattern validated by caller */ $result = @preg_match($pattern, ''); // Check if preg_match succeeded (returns 0 or 1) or failed (returns false) $isValid = $result !== false; // Additional check for PREG errors if ($isValid && preg_last_error() !== PREG_NO_ERROR) { $isValid = false; } // Additional validation for effectively empty patterns // Check for patterns that are effectively empty (like '//' or '/\s*/') // Extract the pattern content between delimiters if ($isValid && preg_match('/^(.)(.*?)\1[gimuxXs]*$/', $pattern, $matches)) { $patternContent = $matches[2]; // Reject patterns that are empty or only whitespace-based if ($patternContent === '' || trim($patternContent) === '' || $patternContent === '\s*') { $isValid = false; } } return $isValid; } finally { // Restore previous error reporting level error_reporting($previousErrorReporting); } } }