[PHP]normalize関数・正規化処理完全ガイド – 文字列とデータの標準化をマスターしよう

PHP

はじめに

Webアプリケーション開発において、**データの正規化(normalize)**は品質と一貫性を保つ上で非常に重要な処理です。PHPでは様々な場面で正規化が必要になりますが、特に文字列処理、Unicode正規化、データベース設計などで頻繁に使用されます。

この記事では、PHPでの正規化処理について、実用的なコード例とともに詳しく解説していきます。

正規化(Normalize)とは?

正規化とは、データを一定の規則に従って標準的な形式に変換することです。PHPにおける主な正規化の種類:

  1. Unicode正規化 – 文字の表現形式を統一
  2. 文字列正規化 – 空白、大小文字、特殊文字の処理
  3. データ正規化 – 配列やオブジェクトの構造統一
  4. パス正規化 – ファイルパスの標準化

1. Unicode正規化 – Normalizer クラス

基本的な使い方

<?php
// Intl拡張が必要
if (!class_exists('Normalizer')) {
    die('Intl拡張が必要です');
}

// 合成文字と分解文字の例
$text1 = "é";  // 合成文字(U+00E9)
$text2 = "é";  // e + 結合文字(U+0065 + U+0301)

// NFC正規化(合成形式)
$nfc1 = Normalizer::normalize($text1, Normalizer::FORM_C);
$nfc2 = Normalizer::normalize($text2, Normalizer::FORM_C);

echo ($nfc1 === $nfc2) ? "同一" : "異なる"; // "同一"
?>

正規化形式の種類

<?php
$text = "カフェ";

// NFC - 正準合成形式
$nfc = Normalizer::normalize($text, Normalizer::FORM_C);

// NFD - 正準分解形式  
$nfd = Normalizer::normalize($text, Normalizer::FORM_D);

// NFKC - 互換合成形式
$nfkc = Normalizer::normalize($text, Normalizer::FORM_KC);

// NFKD - 互換分解形式
$nfkd = Normalizer::normalize($text, Normalizer::FORM_KD);

echo "NFC: " . bin2hex($nfc) . "\n";
echo "NFD: " . bin2hex($nfd) . "\n";
?>

2. 文字列正規化のカスタム関数

基本的な文字列正規化

<?php
function normalizeString($string, $options = []) {
    // デフォルトオプション
    $defaults = [
        'trim' => true,
        'lowercase' => false,
        'remove_extra_spaces' => true,
        'unicode_normalize' => true,
        'encoding' => 'UTF-8'
    ];
    
    $options = array_merge($defaults, $options);
    
    // 空白の正規化
    if ($options['trim']) {
        $string = trim($string);
    }
    
    // 余分な空白を除去
    if ($options['remove_extra_spaces']) {
        $string = preg_replace('/\s+/', ' ', $string);
    }
    
    // 小文字化
    if ($options['lowercase']) {
        $string = mb_strtolower($string, $options['encoding']);
    }
    
    // Unicode正規化
    if ($options['unicode_normalize'] && class_exists('Normalizer')) {
        $string = Normalizer::normalize($string, Normalizer::FORM_C);
    }
    
    return $string;
}

// 使用例
$text = "  こんにちは  世界  ";
echo normalizeString($text); // "こんにちは 世界"
?>

電話番号の正規化

<?php
function normalizePhoneNumber($phone) {
    // 数字以外を除去
    $phone = preg_replace('/[^0-9]/', '', $phone);
    
    // 日本の携帯電話番号の正規化例
    if (strlen($phone) === 11 && substr($phone, 0, 2) === '09') {
        return substr($phone, 0, 3) . '-' . substr($phone, 3, 4) . '-' . substr($phone, 7);
    }
    
    // 固定電話(東京03)の場合
    if (strlen($phone) === 10 && substr($phone, 0, 2) === '03') {
        return substr($phone, 0, 2) . '-' . substr($phone, 2, 4) . '-' . substr($phone, 6);
    }
    
    return $phone;
}

echo normalizePhoneNumber('090-1234-5678'); // "090-1234-5678"
echo normalizePhoneNumber('09012345678');   // "090-1234-5678"
?>

3. 配列・データの正規化

配列の正規化

<?php
class DataNormalizer {
    
    /**
     * 配列のキーを正規化
     */
    public static function normalizeKeys(array $array, $case = 'snake') {
        $normalized = [];
        
        foreach ($array as $key => $value) {
            $normalizedKey = self::normalizeKey($key, $case);
            $normalized[$normalizedKey] = is_array($value) 
                ? self::normalizeKeys($value, $case) 
                : $value;
        }
        
        return $normalized;
    }
    
    /**
     * キーの形式を統一
     */
    private static function normalizeKey($key, $case) {
        switch ($case) {
            case 'snake':
                return strtolower(preg_replace('/(?<!^)[A-Z]/', '_$0', $key));
            case 'camel':
                return lcfirst(str_replace(' ', '', ucwords(str_replace('_', ' ', $key))));
            case 'pascal':
                return str_replace(' ', '', ucwords(str_replace('_', ' ', $key)));
            default:
                return $key;
        }
    }
    
    /**
     * 空の値を除去
     */
    public static function removeEmpty(array $array, $strict = false) {
        return array_filter($array, function($value) use ($strict) {
            if ($strict) {
                return $value !== null && $value !== '';
            }
            return !empty($value);
        });
    }
}

// 使用例
$data = [
    'firstName' => 'John',
    'lastName' => 'Doe',
    'phoneNumber' => '090-1234-5678',
    'emptyField' => '',
    'nullField' => null
];

$normalized = DataNormalizer::normalizeKeys($data, 'snake');
// ['first_name' => 'John', 'last_name' => 'Doe', ...]

$cleaned = DataNormalizer::removeEmpty($normalized, true);
// 空の値が除去される
?>

4. ファイルパスの正規化

<?php
function normalizePath($path) {
    // バックスラッシュをスラッシュに統一
    $path = str_replace('\\', '/', $path);
    
    // 連続するスラッシュを単一に
    $path = preg_replace('#/+#', '/', $path);
    
    // . と .. の解決
    $parts = explode('/', $path);
    $absolute = ($path[0] === '/');
    $stack = [];
    
    foreach ($parts as $part) {
        if ($part === '' || $part === '.') {
            continue;
        }
        
        if ($part === '..') {
            if (!empty($stack)) {
                array_pop($stack);
            }
        } else {
            $stack[] = $part;
        }
    }
    
    $result = implode('/', $stack);
    return $absolute ? '/' . $result : $result;
}

// 使用例
echo normalizePath('./path/../to/./file.txt');     // "to/file.txt"
echo normalizePath('/home//user///documents/');    // "/home/user/documents"
?>

5. URL正規化

<?php
function normalizeUrl($url) {
    $parsed = parse_url($url);
    
    if (!$parsed) {
        return false;
    }
    
    // スキームを小文字に
    $scheme = isset($parsed['scheme']) ? strtolower($parsed['scheme']) : 'http';
    
    // ホストを小文字に
    $host = isset($parsed['host']) ? strtolower($parsed['host']) : '';
    
    // デフォルトポートの除去
    $port = isset($parsed['port']) ? $parsed['port'] : null;
    if ($port && (
        ($scheme === 'http' && $port == 80) ||
        ($scheme === 'https' && $port == 443)
    )) {
        $port = null;
    }
    
    // パスの正規化
    $path = isset($parsed['path']) ? normalizePath($parsed['path']) : '/';
    
    // URLの再構築
    $normalized = $scheme . '://' . $host;
    if ($port) {
        $normalized .= ':' . $port;
    }
    $normalized .= $path;
    
    if (isset($parsed['query'])) {
        $normalized .= '?' . $parsed['query'];
    }
    
    if (isset($parsed['fragment'])) {
        $normalized .= '#' . $parsed['fragment'];
    }
    
    return $normalized;
}

// 使用例
echo normalizeUrl('HTTP://Example.COM:80/Path//To/../Page/');
// "http://example.com/Path/Page/"
?>

実際の開発での活用例

ユーザー入力の正規化

<?php
class UserInputNormalizer {
    public static function normalizeUserData($data) {
        $normalized = [];
        
        foreach ($data as $field => $value) {
            switch ($field) {
                case 'email':
                    $normalized[$field] = self::normalizeEmail($value);
                    break;
                case 'phone':
                    $normalized[$field] = normalizePhoneNumber($value);
                    break;
                case 'name':
                    $normalized[$field] = normalizeString($value, [
                        'trim' => true,
                        'remove_extra_spaces' => true
                    ]);
                    break;
                default:
                    $normalized[$field] = normalizeString($value);
            }
        }
        
        return $normalized;
    }
    
    private static function normalizeEmail($email) {
        $email = trim(strtolower($email));
        return filter_var($email, FILTER_VALIDATE_EMAIL) ? $email : '';
    }
}
?>

パフォーマンス最適化

<?php
class CachedNormalizer {
    private static $cache = [];
    private static $maxCacheSize = 1000;
    
    public static function normalize($string, $type = 'string') {
        $key = md5($string . $type);
        
        if (isset(self::$cache[$key])) {
            return self::$cache[$key];
        }
        
        // キャッシュサイズ制限
        if (count(self::$cache) >= self::$maxCacheSize) {
            self::$cache = array_slice(self::$cache, 100, null, true);
        }
        
        switch ($type) {
            case 'string':
                $result = normalizeString($string);
                break;
            case 'url':
                $result = normalizeUrl($string);
                break;
            default:
                $result = $string;
        }
        
        self::$cache[$key] = $result;
        return $result;
    }
}
?>

まとめ

正規化処理は、データの品質と一貫性を保つために欠かせない技術です。PHPでは:

  • Unicode正規化: Normalizerクラスで文字表現を統一
  • 文字列正規化: カスタム関数で空白や大小文字を処理
  • データ正規化: 配列やオブジェクトの構造を統一
  • パス・URL正規化: ファイルパスやURLを標準化

適切な正規化処理を実装することで、バグの減少、検索精度の向上、データの整合性確保が可能になります。用途に応じて最適な正規化手法を選択し、パフォーマンスも考慮した実装を心がけましょう。

タイトルとURLをコピーしました