[PHP]文字コード変換の基礎!ord関数の使い方と実践的な活用例

PHP

はじめに

PHPで文字列を扱う際、文字を数値として処理したい場面に遭遇することがあります。例えば、文字コードの比較、バイナリデータの処理、独自のエンコーディング処理などです。

そんな時に活躍するのが ord 関数です。この記事では、文字をASCIIコード(数値)に変換するこの基本的ながら重要な関数について、基礎から実践的な活用法まで詳しく解説します。

ord関数とは

ord は、文字列の最初の1バイトを取得し、その値を0〜255の整数として返す関数です。主にASCII文字の数値コードを取得する際に使用されます。

基本構文

int ord(string $character)

パラメータ

  • $character (string): 変換したい文字を含む文字列

戻り値

  • int: 文字列の最初の1バイトの数値(0〜255)

基本的な使用例

シンプルな文字コード取得

<?php
// アルファベットの文字コード
echo ord('A');      // 出力: 65
echo ord('a');      // 出力: 97
echo ord('Z');      // 出力: 90
echo ord('z');      // 出力: 122

// 数字の文字コード
echo ord('0');      // 出力: 48
echo ord('9');      // 出力: 57

// 記号の文字コード
echo ord(' ');      // 出力: 32(スペース)
echo ord('!');      // 出力: 33
echo ord('.');      // 出力: 46

// 改行文字
echo ord("\n");     // 出力: 10
echo ord("\r");     // 出力: 13
echo ord("\t");     // 出力: 9(タブ)
?>

複数文字の処理

<?php
// 文字列の各文字の文字コードを表示
function displayCharCodes($string) {
    echo "文字列: \"{$string}\"\n";
    echo "文字コード一覧:\n";
    
    for ($i = 0; $i < strlen($string); $i++) {
        $char = $string[$i];
        $code = ord($char);
        echo "  '{$char}' => {$code}\n";
    }
}

displayCharCodes("Hello");
// 出力:
// 文字列: "Hello"
// 文字コード一覧:
//   'H' => 72
//   'e' => 101
//   'l' => 108
//   'l' => 108
//   'o' => 111
?>

chr関数との関係(逆変換)

<?php
// ord()とchr()は相互変換の関係
$char = 'A';
$code = ord($char);        // 65
$back = chr($code);        // 'A'

echo "元の文字: {$char}\n";
echo "文字コード: {$code}\n";
echo "逆変換: {$back}\n";

// 文字コードの範囲を確認
echo "\n=== ASCII印字可能文字 ===\n";
for ($i = 32; $i <= 126; $i++) {
    echo chr($i) . " ";
    if (($i - 31) % 20 == 0) echo "\n";
}
?>

実践的な活用例

1. 文字種の判定

<?php
class CharValidator {
    /**
     * 大文字アルファベットかチェック
     */
    public static function isUppercase($char) {
        $code = ord($char);
        return $code >= 65 && $code <= 90;  // A-Z
    }
    
    /**
     * 小文字アルファベットかチェック
     */
    public static function isLowercase($char) {
        $code = ord($char);
        return $code >= 97 && $code <= 122;  // a-z
    }
    
    /**
     * アルファベットかチェック
     */
    public static function isAlpha($char) {
        return self::isUppercase($char) || self::isLowercase($char);
    }
    
    /**
     * 数字かチェック
     */
    public static function isDigit($char) {
        $code = ord($char);
        return $code >= 48 && $code <= 57;  // 0-9
    }
    
    /**
     * 英数字かチェック
     */
    public static function isAlphanumeric($char) {
        return self::isAlpha($char) || self::isDigit($char);
    }
    
    /**
     * 空白文字かチェック
     */
    public static function isWhitespace($char) {
        $code = ord($char);
        // スペース、タブ、改行、キャリッジリターン
        return in_array($code, [9, 10, 13, 32]);
    }
    
    /**
     * 印字可能文字かチェック
     */
    public static function isPrintable($char) {
        $code = ord($char);
        return $code >= 32 && $code <= 126;
    }
}

// 使用例
$test_chars = ['A', 'z', '5', ' ', '@', "\n", 'あ'];

foreach ($test_chars as $char) {
    echo "文字: '{$char}' (コード: " . ord($char) . ")\n";
    echo "  大文字: " . (CharValidator::isUppercase($char) ? 'Yes' : 'No') . "\n";
    echo "  小文字: " . (CharValidator::isLowercase($char) ? 'Yes' : 'No') . "\n";
    echo "  数字: " . (CharValidator::isDigit($char) ? 'Yes' : 'No') . "\n";
    echo "  印字可能: " . (CharValidator::isPrintable($char) ? 'Yes' : 'No') . "\n";
    echo "\n";
}
?>

2. シーザー暗号の実装

<?php
class CaesarCipher {
    /**
     * シーザー暗号で暗号化
     * @param string $text 平文
     * @param int $shift シフト量
     * @return string 暗号文
     */
    public static function encrypt($text, $shift = 3) {
        $result = '';
        
        for ($i = 0; $i < strlen($text); $i++) {
            $char = $text[$i];
            $code = ord($char);
            
            if (ord('A') <= $code && $code <= ord('Z')) {
                // 大文字の処理
                $shifted = (($code - ord('A') + $shift) % 26) + ord('A');
                $result .= chr($shifted);
            } elseif (ord('a') <= $code && $code <= ord('z')) {
                // 小文字の処理
                $shifted = (($code - ord('a') + $shift) % 26) + ord('a');
                $result .= chr($shifted);
            } else {
                // アルファベット以外はそのまま
                $result .= $char;
            }
        }
        
        return $result;
    }
    
    /**
     * シーザー暗号で復号化
     * @param string $text 暗号文
     * @param int $shift シフト量
     * @return string 平文
     */
    public static function decrypt($text, $shift = 3) {
        // 復号化は負のシフト
        return self::encrypt($text, 26 - $shift);
    }
    
    /**
     * 総当たりで復号化を試みる
     * @param string $text 暗号文
     * @return array 全パターンの結果
     */
    public static function bruteForce($text) {
        $results = [];
        
        for ($shift = 0; $shift < 26; $shift++) {
            $results[$shift] = self::decrypt($text, $shift);
        }
        
        return $results;
    }
}

// 使用例
$original = "Hello World!";
$encrypted = CaesarCipher::encrypt($original, 3);
$decrypted = CaesarCipher::decrypt($encrypted, 3);

echo "元のテキスト: {$original}\n";
echo "暗号化: {$encrypted}\n";
echo "復号化: {$decrypted}\n";

// 総当たり攻撃のデモ
echo "\n=== 総当たり攻撃 ===\n";
$attempts = CaesarCipher::bruteForce($encrypted);
foreach ($attempts as $shift => $result) {
    echo "シフト {$shift}: {$result}\n";
}
?>

3. 文字列の差分計算

<?php
class StringComparator {
    /**
     * 2つの文字列の文字コード差分を計算
     */
    public static function calculateDifference($str1, $str2) {
        $max_len = max(strlen($str1), strlen($str2));
        $differences = [];
        
        for ($i = 0; $i < $max_len; $i++) {
            $char1 = $i < strlen($str1) ? $str1[$i] : null;
            $char2 = $i < strlen($str2) ? $str2[$i] : null;
            
            $code1 = $char1 !== null ? ord($char1) : 0;
            $code2 = $char2 !== null ? ord($char2) : 0;
            
            $differences[] = [
                'position' => $i,
                'char1' => $char1,
                'char2' => $char2,
                'code1' => $code1,
                'code2' => $code2,
                'diff' => abs($code1 - $code2),
                'same' => $char1 === $char2
            ];
        }
        
        return $differences;
    }
    
    /**
     * 差分を視覚的に表示
     */
    public static function displayDifference($str1, $str2) {
        $diffs = self::calculateDifference($str1, $str2);
        
        echo "文字列1: \"{$str1}\"\n";
        echo "文字列2: \"{$str2}\"\n";
        echo str_repeat("=", 60) . "\n";
        
        foreach ($diffs as $diff) {
            $status = $diff['same'] ? '✓' : '✗';
            echo sprintf(
                "[%d] %s '%s'(%d) vs '%s'(%d) | 差分: %d\n",
                $diff['position'],
                $status,
                $diff['char1'] ?? 'null',
                $diff['code1'],
                $diff['char2'] ?? 'null',
                $diff['code2'],
                $diff['diff']
            );
        }
    }
    
    /**
     * 文字列の類似度を計算(簡易版)
     */
    public static function calculateSimilarity($str1, $str2) {
        $diffs = self::calculateDifference($str1, $str2);
        $same_count = 0;
        
        foreach ($diffs as $diff) {
            if ($diff['same']) {
                $same_count++;
            }
        }
        
        $max_len = max(strlen($str1), strlen($str2));
        return $max_len > 0 ? ($same_count / $max_len) * 100 : 0;
    }
}

// 使用例
StringComparator::displayDifference("Hello", "Hallo");

echo "\n類似度: " . StringComparator::calculateSimilarity("Hello", "Hallo") . "%\n";
?>

4. チェックサム計算

<?php
class ChecksumCalculator {
    /**
     * 単純な加算チェックサム
     */
    public static function simpleChecksum($data) {
        $sum = 0;
        
        for ($i = 0; $i < strlen($data); $i++) {
            $sum += ord($data[$i]);
        }
        
        return $sum % 256;  // 1バイトに収める
    }
    
    /**
     * XORチェックサム
     */
    public static function xorChecksum($data) {
        $checksum = 0;
        
        for ($i = 0; $i < strlen($data); $i++) {
            $checksum ^= ord($data[$i]);
        }
        
        return $checksum;
    }
    
    /**
     * チェックサム検証
     */
    public static function verifyChecksum($data, $expected_checksum, $method = 'simple') {
        $calculated = $method === 'xor' 
            ? self::xorChecksum($data) 
            : self::simpleChecksum($data);
        
        return $calculated === $expected_checksum;
    }
    
    /**
     * データにチェックサムを付加
     */
    public static function appendChecksum($data, $method = 'simple') {
        $checksum = $method === 'xor' 
            ? self::xorChecksum($data) 
            : self::simpleChecksum($data);
        
        return $data . chr($checksum);
    }
    
    /**
     * チェックサム検証付きデータ抽出
     */
    public static function extractAndVerify($data_with_checksum, $method = 'simple') {
        if (strlen($data_with_checksum) < 2) {
            return ['valid' => false, 'data' => null, 'error' => 'データが短すぎます'];
        }
        
        $data = substr($data_with_checksum, 0, -1);
        $checksum = ord(substr($data_with_checksum, -1));
        
        $valid = self::verifyChecksum($data, $checksum, $method);
        
        return [
            'valid' => $valid,
            'data' => $valid ? $data : null,
            'checksum' => $checksum,
            'error' => $valid ? null : 'チェックサムが一致しません'
        ];
    }
}

// 使用例
$original_data = "Important Data";

// チェックサムを付加
$data_with_checksum = ChecksumCalculator::appendChecksum($original_data);

echo "元データ: {$original_data}\n";
echo "チェックサム付きデータ長: " . strlen($data_with_checksum) . "\n";
echo "チェックサム値: " . ord(substr($data_with_checksum, -1)) . "\n";

// 検証
$result = ChecksumCalculator::extractAndVerify($data_with_checksum);
echo "検証結果: " . ($result['valid'] ? '✓ 正常' : '✗ エラー') . "\n";
echo "抽出データ: {$result['data']}\n";

// データ改ざんのテスト
$tampered_data = "Important Date" . substr($data_with_checksum, -1);
$tampered_result = ChecksumCalculator::extractAndVerify($tampered_data);
echo "\n改ざんデータの検証: " . ($tampered_result['valid'] ? '✓ 正常' : '✗ エラー') . "\n";
echo "エラー: {$tampered_result['error']}\n";
?>

5. バイナリデータの可視化

<?php
class BinaryVisualizer {
    /**
     * バイナリデータを16進数ダンプで表示
     */
    public static function hexDump($data, $bytes_per_line = 16) {
        $len = strlen($data);
        
        echo "オフセット  ";
        for ($i = 0; $i < $bytes_per_line; $i++) {
            echo sprintf("%02X ", $i);
        }
        echo " ASCII\n";
        echo str_repeat("-", $bytes_per_line * 3 + 30) . "\n";
        
        for ($i = 0; $i < $len; $i += $bytes_per_line) {
            // オフセット表示
            echo sprintf("%08X  ", $i);
            
            // 16進数表示
            for ($j = 0; $j < $bytes_per_line; $j++) {
                if ($i + $j < $len) {
                    echo sprintf("%02X ", ord($data[$i + $j]));
                } else {
                    echo "   ";
                }
            }
            
            echo " ";
            
            // ASCII表示
            for ($j = 0; $j < $bytes_per_line && ($i + $j) < $len; $j++) {
                $char = $data[$i + $j];
                $code = ord($char);
                // 印字可能文字のみ表示
                echo ($code >= 32 && $code <= 126) ? $char : '.';
            }
            
            echo "\n";
        }
    }
    
    /**
     * バイト値の統計情報を表示
     */
    public static function analyzeBytes($data) {
        $stats = [
            'length' => strlen($data),
            'min' => 255,
            'max' => 0,
            'sum' => 0,
            'frequency' => array_fill(0, 256, 0)
        ];
        
        for ($i = 0; $i < $stats['length']; $i++) {
            $byte = ord($data[$i]);
            $stats['sum'] += $byte;
            $stats['min'] = min($stats['min'], $byte);
            $stats['max'] = max($stats['max'], $byte);
            $stats['frequency'][$byte]++;
        }
        
        $stats['average'] = $stats['length'] > 0 
            ? $stats['sum'] / $stats['length'] 
            : 0;
        
        echo "=== バイナリデータ分析 ===\n";
        echo "データ長: {$stats['length']} バイト\n";
        echo "最小値: {$stats['min']}\n";
        echo "最大値: {$stats['max']}\n";
        echo "平均値: " . number_format($stats['average'], 2) . "\n";
        
        // 頻出バイト値
        arsort($stats['frequency']);
        echo "\n頻出バイト値 (上位10個):\n";
        $count = 0;
        foreach ($stats['frequency'] as $byte => $freq) {
            if ($freq > 0 && $count < 10) {
                $char = ($byte >= 32 && $byte <= 126) ? chr($byte) : '.';
                echo sprintf("  0x%02X ('%s'): %d回\n", $byte, $char, $freq);
                $count++;
            }
        }
        
        return $stats;
    }
}

// 使用例
$sample_data = "Hello, World! This is a binary data sample.\n";
$sample_data .= "It contains various characters: 123 !@#$%";

echo "=== 16進数ダンプ ===\n";
BinaryVisualizer::hexDump($sample_data);

echo "\n";
BinaryVisualizer::analyzeBytes($sample_data);
?>

マルチバイト文字との注意点

<?php
// ord()は最初の1バイトのみを処理
$japanese = "あいうえお";

echo "日本語文字列: {$japanese}\n";
echo "最初の1バイト: " . ord($japanese) . "\n";
echo "文字列長: " . strlen($japanese) . " バイト\n";
echo "文字数: " . mb_strlen($japanese) . " 文字\n\n";

// マルチバイト文字の正しい処理
function getUnicodeCodePoint($char) {
    $values = unpack('N', mb_convert_encoding($char, 'UTF-32BE', 'UTF-8'));
    return $values[1] ?? null;
}

$chars = ['A', 'あ', '🍎', '€'];

foreach ($chars as $char) {
    echo "文字: {$char}\n";
    echo "  ord(): " . ord($char) . " (最初の1バイトのみ)\n";
    echo "  Unicodeコードポイント: U+" . 
         sprintf("%04X", getUnicodeCodePoint($char)) . "\n";
    echo "\n";
}

// 推奨: マルチバイト文字を扱う場合
function safeGetByteValues($string) {
    $encoding = mb_detect_encoding($string, ['UTF-8', 'ASCII', 'ISO-8859-1'], true);
    
    echo "エンコーディング: {$encoding}\n";
    echo "バイト値:\n";
    
    for ($i = 0; $i < strlen($string); $i++) {
        echo sprintf("  [%d] 0x%02X (%d)\n", $i, ord($string[$i]), ord($string[$i]));
    }
}

safeGetByteValues("Hello");
echo "\n";
safeGetByteValues("こんにちは");
?>

パフォーマンスの考慮事項

<?php
// 大量の文字を処理する場合の最適化

function processStringNaive($string) {
    $start = microtime(true);
    
    $result = [];
    for ($i = 0; $i < strlen($string); $i++) {
        $result[] = ord($string[$i]);
    }
    
    $elapsed = microtime(true) - $start;
    echo "素朴な方法: " . number_format($elapsed * 1000, 4) . " ms\n";
    
    return $result;
}

function processStringOptimized($string) {
    $start = microtime(true);
    
    // unpackを使った高速化
    $result = array_values(unpack('C*', $string));
    
    $elapsed = microtime(true) - $start;
    echo "最適化版: " . number_format($elapsed * 1000, 4) . " ms\n";
    
    return $result;
}

// ベンチマーク
$test_string = str_repeat("The quick brown fox jumps over the lazy dog. ", 1000);

echo "テスト文字列長: " . strlen($test_string) . " バイト\n\n";

$result1 = processStringNaive($test_string);
$result2 = processStringOptimized($test_string);

echo "\n結果が一致: " . ($result1 === $result2 ? '✓' : '✗') . "\n";
?>

まとめ

ord 関数は、文字とバイト値を橋渡しするPHPの基本的な関数です。

主な用途:

  • 文字コードの取得: ASCII/バイナリ値の確認
  • 文字種の判定: カスタムバリデーション処理
  • 暗号化・エンコーディング: 簡易的な暗号化処理
  • チェックサム計算: データ整合性の検証
  • バイナリデータ処理: プロトコル実装やデータ解析

注意点:

  • 1バイトのみを処理(マルチバイト文字には注意)
  • UTF-8などのマルチバイトエンコーディングでは mb_* 関数を併用
  • 大量データ処理には unpack() などの最適化を検討

シンプルな関数ですが、低レベルな文字列処理やバイナリデータの扱いにおいて、非常に重要な役割を果たします。基礎をしっかり理解することで、より高度な文字列処理やデータ操作が可能になります。


文字コードの理解は、プログラミングの基礎です。この記事を通じて、PHPでの文字列処理の深い理解につながれば幸いです。

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