1. php
  2. /security
  3. /input-validation

PHP Input Validation

Introduction to Input Validation

Input validation is the cornerstone of web application security and data integrity. It's the process of ensuring that user-provided data meets your application's requirements before processing or storing it. Proper input validation protects against numerous security vulnerabilities and prevents data corruption that could compromise your application's functionality.

Input validation is often confused with input sanitization, but they serve different purposes. Validation determines whether input meets specific criteria (rejecting invalid data), while sanitization modifies input to make it safe (cleaning problematic data). Both are essential components of a comprehensive security strategy.

Why Input Validation is Critical

Security Protection: Unvalidated input is the root cause of many security vulnerabilities including SQL injection, XSS attacks, command injection, and path traversal attacks. Attackers frequently exploit applications that blindly trust user input.

Data Integrity: Validation ensures that your database contains meaningful, correctly formatted data. This prevents application errors, improves user experience, and maintains data quality for business operations.

Business Logic Enforcement: Validation enforces business rules at the application level. For example, ensuring that email addresses are unique, passwords meet complexity requirements, or that numeric values fall within acceptable ranges.

Error Prevention: Early validation prevents errors that would otherwise occur deeper in your application stack, making debugging easier and improving application reliability.

Compliance Requirements: Many regulatory frameworks (GDPR, HIPAA, PCI DSS) require proper input validation as part of data protection measures.

Common Input Validation Vulnerabilities

Trusting Client-Side Validation: JavaScript validation can be bypassed by attackers. Server-side validation is mandatory for security.

Insufficient Validation: Checking only for presence but not format, length, or content validity leaves applications vulnerable.

Validation Bypass: Inconsistent validation across different input methods (forms, APIs, file uploads) creates security gaps.

Type Confusion: Not validating data types can lead to unexpected behavior and security issues.

Encoding Issues: Failing to handle different character encodings can lead to validation bypass and injection attacks.

Input Validation Principles

Whitelist over Blacklist: Define what is allowed rather than what is forbidden. Blacklists are difficult to maintain and often incomplete.

Fail Securely: When validation fails, reject the input and provide appropriate error messages without revealing system details.

Server-Side Validation: Always validate on the server side, regardless of client-side validation.

Early Validation: Validate input as early as possible in the request processing pipeline.

Contextual Validation: Apply different validation rules based on how and where data will be used.

Types of Input Validation

Format Validation

Format validation ensures data matches expected patterns, structures, or formats. This includes checking email addresses, phone numbers, dates, URLs, and other structured data.

Email Validation: Ensuring email addresses follow RFC standards and are syntactically correct.

Date Validation: Verifying dates are valid, in correct format, and within acceptable ranges.

URL Validation: Checking that URLs are properly formatted and use allowed schemes.

Regular Expression Validation: Using patterns to validate complex formats like social security numbers, postal codes, or custom identifiers.

Length Validation

Length validation prevents buffer overflow attacks, database errors, and ensures data fits within expected constraints.

Minimum Length: Ensuring passwords, usernames, or other fields meet minimum requirements.

Maximum Length: Preventing excessively long input that could cause performance issues or storage problems.

Exact Length: Validating fixed-length fields like credit card numbers or product codes.

Range Validation

Range validation ensures numeric values, dates, or other comparable data fall within acceptable bounds.

Numeric Ranges: Validating that quantities, prices, or ages are within realistic limits.

Date Ranges: Ensuring dates fall within valid periods (birth dates in the past, appointment dates in the future).

File Size Ranges: Limiting upload file sizes to prevent resource exhaustion.

Type Validation

Type validation ensures data is of the expected type and can be safely processed by your application.

Data Type Checking: Verifying that numeric fields contain numbers, boolean fields contain valid boolean values.

File Type Validation: Ensuring uploaded files are of allowed types based on content, not just extension.

Array Structure Validation: Validating that arrays contain expected keys and value types.

Content Validation

Content validation examines the actual content of input for malicious patterns, inappropriate content, or business rule violations.

Malicious Pattern Detection: Identifying potential SQL injection, XSS, or other attack patterns.

Profanity Filtering: Detecting and handling inappropriate language in user-generated content.

Business Rule Validation: Ensuring data meets complex business requirements.

PHP Validation Functions and Filters

Built-in Filter Functions

PHP provides a comprehensive filter extension for input validation and sanitization:

<?php
/**
 * Comprehensive example of PHP's built-in filter functions
 * 
 * PHP's filter extension provides robust validation and sanitization
 * capabilities. These functions are optimized, well-tested, and handle
 * edge cases that custom validation might miss.
 */
class PHPFilterValidation
{
    /**
     * Validate email addresses using PHP's filter system
     * 
     * PHP's FILTER_VALIDATE_EMAIL follows RFC 5322 standards
     * and handles complex email formats correctly.
     */
    public static function validateEmail(string $email): bool
    {
        // Basic email validation
        $isValid = filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
        
        if (!$isValid) {
            return false;
        }
        
        // Additional checks for business requirements
        $domain = substr(strrchr($email, "@"), 1);
        
        // Check domain length (RFC compliance)
        if (strlen($domain) > 253) {
            return false;
        }
        
        // Optional: Check if domain exists (requires network call)
        // if (!checkdnsrr($domain, 'MX')) {
        //     return false;
        // }
        
        return true;
    }
    
    /**
     * Validate URLs with various options
     * 
     * URL validation can include protocol checking,
     * path validation, and domain restrictions.
     */
    public static function validateURL(string $url, array $options = []): bool
    {
        $flags = FILTER_FLAG_SCHEME_REQUIRED | FILTER_FLAG_HOST_REQUIRED;
        
        // Add path requirement if specified
        if ($options['require_path'] ?? false) {
            $flags |= FILTER_FLAG_PATH_REQUIRED;
        }
        
        $isValid = filter_var($url, FILTER_VALIDATE_URL, $flags) !== false;
        
        if (!$isValid) {
            return false;
        }
        
        // Additional security checks
        $parsedUrl = parse_url($url);
        
        // Check allowed protocols
        $allowedSchemes = $options['allowed_schemes'] ?? ['http', 'https'];
        if (!in_array($parsedUrl['scheme'], $allowedSchemes)) {
            return false;
        }
        
        // Prevent localhost/private IP access if specified
        if ($options['block_private'] ?? true) {
            $ip = gethostbyname($parsedUrl['host']);
            if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false) {
                return false;
            }
        }
        
        return true;
    }
    
    /**
     * Validate numeric values with range checking
     * 
     * Numeric validation should consider type, range,
     * and precision requirements.
     */
    public static function validateNumeric($value, array $options = []): bool
    {
        // Check if value is numeric
        if (!is_numeric($value)) {
            return false;
        }
        
        $numValue = floatval($value);
        
        // Range validation
        if (isset($options['min']) && $numValue < $options['min']) {
            return false;
        }
        
        if (isset($options['max']) && $numValue > $options['max']) {
            return false;
        }
        
        // Integer validation if required
        if (($options['integer'] ?? false) && !is_int($value + 0)) {
            return false;
        }
        
        // Positive number validation
        if (($options['positive'] ?? false) && $numValue <= 0) {
            return false;
        }
        
        return true;
    }
    
    /**
     * Validate dates with format and range checking
     * 
     * Date validation should handle various formats
     * and ensure logical date ranges.
     */
    public static function validateDate(string $date, string $format = 'Y-m-d'): bool
    {
        $dateTime = DateTime::createFromFormat($format, $date);
        
        // Check if date was parsed correctly
        if (!$dateTime || $dateTime->format($format) !== $date) {
            return false;
        }
        
        return true;
    }
    
    /**
     * Validate date ranges for business logic
     * 
     * Many applications need to ensure dates fall within
     * logical ranges based on business requirements.
     */
    public static function validateDateRange(string $date, array $options = []): bool
    {
        if (!self::validateDate($date, $options['format'] ?? 'Y-m-d')) {
            return false;
        }
        
        $dateTime = new DateTime($date);
        $now = new DateTime();
        
        // Past date validation (for birth dates, historical events)
        if (($options['must_be_past'] ?? false) && $dateTime > $now) {
            return false;
        }
        
        // Future date validation (for appointments, deadlines)
        if (($options['must_be_future'] ?? false) && $dateTime < $now) {
            return false;
        }
        
        // Minimum age validation
        if (isset($options['min_age_years'])) {
            $minDate = clone $now;
            $minDate->sub(new DateInterval('P' . $options['min_age_years'] . 'Y'));
            
            if ($dateTime > $minDate) {
                return false;
            }
        }
        
        // Maximum age validation
        if (isset($options['max_age_years'])) {
            $maxDate = clone $now;
            $maxDate->sub(new DateInterval('P' . $options['max_age_years'] . 'Y'));
            
            if ($dateTime < $maxDate) {
                return false;
            }
        }
        
        return true;
    }
}

// Example usage demonstrating various validation scenarios
$testData = [
    'emails' => [
        '[email protected]',
        'invalid.email',
        '[email protected]'
    ],
    'urls' => [
        'https://example.com',
        'http://localhost',
        'ftp://files.example.com'
    ],
    'numbers' => [
        '42',
        '-10',
        '3.14159',
        'not-a-number'
    ],
    'dates' => [
        '2024-01-15',
        '2024-13-01', // Invalid month
        '2024-02-30'  // Invalid day
    ]
];

// Validate emails
foreach ($testData['emails'] as $email) {
    $isValid = PHPFilterValidation::validateEmail($email);
    echo "Email '$email': " . ($isValid ? 'Valid' : 'Invalid') . "\n";
}

// Validate URLs with options
foreach ($testData['urls'] as $url) {
    $isValid = PHPFilterValidation::validateURL($url, [
        'allowed_schemes' => ['https'],
        'block_private' => true
    ]);
    echo "URL '$url': " . ($isValid ? 'Valid' : 'Invalid') . "\n";
}
?>

Custom Validation Classes

<?php
/**
 * Advanced validation system with custom rules and error handling
 * 
 * This class provides a flexible validation framework that can
 * handle complex validation scenarios while providing detailed
 * error reporting for user feedback.
 */
class AdvancedValidator
{
    private array $errors = [];
    private array $rules = [];
    private array $customMessages = [];
    
    /**
     * Add validation rule for a field
     * 
     * Rules can be simple (required) or complex with parameters.
     * This method supports chaining for multiple rules per field.
     */
    public function addRule(string $field, string $rule, $parameters = null, string $message = null): self
    {
        if (!isset($this->rules[$field])) {
            $this->rules[$field] = [];
        }
        
        $this->rules[$field][] = [
            'rule' => $rule,
            'parameters' => $parameters,
            'message' => $message
        ];
        
        return $this;
    }
    
    /**
     * Set custom error messages for fields
     * 
     * Custom messages provide better user experience by
     * giving context-specific validation feedback.
     */
    public function setCustomMessage(string $field, string $rule, string $message): self
    {
        if (!isset($this->customMessages[$field])) {
            $this->customMessages[$field] = [];
        }
        
        $this->customMessages[$field][$rule] = $message;
        
        return $this;
    }
    
    /**
     * Validate data against defined rules
     * 
     * This method processes all validation rules and collects
     * errors for detailed feedback to users.
     */
    public function validate(array $data): bool
    {
        $this->errors = [];
        
        foreach ($this->rules as $field => $fieldRules) {
            $value = $data[$field] ?? null;
            
            foreach ($fieldRules as $ruleConfig) {
                $rule = $ruleConfig['rule'];
                $parameters = $ruleConfig['parameters'];
                $customMessage = $ruleConfig['message'];
                
                if (!$this->executeRule($field, $value, $rule, $parameters)) {
                    $message = $customMessage 
                        ?? $this->customMessages[$field][$rule] ?? null 
                        ?? $this->getDefaultMessage($field, $rule, $parameters);
                    
                    $this->errors[$field][] = $message;
                    
                    // Stop validating this field if required rule fails
                    if ($rule === 'required') {
                        break;
                    }
                }
            }
        }
        
        return empty($this->errors);
    }
    
    /**
     * Execute individual validation rule
     * 
     * This method handles the actual validation logic for each rule type.
     * It's designed to be extensible for adding new validation rules.
     */
    private function executeRule(string $field, $value, string $rule, $parameters): bool
    {
        switch ($rule) {
            case 'required':
                return !empty($value) || $value === '0' || $value === 0;
                
            case 'email':
                return empty($value) || PHPFilterValidation::validateEmail($value);
                
            case 'url':
                return empty($value) || PHPFilterValidation::validateURL($value, $parameters ?? []);
                
            case 'numeric':
                return empty($value) || PHPFilterValidation::validateNumeric($value, $parameters ?? []);
                
            case 'min_length':
                return empty($value) || strlen($value) >= $parameters;
                
            case 'max_length':
                return empty($value) || strlen($value) <= $parameters;
                
            case 'regex':
                return empty($value) || preg_match($parameters, $value);
                
            case 'in':
                return empty($value) || in_array($value, $parameters);
                
            case 'unique':
                return $this->validateUnique($field, $value, $parameters);
                
            case 'confirmed':
                return isset($parameters[$field . '_confirmation']) && 
                       $value === $parameters[$field . '_confirmation'];
                
            case 'date':
                return empty($value) || PHPFilterValidation::validateDate($value, $parameters ?? 'Y-m-d');
                
            case 'date_range':
                return empty($value) || PHPFilterValidation::validateDateRange($value, $parameters ?? []);
                
            case 'file_type':
                return $this->validateFileType($value, $parameters);
                
            case 'custom':
                return $this->executeCustomRule($field, $value, $parameters);
                
            default:
                throw new InvalidArgumentException("Unknown validation rule: $rule");
        }
    }
    
    /**
     * Validate file uploads
     * 
     * File validation requires checking MIME types, file sizes,
     * and potential security threats.
     */
    private function validateFileType($file, array $allowedTypes): bool
    {
        if (empty($file) || !is_array($file)) {
            return true; // Empty files pass (use 'required' rule to enforce)
        }
        
        // Check upload errors
        if ($file['error'] !== UPLOAD_ERR_OK) {
            return false;
        }
        
        // Get file MIME type
        $finfo = finfo_open(FILEINFO_MIME_TYPE);
        $mimeType = finfo_file($finfo, $file['tmp_name']);
        finfo_close($finfo);
        
        // Check against allowed types
        return in_array($mimeType, $allowedTypes);
    }
    
    /**
     * Validate uniqueness (requires database connection)
     * 
     * Uniqueness validation typically requires checking against
     * existing database records.
     */
    private function validateUnique(string $field, $value, array $config): bool
    {
        if (empty($value)) {
            return true;
        }
        
        // This would typically involve a database query
        // $pdo = $config['pdo'];
        // $table = $config['table'];
        // $column = $config['column'] ?? $field;
        // $excludeId = $config['exclude_id'] ?? null;
        
        // $sql = "SELECT COUNT(*) FROM $table WHERE $column = ?";
        // $params = [$value];
        
        // if ($excludeId) {
        //     $sql .= " AND id != ?";
        //     $params[] = $excludeId;
        // }
        
        // $stmt = $pdo->prepare($sql);
        // $stmt->execute($params);
        
        // return $stmt->fetchColumn() == 0;
        
        // For demonstration, return true
        return true;
    }
    
    /**
     * Execute custom validation functions
     * 
     * Custom rules allow for complex business logic validation
     * that doesn't fit standard patterns.
     */
    private function executeCustomRule(string $field, $value, callable $callback): bool
    {
        return call_user_func($callback, $field, $value);
    }
    
    /**
     * Generate default error messages
     * 
     * Provides fallback messages when custom messages aren't defined.
     */
    private function getDefaultMessage(string $field, string $rule, $parameters): string
    {
        $fieldName = ucfirst(str_replace('_', ' ', $field));
        
        switch ($rule) {
            case 'required':
                return "$fieldName is required.";
            case 'email':
                return "$fieldName must be a valid email address.";
            case 'url':
                return "$fieldName must be a valid URL.";
            case 'numeric':
                return "$fieldName must be a number.";
            case 'min_length':
                return "$fieldName must be at least $parameters characters.";
            case 'max_length':
                return "$fieldName must not exceed $parameters characters.";
            case 'regex':
                return "$fieldName format is invalid.";
            case 'in':
                return "$fieldName must be one of: " . implode(', ', $parameters);
            case 'unique':
                return "$fieldName already exists.";
            case 'confirmed':
                return "$fieldName confirmation does not match.";
            case 'date':
                return "$fieldName must be a valid date.";
            case 'file_type':
                return "$fieldName must be a valid file type.";
            default:
                return "$fieldName is invalid.";
        }
    }
    
    /**
     * Get all validation errors
     * 
     * Returns errors in a structured format for easy display
     * in user interfaces.
     */
    public function getErrors(): array
    {
        return $this->errors;
    }
    
    /**
     * Get errors for a specific field
     * 
     * Useful for displaying field-specific error messages
     * in forms.
     */
    public function getFieldErrors(string $field): array
    {
        return $this->errors[$field] ?? [];
    }
    
    /**
     * Check if validation passed
     * 
     * Convenience method to check if there are any errors.
     */
    public function passes(): bool
    {
        return empty($this->errors);
    }
    
    /**
     * Check if validation failed
     * 
     * Convenience method to check if there are errors.
     */
    public function fails(): bool
    {
        return !empty($this->errors);
    }
}

// Example usage of the advanced validator
$validator = new AdvancedValidator();

// Define validation rules
$validator
    ->addRule('name', 'required')
    ->addRule('name', 'min_length', 2)
    ->addRule('name', 'max_length', 50)
    ->addRule('email', 'required')
    ->addRule('email', 'email')
    ->addRule('age', 'required')
    ->addRule('age', 'numeric', ['min' => 18, 'max' => 120, 'integer' => true])
    ->addRule('website', 'url', ['allowed_schemes' => ['https']])
    ->addRule('password', 'required')
    ->addRule('password', 'min_length', 8)
    ->addRule('password', 'regex', '/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/')
    ->addRule('password', 'confirmed')
    ->addRule('terms', 'required')
    ->addRule('birth_date', 'required')
    ->addRule('birth_date', 'date')
    ->addRule('birth_date', 'date_range', ['must_be_past' => true, 'min_age_years' => 13]);

// Set custom error messages
$validator
    ->setCustomMessage('password', 'regex', 'Password must contain at least one uppercase letter, one lowercase letter, and one number.')
    ->setCustomMessage('terms', 'required', 'You must accept the terms and conditions.');

// Test data
$formData = [
    'name' => 'John Doe',
    'email' => '[email protected]',
    'age' => '25',
    'website' => 'https://johndoe.com',
    'password' => 'SecurePass123',
    'password_confirmation' => 'SecurePass123',
    'terms' => '1',
    'birth_date' => '1998-01-15'
];

// Validate the data
if ($validator->validate($formData)) {
    echo "All validation passed!\n";
    // Process the form data
} else {
    echo "Validation failed:\n";
    foreach ($validator->getErrors() as $field => $errors) {
        echo "$field: " . implode(', ', $errors) . "\n";
    }
}
?>

Sanitization and Filtering

While validation determines if input is acceptable, sanitization modifies input to make it safe for processing:

<?php
/**
 * Comprehensive input sanitization system
 * 
 * Sanitization prepares input for safe processing by removing
 * or encoding potentially dangerous content while preserving
 * the intended data structure and meaning.
 */
class InputSanitizer
{
    /**
     * Sanitize string input for different contexts
     * 
     * Different contexts require different sanitization approaches.
     * HTML output, database storage, and file names all have
     * different security requirements.
     */
    public static function sanitizeString(string $input, string $context = 'general'): string
    {
        // Remove null bytes and control characters
        $sanitized = str_replace("\0", '', $input);
        $sanitized = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', '', $sanitized);
        
        switch ($context) {
            case 'html':
                // Escape HTML entities for safe display
                return htmlspecialchars($sanitized, ENT_QUOTES | ENT_HTML5, 'UTF-8');
                
            case 'attribute':
                // More aggressive escaping for HTML attributes
                return htmlspecialchars($sanitized, ENT_QUOTES | ENT_HTML5, 'UTF-8');
                
            case 'filename':
                // Remove characters not safe for filenames
                $sanitized = preg_replace('/[^a-zA-Z0-9._-]/', '', $sanitized);
                return trim($sanitized, '.');
                
            case 'alphanumeric':
                // Keep only letters and numbers
                return preg_replace('/[^a-zA-Z0-9]/', '', $sanitized);
                
            case 'slug':
                // Create URL-safe slugs
                $sanitized = strtolower($sanitized);
                $sanitized = preg_replace('/[^a-z0-9]+/', '-', $sanitized);
                return trim($sanitized, '-');
                
            case 'email':
                // Basic email sanitization
                return filter_var($sanitized, FILTER_SANITIZE_EMAIL);
                
            case 'url':
                // URL sanitization
                return filter_var($sanitized, FILTER_SANITIZE_URL);
                
            default:
                // General sanitization - remove dangerous characters
                return trim($sanitized);
        }
    }
    
    /**
     * Sanitize numeric input
     * 
     * Ensures numeric input is properly formatted and within
     * acceptable ranges for the application context.
     */
    public static function sanitizeNumeric($input, string $type = 'float'): ?float
    {
        if (!is_numeric($input)) {
            return null;
        }
        
        switch ($type) {
            case 'int':
                return (int) $input;
            case 'float':
                return (float) $input;
            case 'positive_int':
                $int = (int) $input;
                return $int > 0 ? $int : null;
            case 'positive_float':
                $float = (float) $input;
                return $float > 0 ? $float : null;
            default:
                return (float) $input;
        }
    }
    
    /**
     * Sanitize array input recursively
     * 
     * Arrays require recursive sanitization to ensure all
     * nested values are properly cleaned.
     */
    public static function sanitizeArray(array $input, string $context = 'general'): array
    {
        $sanitized = [];
        
        foreach ($input as $key => $value) {
            // Sanitize the key
            $cleanKey = self::sanitizeString((string) $key, 'alphanumeric');
            
            if (is_array($value)) {
                $sanitized[$cleanKey] = self::sanitizeArray($value, $context);
            } elseif (is_string($value)) {
                $sanitized[$cleanKey] = self::sanitizeString($value, $context);
            } elseif (is_numeric($value)) {
                $sanitized[$cleanKey] = self::sanitizeNumeric($value);
            } else {
                // For other types, convert to string and sanitize
                $sanitized[$cleanKey] = self::sanitizeString((string) $value, $context);
            }
        }
        
        return $sanitized;
    }
    
    /**
     * Sanitize rich text content
     * 
     * Rich text requires careful sanitization to preserve
     * formatting while removing dangerous content.
     */
    public static function sanitizeRichText(string $html): string
    {
        // Define allowed HTML tags and attributes
        $allowedTags = [
            'p', 'br', 'strong', 'em', 'u', 'ol', 'ul', 'li',
            'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote'
        ];
        
        $allowedAttributes = [
            'class' => ['highlight', 'emphasis'],
            'id' => []
        ];
        
        // Remove all tags except allowed ones
        $html = strip_tags($html, '<' . implode('><', $allowedTags) . '>');
        
        // Remove potentially dangerous attributes
        $html = preg_replace_callback(
            '/<([a-zA-Z0-9]+)([^>]*)>/',
            function ($matches) use ($allowedAttributes) {
                $tag = $matches[1];
                $attributes = $matches[2];
                
                // Parse attributes
                preg_match_all('/(\w+)=["\']([^"\']*)["\']/', $attributes, $attrMatches);
                
                $cleanAttributes = '';
                for ($i = 0; $i < count($attrMatches[0]); $i++) {
                    $attrName = $attrMatches[1][$i];
                    $attrValue = $attrMatches[2][$i];
                    
                    if (isset($allowedAttributes[$attrName])) {
                        $allowedValues = $allowedAttributes[$attrName];
                        if (empty($allowedValues) || in_array($attrValue, $allowedValues)) {
                            $cleanAttributes .= ' ' . $attrName . '="' . htmlspecialchars($attrValue) . '"';
                        }
                    }
                }
                
                return "<$tag$cleanAttributes>";
            },
            $html
        );
        
        return $html;
    }
}

// Example usage of sanitization
$rawInput = [
    'name' => '<script>alert("xss")</script>John Doe',
    'email' => '[email protected]',
    'age' => '25.5',
    'bio' => '<p>Hello <strong>world</strong>!</p><script>evil()</script>',
    'tags' => ['web<script>', 'php', 'security'],
    'filename' => '../../../etc/passwd'
];

echo "Original input:\n";
print_r($rawInput);

echo "\nSanitized for HTML display:\n";
$htmlSafe = InputSanitizer::sanitizeArray($rawInput, 'html');
print_r($htmlSafe);

echo "\nSpecific sanitizations:\n";
echo "Rich text bio: " . InputSanitizer::sanitizeRichText($rawInput['bio']) . "\n";
echo "Safe filename: " . InputSanitizer::sanitizeString($rawInput['filename'], 'filename') . "\n";
echo "Slug from name: " . InputSanitizer::sanitizeString($rawInput['name'], 'slug') . "\n";
?>

Security Best Practices

Defense in Depth

Input validation should be part of a comprehensive security strategy:

<?php
/**
 * Complete input processing pipeline with validation and sanitization
 * 
 * This class demonstrates a comprehensive approach to input processing
 * that combines validation, sanitization, and security logging.
 */
class SecureInputProcessor
{
    private AdvancedValidator $validator;
    private array $securityLog = [];
    
    public function __construct()
    {
        $this->validator = new AdvancedValidator();
    }
    
    /**
     * Process form input with comprehensive security measures
     * 
     * This method implements multiple layers of security including
     * rate limiting, validation, sanitization, and security logging.
     */
    public function processFormInput(array $rawInput, array $validationRules): array
    {
        // Step 1: Rate limiting check
        if (!$this->checkRateLimit()) {
            throw new SecurityException('Rate limit exceeded');
        }
        
        // Step 2: Initial security scan
        $this->scanForThreats($rawInput);
        
        // Step 3: Apply validation rules
        $this->applyValidationRules($validationRules);
        
        if (!$this->validator->validate($rawInput)) {
            throw new ValidationException('Input validation failed', $this->validator->getErrors());
        }
        
        // Step 4: Sanitize the validated input
        $sanitizedInput = $this->sanitizeInput($rawInput);
        
        // Step 5: Final security check
        $this->finalSecurityCheck($sanitizedInput);
        
        // Step 6: Log successful processing
        $this->logSecurityEvent('INPUT_PROCESSED', 'success');
        
        return $sanitizedInput;
    }
    
    /**
     * Check rate limits to prevent abuse
     * 
     * Rate limiting prevents automated attacks and abuse
     * by limiting the number of requests per time period.
     */
    private function checkRateLimit(): bool
    {
        $clientIP = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
        $cacheKey = "rate_limit:$clientIP";
        
        // This would typically use Redis or another cache system
        // For demonstration, we'll simulate the check
        $currentRequests = 0; // Get from cache
        $maxRequests = 100;   // Per hour
        
        if ($currentRequests >= $maxRequests) {
            $this->logSecurityEvent('RATE_LIMIT_EXCEEDED', 'blocked');
            return false;
        }
        
        // Increment counter (would be done in cache)
        return true;
    }
    
    /**
     * Scan input for common attack patterns
     * 
     * Early detection of attack patterns helps identify
     * malicious requests before they can cause damage.
     */
    private function scanForThreats(array $input): void
    {
        $threatPatterns = [
            'sql_injection' => '/(\bUNION\b|\bSELECT\b|\bINSERT\b|\bDELETE\b|\bDROP\b)/i',
            'xss' => '/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/i',
            'path_traversal' => '/\.\.[\/\\\\]/',
            'command_injection' => '/[;&|`$\(\)]/i'
        ];
        
        foreach ($input as $key => $value) {
            if (is_string($value)) {
                foreach ($threatPatterns as $threatType => $pattern) {
                    if (preg_match($pattern, $value)) {
                        $this->logSecurityEvent('THREAT_DETECTED', $threatType, [
                            'field' => $key,
                            'value' => substr($value, 0, 100),
                            'pattern' => $threatType
                        ]);
                        
                        // Optionally block the request immediately
                        throw new SecurityException("Potential $threatType detected");
                    }
                }
            }
        }
    }
    
    /**
     * Apply validation rules to the validator
     * 
     * This method sets up all validation rules based on
     * the configuration provided for each form.
     */
    private function applyValidationRules(array $rules): void
    {
        foreach ($rules as $field => $fieldRules) {
            foreach ($fieldRules as $rule) {
                if (is_string($rule)) {
                    $this->validator->addRule($field, $rule);
                } elseif (is_array($rule)) {
                    $this->validator->addRule(
                        $field,
                        $rule['rule'],
                        $rule['parameters'] ?? null,
                        $rule['message'] ?? null
                    );
                }
            }
        }
    }
    
    /**
     * Sanitize input based on field types
     * 
     * Context-aware sanitization ensures data is cleaned
     * appropriately for its intended use.
     */
    private function sanitizeInput(array $input): array
    {
        $sanitized = [];
        
        // Field-specific sanitization rules
        $sanitizationRules = [
            'email' => 'email',
            'url' => 'url',
            'name' => 'general',
            'description' => 'html',
            'slug' => 'slug',
            'filename' => 'filename'
        ];
        
        foreach ($input as $key => $value) {
            $context = $sanitizationRules[$key] ?? 'general';
            
            if (is_array($value)) {
                $sanitized[$key] = InputSanitizer::sanitizeArray($value, $context);
            } else {
                $sanitized[$key] = InputSanitizer::sanitizeString((string) $value, $context);
            }
        }
        
        return $sanitized;
    }
    
    /**
     * Final security check after sanitization
     * 
     * Additional validation after sanitization ensures
     * that the cleaning process hasn't broken business logic.
     */
    private function finalSecurityCheck(array $sanitizedInput): void
    {
        // Check for empty required fields after sanitization
        $requiredFields = ['name', 'email']; // Example
        
        foreach ($requiredFields as $field) {
            if (empty($sanitizedInput[$field])) {
                throw new ValidationException("Required field '$field' became empty after sanitization");
            }
        }
        
        // Check for maximum data size
        $serializedSize = strlen(serialize($sanitizedInput));
        if ($serializedSize > 1024 * 100) { // 100KB limit
            throw new SecurityException('Input data size exceeds limits');
        }
    }
    
    /**
     * Log security events for monitoring and analysis
     * 
     * Security logging helps detect attack patterns and
     * provides audit trails for compliance requirements.
     */
    private function logSecurityEvent(string $event, string $status, array $details = []): void
    {
        $logEntry = [
            'timestamp' => date('Y-m-d H:i:s'),
            'event' => $event,
            'status' => $status,
            'ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
            'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'unknown',
            'details' => $details
        ];
        
        $this->securityLog[] = $logEntry;
        
        // In production, write to security log file or SIEM system
        error_log('SECURITY: ' . json_encode($logEntry));
    }
    
    /**
     * Get security log for analysis
     * 
     * Security logs can be analyzed to identify attack patterns
     * and improve security measures.
     */
    public function getSecurityLog(): array
    {
        return $this->securityLog;
    }
}

// Custom exception classes for different types of failures
class ValidationException extends Exception
{
    private array $errors;
    
    public function __construct(string $message, array $errors = [])
    {
        parent::__construct($message);
        $this->errors = $errors;
    }
    
    public function getErrors(): array
    {
        return $this->errors;
    }
}

class SecurityException extends Exception {}

// Example usage of the secure input processor
try {
    $processor = new SecureInputProcessor();
    
    $formData = [
        'name' => 'John Doe',
        'email' => '[email protected]',
        'message' => '<p>Hello <strong>world</strong>!</p>'
    ];
    
    $validationRules = [
        'name' => ['required', ['rule' => 'max_length', 'parameters' => 50]],
        'email' => ['required', 'email'],
        'message' => [['rule' => 'max_length', 'parameters' => 1000]]
    ];
    
    $cleanData = $processor->processFormInput($formData, $validationRules);
    echo "Processing successful!\n";
    print_r($cleanData);
    
} catch (ValidationException $e) {
    echo "Validation failed: " . $e->getMessage() . "\n";
    print_r($e->getErrors());
} catch (SecurityException $e) {
    echo "Security error: " . $e->getMessage() . "\n";
}
?>

For comprehensive web application security:

Summary

Input validation is fundamental to web application security and data integrity. Key principles include:

Multi-Layer Defense: Combine validation, sanitization, rate limiting, and monitoring for comprehensive protection.

Context-Aware Processing: Apply different validation and sanitization rules based on how data will be used.

Server-Side Enforcement: Never rely solely on client-side validation for security.

Comprehensive Logging: Monitor validation failures and security events for threat detection.

User Experience: Provide clear, helpful error messages while maintaining security.

Proper input validation requires understanding both the technical implementation and the security implications. By implementing robust validation systems, you protect your application from attacks while ensuring data quality and user experience remain high. Remember that validation is an ongoing process that should evolve with your application's needs and emerging security threats.