filter_var

PHP標準の入力検証・サニタイズ関数

概要

filter_varは、PHP標準で提供される強力な入力検証・サニタイズ関数です。様々な組み込みフィルタを使用して、ユーザー入力の検証(validation)やサニタイズ(sanitization)を簡単かつ安全に実行できます。SQLインジェクションやXSS攻撃などのセキュリティ脆弱性を防ぐための重要な機能を提供します。

基本的な使い方

基本構文

filter_var(mixed $value, int $filter = FILTER_DEFAULT, array|int $options = 0): mixed

パラメータ

  • $value: フィルタリングする値(スカラー値は内部的に文字列に変換される)
  • $filter: 適用するフィルタ(FILTER_VALIDATE_、FILTER_SANITIZE_、またはFILTER_CALLBACK)
  • $options: オプションの連想配列またはフィルタフラグのビットマスク

返り値

  • 成功時:フィルタリングされたデータ
  • 失敗時:false(FILTER_NULL_ON_FAILUREフラグ使用時はnull

検証フィルタ(FILTER_VALIDATE_*)

検証フィルタは、データが特定の条件を満たしているかチェックします。

主な検証フィルタ

// メールアドレスの検証
$email = "[email protected]";
if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
    echo "有効なメールアドレスです";
} else {
    echo "無効なメールアドレスです";
}

// 整数の検証(範囲指定付き)
$int = 100;
$options = [
    "options" => [
        "min_range" => 1,
        "max_range" => 200
    ]
];
if (filter_var($int, FILTER_VALIDATE_INT, $options) !== false) {
    echo "有効な整数です";
}

// URLの検証
$url = "https://example.com";
if (filter_var($url, FILTER_VALIDATE_URL)) {
    echo "有効なURLです";
}

// IPアドレスの検証
$ip = "192.168.1.1";
if (filter_var($ip, FILTER_VALIDATE_IP)) {
    echo "有効なIPアドレスです";
}

// 浮動小数点数の検証
$float = "1.234";
if (filter_var($float, FILTER_VALIDATE_FLOAT) !== false) {
    echo "有効な浮動小数点数です";
}

// ブール値の検証
$bool = "true";
if (filter_var($bool, FILTER_VALIDATE_BOOLEAN) !== false) {
    echo "有効なブール値です";
}

// 正規表現による検証
$pattern = "/^[a-zA-Z0-9]+$/";
$options = ["options" => ["regexp" => $pattern]];
if (filter_var("abc123", FILTER_VALIDATE_REGEXP, $options)) {
    echo "パターンに一致します";
}

検証フィルタのフラグ

// IPv4のみを許可
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
    echo "有効なIPv4アドレスです";
}

// プライベートIPを除外
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE)) {
    echo "パブリックIPアドレスです";
}

// URLのパスを必須にする
if (filter_var($url, FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED)) {
    echo "パスを含むURLです";
}

// URLのクエリ文字列を必須にする
if (filter_var($url, FILTER_VALIDATE_URL, FILTER_FLAG_QUERY_REQUIRED)) {
    echo "クエリ文字列を含むURLです";
}

サニタイズフィルタ(FILTER_SANITIZE_*)

サニタイズフィルタは、データから不要な文字を除去して安全に使用できる形式に変換します。

主なサニタイズフィルタ

// メールアドレスのサニタイズ
$email = "user@exa mple.com";
$sanitized_email = filter_var($email, FILTER_SANITIZE_EMAIL);
echo $sanitized_email; // "[email protected]"

// URLのサニタイズ
$url = "https://example.com/<script>";
$sanitized_url = filter_var($url, FILTER_SANITIZE_URL);
echo $sanitized_url; // "https://example.com/script"

// 整数のサニタイズ
$number = "100abc";
$sanitized_int = filter_var($number, FILTER_SANITIZE_NUMBER_INT);
echo $sanitized_int; // "100"

// 浮動小数点数のサニタイズ
$float = "1.5abc";
$sanitized_float = filter_var($float, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);
echo $sanitized_float; // "1.5"

// 特殊文字のエスケープ
$string = "<script>alert('XSS')</script>";
$sanitized_string = filter_var($string, FILTER_SANITIZE_SPECIAL_CHARS);
echo $sanitized_string; // "&lt;script&gt;alert(&#39;XSS&#39;)&lt;/script&gt;"

// HTMLタグの完全な除去
$html = "<p>Hello <b>World</b></p>";
$sanitized_html = filter_var($html, FILTER_SANITIZE_FULL_SPECIAL_CHARS);
echo $sanitized_html; // "&lt;p&gt;Hello &lt;b&gt;World&lt;/b&gt;&lt;/p&gt;"

サニタイズフィルタのフラグ

// 複数のフラグを組み合わせる
$string = "Hello\x00World\x1F";
$sanitized = filter_var(
    $string, 
    FILTER_SANITIZE_SPECIAL_CHARS, 
    FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH
);

// 小数点と千位区切り文字を許可
$number = "1,234.56";
$sanitized = filter_var(
    $number,
    FILTER_SANITIZE_NUMBER_FLOAT,
    FILTER_FLAG_ALLOW_FRACTION | FILTER_FLAG_ALLOW_THOUSAND
);
echo $sanitized; // "1,234.56"

// 科学的記法を許可
$scientific = "1.23e10";
$sanitized = filter_var(
    $scientific,
    FILTER_SANITIZE_NUMBER_FLOAT,
    FILTER_FLAG_ALLOW_SCIENTIFIC
);
echo $sanitized; // "1.23e10"

カスタムフィルタ(FILTER_CALLBACK)

独自の検証・サニタイズロジックを実装できます。

// カスタム検証関数
function validatePassword($password) {
    // パスワードの長さチェック
    if (strlen($password) < 8) {
        return false;
    }
    
    // 大文字、小文字、数字を含むかチェック
    if (!preg_match('/[A-Z]/', $password) ||
        !preg_match('/[a-z]/', $password) ||
        !preg_match('/[0-9]/', $password)) {
        return false;
    }
    
    return $password;
}

$password = "MyPass123";
$validated = filter_var($password, FILTER_CALLBACK, [
    'options' => 'validatePassword'
]);

if ($validated !== false) {
    echo "パスワードは有効です";
} else {
    echo "パスワードは無効です";
}

// カスタムサニタイズ関数
function sanitizePhoneNumber($phone) {
    // 数字以外を除去
    $phone = preg_replace('/[^0-9]/', '', $phone);
    
    // 日本の電話番号形式にフォーマット
    if (strlen($phone) === 11 && substr($phone, 0, 1) === '0') {
        return substr($phone, 0, 3) . '-' . 
               substr($phone, 3, 4) . '-' . 
               substr($phone, 7, 4);
    }
    
    return false;
}

$phone = "090-1234-5678";
$sanitized = filter_var($phone, FILTER_CALLBACK, [
    'options' => 'sanitizePhoneNumber'
]);
echo $sanitized; // "090-1234-5678"

実践的な使用例

フォーム入力の包括的な検証

class FormValidator {
    private $errors = [];
    
    public function validateRegistrationForm($data) {
        // メールアドレスの検証
        $email = filter_var($data['email'] ?? '', FILTER_SANITIZE_EMAIL);
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            $this->errors['email'] = '有効なメールアドレスを入力してください';
        }
        
        // 年齢の検証
        $age = filter_var($data['age'] ?? '', FILTER_SANITIZE_NUMBER_INT);
        $ageOptions = [
            'options' => [
                'min_range' => 18,
                'max_range' => 120
            ]
        ];
        if (filter_var($age, FILTER_VALIDATE_INT, $ageOptions) === false) {
            $this->errors['age'] = '年齢は18歳以上120歳以下で入力してください';
        }
        
        // URLの検証(オプション)
        if (!empty($data['website'])) {
            $website = filter_var($data['website'], FILTER_SANITIZE_URL);
            if (!filter_var($website, FILTER_VALIDATE_URL)) {
                $this->errors['website'] = '有効なURLを入力してください';
            }
        }
        
        // 名前のサニタイズ
        $name = filter_var($data['name'] ?? '', FILTER_SANITIZE_SPECIAL_CHARS);
        if (empty($name)) {
            $this->errors['name'] = '名前を入力してください';
        }
        
        return empty($this->errors);
    }
    
    public function getErrors() {
        return $this->errors;
    }
}

// 使用例
$validator = new FormValidator();
$formData = [
    'email' => '[email protected]',
    'age' => '25',
    'website' => 'https://example.com',
    'name' => 'John <script>alert("XSS")</script> Doe'
];

if ($validator->validateRegistrationForm($formData)) {
    echo "フォームデータは有効です";
} else {
    print_r($validator->getErrors());
}

XSSとSQLインジェクション対策

class SecurityFilter {
    /**
     * XSS対策:HTMLコンテンツの安全な出力
     */
    public static function outputHtml($content) {
        // HTMLエンティティに変換
        return filter_var($content, FILTER_SANITIZE_SPECIAL_CHARS);
    }
    
    /**
     * SQLインジェクション対策:整数値の検証
     */
    public static function validateId($id) {
        return filter_var($id, FILTER_VALIDATE_INT, [
            'options' => ['min_range' => 1]
        ]);
    }
    
    /**
     * ファイル名の安全なサニタイズ
     */
    public static function sanitizeFilename($filename) {
        // ファイル名に使用できない文字を除去
        $filename = preg_replace('/[^a-zA-Z0-9._-]/', '', $filename);
        
        // パストラバーサル攻撃を防ぐ
        $filename = str_replace(['..', '/'], '', $filename);
        
        return $filename;
    }
    
    /**
     * JSONデータの検証
     */
    public static function validateJson($json) {
        $data = json_decode($json, true);
        
        if (json_last_error() !== JSON_ERROR_NONE) {
            return false;
        }
        
        // 必要に応じて各フィールドを検証
        if (isset($data['email'])) {
            $data['email'] = filter_var($data['email'], FILTER_VALIDATE_EMAIL);
            if ($data['email'] === false) {
                return false;
            }
        }
        
        return $data;
    }
}

// 使用例
$userInput = "<script>alert('XSS')</script>";
echo SecurityFilter::outputHtml($userInput);

$id = $_GET['id'] ?? '';
if ($validId = SecurityFilter::validateId($id)) {
    // 安全にデータベースクエリで使用
    $query = "SELECT * FROM users WHERE id = " . $validId;
}

バッチ処理での一括検証

/**
 * 複数の値を一括で検証・サニタイズ
 */
function filterMultiple($data, $rules) {
    $result = [];
    $errors = [];
    
    foreach ($rules as $field => $rule) {
        $value = $data[$field] ?? null;
        
        // サニタイズ
        if (isset($rule['sanitize'])) {
            $value = filter_var($value, $rule['sanitize']);
        }
        
        // 検証
        if (isset($rule['validate'])) {
            $options = $rule['options'] ?? null;
            $filtered = filter_var($value, $rule['validate'], $options);
            
            if ($filtered === false) {
                $errors[$field] = $rule['error'] ?? "{$field}が無効です";
            } else {
                $result[$field] = $filtered;
            }
        } else {
            $result[$field] = $value;
        }
    }
    
    return [
        'data' => $result,
        'errors' => $errors,
        'valid' => empty($errors)
    ];
}

// 使用例
$userInput = [
    'email' => '  [email protected]  ',
    'age' => '25abc',
    'website' => 'https://example.com/<script>',
    'price' => '1,234.56'
];

$rules = [
    'email' => [
        'sanitize' => FILTER_SANITIZE_EMAIL,
        'validate' => FILTER_VALIDATE_EMAIL,
        'error' => '有効なメールアドレスを入力してください'
    ],
    'age' => [
        'sanitize' => FILTER_SANITIZE_NUMBER_INT,
        'validate' => FILTER_VALIDATE_INT,
        'options' => ['options' => ['min_range' => 0, 'max_range' => 150]],
        'error' => '年齢は0〜150の範囲で入力してください'
    ],
    'website' => [
        'sanitize' => FILTER_SANITIZE_URL,
        'validate' => FILTER_VALIDATE_URL,
        'error' => '有効なURLを入力してください'
    ],
    'price' => [
        'sanitize' => FILTER_SANITIZE_NUMBER_FLOAT,
        'validate' => FILTER_VALIDATE_FLOAT,
        'options' => FILTER_FLAG_ALLOW_THOUSAND,
        'error' => '有効な価格を入力してください'
    ]
];

$result = filterMultiple($userInput, $rules);

if ($result['valid']) {
    echo "すべてのデータが有効です\n";
    print_r($result['data']);
} else {
    echo "エラーがあります\n";
    print_r($result['errors']);
}

セキュリティに関する注意事項

XSS対策の注意点

// URLの検証だけでは不十分
$url = "javascript:alert('XSS')";
if (filter_var($url, FILTER_VALIDATE_URL)) {
    // この条件は通過してしまう!
}

// スキームも確認する必要がある
function validateSafeUrl($url) {
    // まずURLとして有効か確認
    if (!filter_var($url, FILTER_VALIDATE_URL)) {
        return false;
    }
    
    // 安全なスキームのみ許可
    $allowedSchemes = ['http', 'https'];
    $scheme = parse_url($url, PHP_URL_SCHEME);
    
    return in_array($scheme, $allowedSchemes);
}

検証とサニタイズの違い

// サニタイズ:修正を試みる
$input = "123abc";
$sanitized = filter_var($input, FILTER_SANITIZE_NUMBER_INT);
echo $sanitized; // "123" - 使える形に修正

// 検証:条件を満たすか確認
$validated = filter_var($input, FILTER_VALIDATE_INT);
var_dump($validated); // false - 無効として拒否

// 推奨パターン:サニタイズ後に検証
function processInput($input) {
    // 1. まずサニタイズ
    $sanitized = filter_var($input, FILTER_SANITIZE_NUMBER_INT);
    
    // 2. その後で検証
    $validated = filter_var($sanitized, FILTER_VALIDATE_INT);
    
    if ($validated === false) {
        throw new InvalidArgumentException("無効な入力です");
    }
    
    return $validated;
}

PHP 8.x での変更点

// FILTER_SANITIZE_STRINGは非推奨(PHP 8.1以降)
// 代わりにhtmlspecialchars()やFILTER_SANITIZE_SPECIAL_CHARSを使用

// 非推奨
// $string = filter_var($input, FILTER_SANITIZE_STRING);

// 推奨
$string = htmlspecialchars($input, ENT_QUOTES, 'UTF-8');
// または
$string = filter_var($input, FILTER_SANITIZE_SPECIAL_CHARS);

ベストプラクティス

  1. 常にサーバーサイドで検証する

    • クライアントサイドの検証は補助的なものと考える
    • filter_varはサーバーサイドでの検証に最適
  2. 適切なフィルタを選択する

    • 検証が必要な場合:FILTER_VALIDATE_*
    • 修正が必要な場合:FILTER_SANITIZE_*
  3. コンテキストに応じた処理

    • HTML出力:FILTER_SANITIZE_SPECIAL_CHARS
    • SQLクエリ:FILTER_VALIDATE_INTやプレースホルダー
    • ファイル名:カスタムサニタイズ
  4. エラーハンドリングを忘れない

    • falseとnullの違いを理解する
    • 適切なエラーメッセージを提供
  5. 定期的なセキュリティアップデート

    • PHPのバージョンを最新に保つ
    • セキュリティ関連の変更を確認

filter_varは、PHPアプリケーションのセキュリティを高める重要な機能です。適切に使用することで、多くの一般的なセキュリティ脆弱性を防ぐことができます。